Skip to content

Commit

Permalink
Merge branch 'dev' into feat/flexible-multisig
Browse files Browse the repository at this point in the history
  • Loading branch information
johnthecat committed Nov 21, 2024
2 parents 560271f + e6dd499 commit 9ae460f
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 40 deletions.
18 changes: 17 additions & 1 deletion src/renderer/entities/contact/model/contact-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createEffect, createStore, sample } from 'effector';

import { storageService } from '@/shared/api/storage';
import { type Contact, kernelModel } from '@/shared/core';
import { splice } from '@/shared/lib/utils';
import { merge, splice } from '@/shared/lib/utils';

const $contacts = createStore<Contact[]>([]);

Expand All @@ -24,6 +24,14 @@ const updateContactFx = createEffect(async ({ id, ...rest }: Contact): Promise<C
return { id, ...rest };
});

const updateContactsFx = createEffect(async (contacts: Contact[]): Promise<Contact[]> => {
if (contacts.length === 0) return [];

await storageService.contacts.updateAll(contacts);

return contacts;
});

const deleteContactFx = createEffect(async (contactId: number): Promise<number> => {
await storageService.contacts.delete(contactId);

Expand All @@ -44,6 +52,13 @@ $contacts
const position = state.findIndex((s) => s.id === contact.id);

return splice(state, contact, position);
})
.on(updateContactsFx.doneData, (state, contacts) => {
return merge({
a: state,
b: contacts,
mergeBy: (c) => c.id,
});
});

