Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: operations refactoring #2772

Merged
merged 72 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
edff5ec
refactor: rearrange of wallet select feature, removed some modules co…
johnthecat Nov 6, 2024
89b05f2
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 7, 2024
ab16ebc
feat: proxy pallet (#2590)
johnthecat Nov 7, 2024
e468428
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 8, 2024
7751986
fix: after merge
johnthecat Nov 8, 2024
93f31e7
feat: changed proxy type from enum to string union (#2620)
johnthecat Nov 11, 2024
5e7bf9e
feat: select multisig flow (#2577)
sokolova-an Nov 12, 2024
600e663
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 12, 2024
e2d82f7
Merge dev into feat/flexible-multisig
johnthecat Nov 12, 2024
aec30e4
fix: wallet card layout
johnthecat Nov 12, 2024
f683a39
feat: add flexible multisig wallet and account types (#2636)
johnthecat Nov 12, 2024
85c7d84
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 13, 2024
eb6f21e
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 13, 2024
37925ea
Feat: Input refactoring (#2592)
tuul-wq Nov 14, 2024
4980dcc
Merge dev into feat/flexible-multisig
johnthecat Nov 14, 2024
0b8cc72
Merge dev into feat/flexible-multisig
johnthecat Nov 15, 2024
fa2b071
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 15, 2024
6749c67
Fix: Multisig select (#2626)
tuul-wq Nov 15, 2024
2c3f5f7
Fix: Multisig single chain view (#2649)
tuul-wq Nov 15, 2024
cdf8a1b
chore: merge dev
tuul-wq Nov 18, 2024
8554f51
chore: merge dev
tuul-wq Nov 18, 2024
55ba8e7
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 18, 2024
c19d200
Merge dev into feat/flexible-multisig
johnthecat Nov 19, 2024
8da82dd
feat: flexible multisig discovery (#2635)
johnthecat Nov 19, 2024
16aed7e
feat: add batch transaction for flexible multisig (#2672)
sokolova-an Nov 20, 2024
17ce551
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 20, 2024
560271f
Merge dev into feat/flexible-multisig
johnthecat Nov 20, 2024
9ae460f
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 21, 2024
9ec514c
feat: update types and checks for multisigs (#2693)
sokolova-an Nov 21, 2024
458a35d
feat: add existential deposit (#2706)
sokolova-an Nov 21, 2024
32b6a2e
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 21, 2024
9f5f1e9
Merge dev into feat/flexible-multisig
johnthecat Nov 22, 2024
e707339
fix: fields
johnthecat Nov 22, 2024
dd98571
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 22, 2024
ec82d29
fix: merge
johnthecat Nov 22, 2024
5fc7f33
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 22, 2024
e37e903
feat: update flexible multisig (#2709)
sokolova-an Nov 22, 2024
4fc5599
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 25, 2024
855c0ef
Merge branch 'dev' into feat/flexible-multisig
johnthecat Nov 27, 2024
b80fcc7
Merge dev into feat/flexible-multisig
johnthecat Nov 28, 2024
c22314c
Feat: new multisig operations subscription service (#2654)
Asmadek Nov 28, 2024
6206e5c
Merge dev into feat/flexible-multisig
johnthecat Dec 3, 2024
b8e393e
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Dec 4, 2024
bff2be1
feat: add transfer details
Asmadek Dec 4, 2024
a14c63b
feat: register feature
johnthecat Dec 5, 2024
7796984
Merge branch 'dev' into feat/flexible-multisig
johnthecat Dec 6, 2024
6372bb9
feat: support proxy, governance operations
Asmadek Dec 6, 2024
14ac87a
chore: turned off flexible multisig search
johnthecat Dec 6, 2024
a546119
Merge branch 'dev' into feat/flexible-multisig
johnthecat Dec 6, 2024
fbd450f
feat: add unknown operation titile
Asmadek Dec 6, 2024
ffd863b
Merge branch 'feat/flexible-multisig' of https://github.com/nova-wall…
Asmadek Dec 6, 2024
f48b087
chore: turned off flexible multisig create flow
johnthecat Dec 6, 2024
5fc2ad9
fix: ui issues
Asmadek Dec 6, 2024
ed3ab11
chore: fix provide events to effector
Asmadek Dec 9, 2024
8531080
chore: fix comment
Asmadek Dec 9, 2024
37569ec
Merge branch 'feat/flexible-multisig' of https://github.com/nova-wall…
Asmadek Dec 9, 2024
605cc1c
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Jan 10, 2025
4318259
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Jan 10, 2025
afaa829
chore: fix comments
Asmadek Jan 13, 2025
cdcd3bb
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Jan 13, 2025
a82a947
chore: comment keys
Asmadek Jan 13, 2025
a9649f1
chore: remove console logs
Asmadek Jan 13, 2025
b7143a2
chore: fix operation title for decoded tx
Asmadek Jan 13, 2025
12dea2d
chore: split gov operations
Asmadek Jan 13, 2025
c12b371
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Jan 13, 2025
2610efe
chore: fix comments
Asmadek Jan 13, 2025
cda1d5f
chore: remove unused file
Asmadek Jan 13, 2025
c9fc67d
chore: remove unused files
Asmadek Jan 13, 2025
8f63b6e
chore: remove unused file
Asmadek Jan 13, 2025
f3989e2
chore: fix comments
Asmadek Jan 15, 2025
b99c6ef
Merge branch 'dev' of https://github.com/nova-wallet/omni-enterprise …
Asmadek Jan 15, 2025
98457d3
chore: fix xcm asset id
Asmadek Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/renderer/app/modelInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ import { contactsNavigationFeature } from '@/features/contacts-navigation';
import { fellowshipNavigationFeature } from '@/features/fellowship-navigation';
import { flexibleMultisigNavigationFeature } from '@/features/flexible-multisig-navigation';
import { governanceNavigationFeature } from '@/features/governance-navigation';
import { governanceOperationDetailFeature } from '@/features/governance-operation-details';
import { importDBFeature } from '@/features/import-db';
import { multisigOperationDetailsFeature } from '@/features/multisig-operation-details';
import { notificationsNavigationFeature } from '@/features/notifications-navigation';
import { operationsNavigationFeature } from '@/features/operations-navigation';
import { proxiesModel } from '@/features/proxies';
import { proxyOperationDetailFeature } from '@/features/proxy-operation-details';
import { settingsNavigationFeature } from '@/features/settings-navigation';
import { stakingNavigationFeature } from '@/features/staking-navigation';
import { stakingOperationDetailFeature } from '@/features/staking-operation-details';
import { transferOperationDetailFeature } from '@/features/transfer-operation-details';
import { walletDetailsFeature } from '@/features/wallet-details';
import { walletMultisigFeature } from '@/features/wallet-multisig';
import { walletPairingFeature } from '@/features/wallet-pairing';
Expand Down Expand Up @@ -79,6 +84,12 @@ export const initModel = () => {
walletWalletConnectFeature,
walletWatchOnlyFeature,

governanceOperationDetailFeature,
multisigOperationDetailsFeature,
proxyOperationDetailFeature,
stakingOperationDetailFeature,
transferOperationDetailFeature,

importDBFeature,
]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const createNewEventsPayload = (
approvals: Vec<AccountId32>,
): MultisigEvent[] => {
return approvals.reduce<MultisigEvent[]>((acc, a) => {
const hasApprovalEvent = events.find((e) => e.status === 'SIGNED' && e.accountId === a.toHex());
const hasApprovalEvent = events.some((e) => e.status === 'SIGNED' && e.accountId === a.toHex());

if (!hasApprovalEvent) {
acc.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const useMultisigTx = ({ addTask }: Props): IMultisigTxService => {
});

const newEvents = createNewEventsPayload(oldEvents, newestOldTx, pendingTx.params.approvals);

for (const e of newEvents) {
addEventWithQueue(e);
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/entities/operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './ui';
export { operationsModel } from './model/operations-model';
export { operationsUtils } from './lib/operationsUtils';
export * as operationDetailsUtils from './lib/operationDetailsUtils';
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
TransactionType,
type Wallet,
} from '@/shared/core';
import { toAddress } from '@/shared/lib/utils';
import { dictionary, toAddress } from '@/shared/lib/utils';
import { convictionVotingPallet } from '@/shared/pallet/convictionVoting';
import { type AccountId } from '@/shared/polkadotjs-schemas';
import { type TransactionVote, votingService } from '@/entities/governance';
Expand Down Expand Up @@ -48,7 +48,7 @@ export const getSignatoryName = (
addressPrefix?: number,
): string => {
const finderFn = <T extends { accountId: AccountId }>(collection: T[]): T | undefined => {
return collection.find((c) => c?.accountId === signatoryId);
return collection.find((c) => c.accountId === signatoryId);
};

// signatory data source priority: transaction -> contacts -> wallets -> address
Expand All @@ -72,33 +72,35 @@ export const getSignatoryAccounts = (
signatories: Signatory[],
chainId: ChainId,
): Account[] => {
const walletsMap = new Map(wallets.map((wallet) => [wallet.id, wallet]));
const walletsMap = dictionary(wallets, 'id');

return signatories.reduce((acc: Account[], signatory) => {
const result = [];

for (const signatory of signatories) {
const filteredAccounts = accounts.filter(
(a) => a.accountId === signatory.accountId && !events.some((e) => e.accountId === a.accountId),
);

const signatoryAccount = filteredAccounts.find((a) => {
const isChainMatch = accountUtils.isChainIdMatch(a, chainId);
const wallet = walletsMap.get(a.walletId);
const wallet = walletsMap[a.walletId];

return isChainMatch && walletUtils.isValidSignatory(wallet);
});

if (signatoryAccount) {
acc.push(signatoryAccount);
result.push(signatoryAccount);
} else {
const legacySignatoryAccount = filteredAccounts.find(
(a) => accountUtils.isVaultChainAccount(a) && a.chainId === chainId,
(a) => accountUtils.isChainDependant(a) && a.chainId === chainId,
);
if (legacySignatoryAccount) {
acc.push(legacySignatoryAccount);
result.push(legacySignatoryAccount);
}
}
}

return acc;
}, []);
return result;
};

export const getDestination = (
Expand All @@ -117,6 +119,16 @@ export const getDestination = (
return toAddress(tx.transaction.args.dest, { prefix: chain.addressPrefix });
};

export const getDestinationAccountId = (tx: MultisigTransaction): AccountId | undefined => {
if (!tx.transaction) return undefined;

if (isProxyTransaction(tx.transaction)) {
return tx.transaction.args.transaction.args.dest;
}

return tx.transaction.args.dest;
};

export const getPayee = (tx: MultisigTransaction): { Account: Address } | string | undefined => {
if (!tx.transaction) return undefined;

Expand Down
40 changes: 40 additions & 0 deletions src/renderer/entities/operations/ui/OperationTitleDate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useStoreMap } from 'effector-react';

import { type MultisigTransaction } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { FootnoteText } from '@/shared/ui';
import { operationsModel } from '../model/operations-model';

type Props = {
operation: MultisigTransaction;
};

export const OperationTitleDate = ({ operation }: Props) => {
const { formatDate } = useI18n();

const events = useStoreMap({
store: operationsModel.$multisigEvents,
keys: [operation],
fn: (events, [operation]) => {
return events.filter(
(e) =>
e.txAccountId === operation.accountId &&
e.txChainId === operation.chainId &&
e.txCallHash === operation.callHash &&
e.txBlock === operation.blockCreated &&
e.txIndex === operation.indexCreated,
);
},
});
const approvals = events.filter((e) => e.status === 'SIGNED');
const initEvent = approvals.find((e) => e.accountId === operation.depositor);
const date = new Date(operation.dateCreated || initEvent?.dateCreated || Date.now());

return (
<div className="w-[58px] pr-1">
<FootnoteText className="text-text-tertiary" align="right">
{formatDate(date, 'p')}
</FootnoteText>
</div>
);
};
38 changes: 38 additions & 0 deletions src/renderer/entities/operations/ui/OperationTitleStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useStoreMap, useUnit } from 'effector-react';

import { type MultisigTransaction } from '@/shared/core';
import { accountUtils, walletModel } from '@/entities/wallet';
import { operationsModel } from '../model/operations-model';

import { Status } from './Status';

type Props = {
operation: MultisigTransaction;
};

export const OperationTitleStatus = ({ operation }: Props) => {
const events = useStoreMap({
store: operationsModel.$multisigEvents,
keys: [operation],
fn: (events, [operation]) => {
return events.filter(
(e) =>
e.txAccountId === operation.accountId &&
e.txChainId === operation.chainId &&
e.txCallHash === operation.callHash &&
e.txBlock === operation.blockCreated &&
e.txIndex === operation.indexCreated,
);
},
});

const approvals = events.filter((e) => e.status === 'SIGNED');
const activeWallet = useUnit(walletModel.$activeWallet);
const account = activeWallet?.accounts.find(accountUtils.isMultisigAccount);

return (
<div className="flex w-[120px] justify-end">
<Status status={operation.status} signed={approvals.length} threshold={account?.threshold || 0} />
</div>
);
};
3 changes: 3 additions & 0 deletions src/renderer/entities/operations/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { SignatorySelector } from './SignatorySelector';
export { SignButton } from './SignButton';
export { Status } from './Status';
export { OperationTitleStatus } from './OperationTitleStatus';
export { OperationTitleDate } from './OperationTitleDate';
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const SelectSignatoriesThreshold = () => {
fields: { threshold },
submit,
} = useForm(formModel.$createMultisigForm);

const chain = useUnit(formModel.$chain);
const signatories = useUnit(signatoryModel.$signatories);
const multisigAlreadyExists = useUnit(formModel.$multisigAlreadyExists);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useUnit } from 'effector-react';
import { useEffect, useState } from 'react';

import { type Address, type MultisigTransaction, TransactionType } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { toAccountId } from '@/shared/lib/utils';
import { DetailRow, FootnoteText } from '@/shared/ui';
import { Account } from '@/shared/ui-entities';
import { Skeleton } from '@/shared/ui-kit';
import { AssetBalance } from '@/entities/asset';
import { TracksDetails } from '@/entities/governance';
import { getTransactionFromMultisigTx } from '@/entities/multisig';
import { networkModel } from '@/entities/network';
import { operationDetailsUtils } from '@/entities/operations';
import { isUndelegateTransaction } from '@/entities/transaction';

type Props = { operation: MultisigTransaction };

export const GovernanceDelegateDetails = ({ operation }: Props) => {
const { t } = useI18n();
const transaction = getTransactionFromMultisigTx(operation);

const chains = useUnit(networkModel.$chains);
const apis = useUnit(networkModel.$apis);

const chain = chains[operation.chainId];
const api = apis[operation.chainId];

const defaultAsset = chain?.assets[0];

const [isUndelegationLoading, setIsUndelegationLoading] = useState(false);
const [undelegationVotes, setUndelegationVotes] = useState<string>();
const [undelegationTarget, setUndelegationTarget] = useState<Address>();

const result = [];

useEffect(() => {
if (isUndelegateTransaction(transaction)) {
setIsUndelegationLoading(true);
}

if (!api) return;

operationDetailsUtils.getUndelegationData(api, operation).then(({ votes, target }) => {
setUndelegationVotes(votes);
setUndelegationTarget(target);
setIsUndelegationLoading(false);
});
}, [api, operation]);

if (
transaction?.type &&
![TransactionType.DELEGATE, TransactionType.UNDELEGATE, TransactionType.EDIT_DELEGATION].includes(transaction.type)
) {
return null;
}

// TODO: Move this to domain layer
const delegationTarget = operationDetailsUtils.getDelegationTarget(operation);
const delegationTracks = operationDetailsUtils.getDelegationTracks(operation);
const delegationVotes = operationDetailsUtils.getDelegationVotes(operation);

if (isUndelegationLoading) {
result.push(
<>
<DetailRow label={t('operation.details.delegationTarget')}>
<Skeleton width={40} height={6} />
</DetailRow>

<DetailRow label={t('operation.details.delegationVotes')}>
<Skeleton width={20} height={5} />
</DetailRow>
</>,
);
}

if (delegationTarget) {
result.push(
<DetailRow label={t('operation.details.delegationTarget')} className="text-text-secondary">
<Account accountId={toAccountId(delegationTarget)} variant="short" chain={chain} />
</DetailRow>,
);
}

if (!delegationTarget && undelegationTarget) {
result.push(
<DetailRow label={t('operation.details.delegationTarget')} className="text-text-secondary">
<Account accountId={toAccountId(undelegationTarget)} variant="short" chain={chain} />
</DetailRow>,
);
}

if (delegationVotes) {
result.push(
<DetailRow label={t('operation.details.delegationVotes')} className="text-text-secondary">
<FootnoteText>
<AssetBalance
className="text-text-secondary"
value={delegationVotes}
asset={defaultAsset}
showSymbol={false}
/>
</FootnoteText>
</DetailRow>,
);
}

if (!delegationVotes && undelegationVotes) {
result.push(
<DetailRow label={t('operation.details.delegationVotes')} className="text-text-secondary">
<FootnoteText>
<AssetBalance
className="text-text-secondary"
value={undelegationVotes}
asset={defaultAsset}
showSymbol={false}
/>
</FootnoteText>
</DetailRow>,
);
}

if (delegationTracks) {
result.push(
<DetailRow label={t('operation.details.delegationTracks')} className="text-text-secondary">
<div className="-mr-2">
<TracksDetails tracks={delegationTracks.map(Number)} />
</div>
</DetailRow>,
);
}

return <>{result.map((e) => e)}</>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { chainsService } from '@/shared/api/network';
import { type MultisigTransaction } from '@/shared/core';
import { getAssetById } from '@/shared/lib/utils';
import { Box } from '@/shared/ui-kit';
import { AssetBalance } from '@/entities/asset';
import { ChainTitle } from '@/entities/chain';
import { TransactionTitle, getTransactionAmount } from '@/entities/transaction';

type Props = {
operation: MultisigTransaction;
};

export const GovernanceOperationTitle = ({ operation }: Props) => {
const asset =
operation.transaction &&
getAssetById(operation.transaction.args.asset, chainsService.getChainById(operation.chainId)?.assets);
const amount = operation.transaction && getTransactionAmount(operation.transaction);

return (
<>
<TransactionTitle className="flex-1 overflow-hidden" tx={operation.transaction} />

{asset && amount && (
<Box width="160px">
<AssetBalance value={amount} asset={asset} showIcon />
</Box>
)}

<ChainTitle chainId={operation.chainId} className="w-[114px]" />
</>
);
};
Loading
Loading