Skip to content

Commit

Permalink
Feat: operations refactoring (#2772)
Browse files Browse the repository at this point in the history
  • Loading branch information
Asmadek authored Jan 15, 2025
1 parent 25bae38 commit f1e5e70
Show file tree
Hide file tree
Showing 49 changed files with 1,286 additions and 474 deletions.
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

0 comments on commit f1e5e70

Please sign in to comment.