sample({
Expand All @@ -58,5 +73,6 @@ export const contactModel = {
createContactsFx,
deleteContactFx,
updateContactFx,
updateContactsFx,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';

import { chainsService } from '@/shared/api/network';
import { type Account, type Chain } from '@/shared/core';
import { isStringsMatchQuery } from '@/shared/lib/utils';
import { isStringsMatchQuery, nullable } from '@/shared/lib/utils';
import { AssetsListView, EmptyAssetsState } from '@/entities/asset';
import { balanceModel } from '@/entities/balance';
import { networkModel, networkUtils } from '@/entities/network';
Expand Down Expand Up @@ -34,26 +34,25 @@ export const AssetsChainView = ({ query, activeShards, hideZeroBalances, assetsV
if (!activeWallet || assetsView !== AssetsListView.CHAIN_CENTRIC || !activeShards.length) return;

const isMultisig = walletUtils.isMultisig(activeWallet);
const multisigChainToInclude = isMultisig ? activeWallet.accounts[0].chainId : undefined;

const availableChains = Object.values(chains).filter((chain) => {
return activeWallet.accounts.some((account) => {
return (
activeWallet &&
accountUtils.isNonBaseVaultAccount(account, activeWallet) &&
accountUtils.isChainAndCryptoMatch(account, chain)
);
});
});

const filteredChains = availableChains.filter((c) => {
if (!connections[c.chainId]) {
return false;
}
const connection = connections[c.chainId];

const isDisabled = networkUtils.isDisabledConnection(connections[c.chainId]);
const hasMultiPallet = !isMultisig || networkUtils.isMultisigSupported(c.options);
if (nullable(connection)) return false;
if (networkUtils.isDisabledConnection(connection)) return false;
if (!isMultisig) return true;

return !isDisabled && hasMultiPallet;
return networkUtils.isMultisigSupported(c.options) || multisigChainToInclude === c.chainId;
});

const sortedChains = chainsService.sortChainsByBalance(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createEffect, createEvent, createStore, restore, sample } from 'effecto
import { once } from 'patronum';

import { type Account, type AssetByChains, type Balance, type Chain, type ChainId, type Wallet } from '@/shared/core';
import { includes } from '@/shared/lib/utils';
import { includes, nullable } from '@/shared/lib/utils';
import { AssetsListView } from '@/entities/asset';
import { balanceModel } from '@/entities/balance';
import { networkModel, networkUtils } from '@/entities/network';
Expand Down Expand Up @@ -34,8 +34,9 @@ type UpdateTokenParams = {

const getUpdatedTokensFx = createEffect(({ activeWallet, chains }: UpdateTokenParams): AssetByChains[] => {
const tokens = tokensService.getTokensData();
const updatedTokens: AssetByChains[] = [];

return tokens.reduce((acc, token) => {
for (const token of tokens) {
const filteredChains = token.chains.filter((chain) => {
return activeWallet?.accounts.some((account) => {
return (
Expand All @@ -45,12 +46,12 @@ const getUpdatedTokensFx = createEffect(({ activeWallet, chains }: UpdateTokenPa
});
});

if (filteredChains.length > 0) {
acc.push({ ...token, chains: filteredChains });
}
if (filteredChains.length === 0) continue;

updatedTokens.push({ ...token, chains: filteredChains });
}

return acc;
}, [] as AssetByChains[]);
return updatedTokens;
});

type PopulateBalanceParams = {
Expand All @@ -61,15 +62,17 @@ type PopulateBalanceParams = {

const populateTokensBalanceFx = createEffect(
({ activeTokens, balances, accounts }: PopulateBalanceParams): AssetByChains[] => {
return activeTokens.reduce<AssetByChains[]>((acc, token) => {
const tokens: AssetByChains[] = [];

for (const token of activeTokens) {
const [chainsWithBalance, totalBalance] = tokensService.getChainWithBalance(balances, token.chains, accounts);

if (chainsWithBalance.length > 0) {
acc.push({ ...token, chains: chainsWithBalance, totalBalance });
}
if (chainsWithBalance.length === 0) continue;

return acc;
}, []);
tokens.push({ ...token, chains: chainsWithBalance, totalBalance });
}

return tokens;
},
);

Expand Down Expand Up @@ -105,22 +108,29 @@ sample({
},
fn: ({ connections, chains, tokens, activeWallet }): AssetByChains[] => {
const isMultisigWallet = walletUtils.isMultisig(activeWallet);
const hasAccounts = activeWallet!.accounts.length > 0;
const multisigChainToInclude = isMultisigWallet && hasAccounts ? activeWallet.accounts[0].chainId : undefined;

const activeTokens: AssetByChains[] = [];

return tokens.reduce<AssetByChains[]>((acc, token) => {
for (const token of tokens) {
const filteredChains = token.chains.filter((c) => {
if (!connections[c.chainId]) return false;
const isDisabled = networkUtils.isDisabledConnection(connections[c.chainId]);
const hasMultiPallet = networkUtils.isMultisigSupported(chains[c.chainId].options);
const connection = connections[c.chainId];

return !isDisabled && (!isMultisigWallet || hasMultiPallet);
if (nullable(connection)) return false;
if (networkUtils.isDisabledConnection(connection)) return false;
if (nullable(chains[c.chainId])) return false;
if (!isMultisigWallet) return true;

return networkUtils.isMultisigSupported(chains[c.chainId].options) || multisigChainToInclude === c.chainId;
});

if (filteredChains.length > 0) {
acc.push({ ...token, chains: filteredChains });
}
if (filteredChains.length === 0) continue;

activeTokens.push({ ...token, chains: filteredChains });
}

return acc;
}, []);
return activeTokens;
},
target: $activeTokens,
});
Expand Down Expand Up @@ -148,7 +158,9 @@ sample({
clock: [$activeTokensWithBalance, queryChanged],
source: { activeTokensWithBalance: $activeTokensWithBalance, query: $query },
fn: ({ activeTokensWithBalance, query }) => {
return activeTokensWithBalance.reduce<AssetByChains[]>((acc, token) => {
const filteredTokens: AssetByChains[] = [];

for (const token of activeTokensWithBalance) {
const filteredChains = token.chains.filter((chain) => {
const hasSymbol = includes(chain.assetSymbol, query);
const hasAssetName = includes(token.name, query);
Expand All @@ -157,12 +169,12 @@ sample({
return hasSymbol || hasAssetName || hasChainName;
});

if (filteredChains.length > 0) {
acc.push({ ...token, chains: filteredChains });
}
if (filteredChains.length === 0) continue;

filteredTokens.push({ ...token, chains: filteredChains });
}

return acc;
}, []);
return filteredTokens;
},
target: $filteredTokens,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const getWalletByGroups = (wallets: Wallet[], query = ''): Record<WalletFamily,
};

const getFirstWallet = (wallets: Wallet[]) => {
return getWalletByGroups(wallets)[WalletType.POLKADOT_VAULT].at(0) ?? null;
return Object.values(getWalletByGroups(wallets)).flat().at(0) ?? null;
};

export const walletSelectService = {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/shared/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@
"notEnoughSignatories": "You need to select at least 2 signatories",
"notEnoughSignatoriesTitle": "Not enough signatories",
"notEmptySignatoryTitle": "Empty signatory",
"notEmptySignatoryNameTitle": "Empty signatory name",
"notEmptySignatory": "No empty signatory allowed",
"notEmptySignatoryName": "No empty signatory name allowed",
"ownAccountSelection": "Address",
"restoreButton": "Restore",
"searchContactPlaceholder": "Search",
Expand Down
38 changes: 37 additions & 1 deletion src/renderer/widgets/CreateWallet/model/flow-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@ sample({
target: $error,
});

sample({
clock: walletModel.events.walletCreatedDone,
filter: ({ wallet, external }) => wallet.type === WalletType.MULTISIG && !external,
fn: ({ wallet }) => wallet.id,
target: walletProviderModel.events.completed,
});

// Submit

sample({
Expand Down Expand Up @@ -411,9 +418,38 @@ sample({
contacts: contactModel.$contacts,
},
fn: ({ signatories, contacts }) => {
const signatoriesWithoutSigner = signatories.slice(1);
const contactMap = new Map(contacts.map((c) => [c.accountId, c]));
const updatedContacts: Contact[] = [];

for (const { address, name } of signatoriesWithoutSigner) {
const contact = contactMap.get(toAccountId(address));

if (!contact) continue;

updatedContacts.push({
...contact,
name,
});
}

return updatedContacts;
},
target: contactModel.effects.updateContactsFx,
});

sample({
clock: signModel.output.formSubmitted,
source: {
signatories: signatoryModel.$signatories,
contacts: contactModel.$contacts,
},
fn: ({ signatories, contacts }) => {
const contactsSet = new Set(contacts.map((c) => c.accountId));

return signatories
.slice(1)
.filter((signatory) => !contacts.some((contact) => contact.accountId === toAccountId(signatory.address)))
.filter((signatory) => !contactsSet.has(toAccountId(signatory.address)))
.map(
({ address, name }) =>
({
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/widgets/CreateWallet/model/signatory-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const $hasEmptySignatories = combine($signatories, (signatories) => {
return signatories.map(({ address }) => address).includes('');
});

const $hasEmptySignatoryName = combine($signatories, (signatories) => {
return signatories.map(({ name }) => name).includes('');
});

const $ownedSignatoriesWallets = combine(
{ wallets: walletModel.$wallets, signatories: $signatories },
({ wallets, signatories }) =>
Expand Down Expand Up @@ -99,6 +103,7 @@ export const signatoryModel = {
$ownedSignatoriesWallets,
$hasDuplicateSignatories,
$hasEmptySignatories,
$hasEmptySignatoryName,
events: {
addSignatory,
changeSignatory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const SelectSignatoriesThreshold = () => {
const ownedSignatoriesWallets = useUnit(signatoryModel.$ownedSignatoriesWallets);
const hasDuplicateSignatories = useUnit(signatoryModel.$hasDuplicateSignatories);
const hasEmptySignatories = useUnit(signatoryModel.$hasEmptySignatories);
const hasEmptySignatoryName = useUnit(signatoryModel.$hasEmptySignatoryName);

const [hasClickedNext, setHasClickedNext] = useState(false);

Expand All @@ -47,6 +48,7 @@ export const SelectSignatoriesThreshold = () => {
hasEnoughSignatories &&
!multisigAlreadyExists &&
!hasEmptySignatories &&
!hasEmptySignatoryName &&
isThresholdValid &&
!hasDuplicateSignatories;

Expand Down Expand Up @@ -101,6 +103,14 @@ export const SelectSignatoriesThreshold = () => {
>
<Alert.Item withDot={false}>{t('createMultisigAccount.notEmptySignatory')}</Alert.Item>
</Alert>

<Alert
active={hasClickedNext && hasEmptySignatoryName}
title={t('createMultisigAccount.notEmptySignatoryNameTitle')}
variant="error"
>
<Alert.Item withDot={false}>{t('createMultisigAccount.notEmptySignatoryName')}</Alert.Item>
</Alert>
</div>
<div className="flex items-center gap-x-4">
<Box width="300px">
Expand Down

0 comments on commit 9ae460f

Please sign in to comment.