From 0c1aa317f0ec8f55a9e801f034498ca30b393579 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Tue, 12 Nov 2024 17:32:26 +0300 Subject: [PATCH] refactor: wallet management now scoped in wallet model (except index db write) (#2632) --- .../entities/wallet/lib/model-utils.ts | 15 ++- .../model/__tests__/wallet-model.test.ts | 93 +------------ .../entities/wallet/model/wallet-model.ts | 126 +++++++++++++++--- .../model/wallet-connect-model.ts | 69 +++++----- .../ui/AssetCard/AssetCard.test.tsx | 2 +- .../model/__tests__/balance-sub-model.test.ts | 6 +- .../model/__tests__/sign-model.test.ts | 2 +- .../OperationSign/model/sign-wc-model.ts | 8 +- .../model/__tests__/submit-model.test.ts | 4 +- .../model/__tests__/proxies-model.test.ts | 2 +- .../features/proxies/model/proxies-model.ts | 31 +++-- .../DerivationsAddressModal/lib/utils.ts | 8 +- .../ui/DerivationsAddressModal.tsx | 2 +- .../__tests__/forget-wallet-model.test.ts | 8 +- .../__tests__/rename-wallet-model.test.ts | 4 +- .../RenameWallet/model/rename-wallet-model.ts | 18 +-- .../model/__tests__/shards-model.test.ts | 4 +- .../__tests__/wallet-select-model.test.ts | 28 +--- .../WalletSelect/model/wallet-select-model.ts | 79 +++-------- .../Vault/ManageVault/ManageVault.tsx | 2 +- .../ManageVault/model/manage-vault-model.ts | 60 ++------- .../model/__tests__/multisigs-model.test.ts | 4 +- .../shared/effector/helpers/series.ts | 38 ++++++ src/renderer/shared/effector/index.ts | 2 + .../model/__tests__/add-proxy-model.test.ts | 2 +- .../model/__tests__/form-model.test.ts | 2 +- .../__tests__/add-pure-proxied-model.test.ts | 2 +- .../model/__tests__/form-model.test.ts | 2 +- .../model/__tests__/confirm-model.test.ts | 2 +- .../model/__tests__/flow-model.test.ts | 2 +- .../model/__tests__/form-model.test.ts | 10 +- .../model/__tests__/signatory-model.test.ts | 2 +- .../__tests__/vault-details-model.test.ts | 4 +- .../__tests__/wallet-provider-model.test.ts | 8 +- .../model/vault-details-model.ts | 27 ++-- .../WalletDetails/model/wc-details-model.ts | 1 + 36 files changed, 329 insertions(+), 350 deletions(-) create mode 100644 src/renderer/shared/effector/helpers/series.ts diff --git a/src/renderer/entities/wallet/lib/model-utils.ts b/src/renderer/entities/wallet/lib/model-utils.ts index 6b1495bed8..749e46ef70 100644 --- a/src/renderer/entities/wallet/lib/model-utils.ts +++ b/src/renderer/entities/wallet/lib/model-utils.ts @@ -1,4 +1,4 @@ -import { type BaseAccount, type ChainAccount } from '@/shared/core'; +import { type BaseAccount, type ChainAccount, type ShardAccount } from '@/shared/core'; import { accountUtils } from './account-utils'; @@ -9,9 +9,10 @@ export const modelUtils = { type AccountsGroup = { base: BaseAccount[]; chains: ChainAccount[][]; + shards: ShardAccount[][]; }; -function groupAccounts(accounts: Omit[]): AccountsGroup { - return accounts.reduce<{ base: BaseAccount[]; chains: ChainAccount[][] }>( +function groupAccounts(accounts: Omit[]) { + return accounts.reduce( (acc, account) => { const lastBaseIndex = acc.base.length - 1; @@ -24,9 +25,15 @@ function groupAccounts(accounts: Omit { expect(scope.getState(walletModel.$activeWallet)).toEqual(wallets[0]); }); - test('should update $allWallets on watchOnlyCreated', async () => { - const wallets = walletMock.getWallets(0); - const { newAccounts, newWallet } = walletMock; - - jest.spyOn(storageService.wallets, 'create').mockResolvedValue(newWallet); - jest.spyOn(storageService.accounts, 'createAll').mockResolvedValue([newAccounts[0]]); - jest.spyOn(storageService.wallets, 'update').mockResolvedValue(3); - - const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), - }); - - await allSettled(walletModel.events.watchOnlyCreated, { - scope, - params: { wallet: newWallet, accounts: [newAccounts[0]] as any[] }, - }); - - expect(scope.getState(walletModel.$allWallets)).toEqual( - wallets.concat({ ...newWallet, accounts: [newAccounts[0]] }), - ); - }); - - test('should update $allWallets on multishardCreated', async () => { - const wallets = walletMock.getWallets(0); - const { newAccounts, newWallet } = walletMock; - - jest.spyOn(storageService.wallets, 'create').mockResolvedValue(newWallet); - jest.spyOn(storageService.accounts, 'create').mockResolvedValue(newAccounts[0]); - jest.spyOn(storageService.accounts, 'createAll').mockResolvedValue([newAccounts[1]]); - jest.spyOn(storageService.wallets, 'update').mockResolvedValue(3); - - const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), - }); - - expect(scope.getState(walletModel.$allWallets)).toHaveLength(wallets.length); - await allSettled(walletModel.events.multishardCreated, { - scope, - params: { wallet: newWallet, accounts: newAccounts as any[] }, - }); - - expect(scope.getState(walletModel.$allWallets)).toEqual(wallets.concat({ ...newWallet, accounts: newAccounts })); - }); - test('should update $allWallets on walletRemoved', async () => { const wallets = walletMock.getWallets(0); const [removedWallet, ...remainingWallets] = wallets; @@ -79,13 +35,13 @@ describe('entities/wallet/model/wallet-model', () => { const deleteAccountsSpy = jest.spyOn(storageService.accounts, 'deleteAll').mockResolvedValue([1, 2, 3]); const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); await allSettled(walletModel.events.walletRemoved, { scope, params: removedWallet.id }); expect(deleteAccountsSpy).toHaveBeenCalledWith(removedAccounts.map((a) => a.id)); - expect(scope.getState(walletModel.$allWallets)).toEqual(remainingWallets); + expect(scope.getState(walletModel._test.$allWallets)).toEqual(remainingWallets); }); test('should update $allWallets on walletsRemoved', async () => { @@ -98,52 +54,13 @@ describe('entities/wallet/model/wallet-model', () => { const deleteAccountsSpy = jest.spyOn(storageService.accounts, 'deleteAll').mockResolvedValue([1, 2, 3]); const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); await allSettled(walletModel.events.walletsRemoved, { scope, params: [removedWallet.id] }); expect(deleteAccountsSpy).toHaveBeenCalledWith(removedAccounts.map((a) => a.id)); - expect(scope.getState(walletModel.$allWallets)).toEqual(remainingWallets); - }); - - test('should update $allWallets on walletHidden', async () => { - const wallets = walletMock.getWallets(0); - const hiddenWallet = wallets[0]; - - const updateSpy = jest.spyOn(storageService.wallets, 'update').mockResolvedValue(1); - - const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), - }); - - await allSettled(walletModel.events.walletHidden, { scope, params: hiddenWallet }); - - expect(updateSpy).toHaveBeenCalledWith(1, { isHidden: true }); - expect(scope.getState(walletModel.$allWallets)).toEqual([{ ...hiddenWallet, isHidden: true }, ...wallets.slice(1)]); - }); - - test('should update $allWallets on walletRestore', async () => { - const wallets = walletMock.getWallets(0); - const walletToRestore = wallets.find((wallet) => wallet.isHidden)!; - - const updateSpy = jest.spyOn(storageService.wallets, 'update').mockResolvedValue(walletToRestore.id); - - const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), - }); - - await allSettled(walletModel.$allWallets, { scope, params: wallets }); - expect(scope.getState(walletModel.$hiddenWallets)).toEqual([walletToRestore]); - - await allSettled(walletModel.events.walletRestored, { scope, params: walletToRestore }); - - expect(updateSpy).toHaveBeenCalledWith(walletToRestore.id, { isHidden: false }); - expect(scope.getState(walletModel.$allWallets)).toEqual( - wallets.map((wallet) => { - return wallet.id === walletToRestore.id ? { ...wallet, isHidden: false } : wallet; - }), - ); + expect(scope.getState(walletModel._test.$allWallets)).toEqual(remainingWallets); }); test('should update $wallets and $hiddenWallets when $allWallets is updated', async () => { @@ -153,7 +70,7 @@ describe('entities/wallet/model/wallet-model', () => { const scope = fork(); - await allSettled(walletModel.$allWallets, { scope, params: wallets }); + await allSettled(walletModel._test.$allWallets, { scope, params: wallets }); expect(scope.getState(walletModel.$hiddenWallets)).toEqual([hiddenWallet]); expect(scope.getState(walletModel.$wallets)).toEqual(visibleWallets); diff --git a/src/renderer/entities/wallet/model/wallet-model.ts b/src/renderer/entities/wallet/model/wallet-model.ts index 8ca0635839..759babaaf9 100644 --- a/src/renderer/entities/wallet/model/wallet-model.ts +++ b/src/renderer/entities/wallet/model/wallet-model.ts @@ -1,6 +1,6 @@ import { combine, createEffect, createEvent, createStore, sample } from 'effector'; import groupBy from 'lodash/groupBy'; -import { combineEvents } from 'patronum'; +import { combineEvents, readonly } from 'patronum'; import { storageService } from '@/shared/api/storage'; import { @@ -10,10 +10,12 @@ import { type ID, type MultisigAccount, type NoID, + type ProxiedAccount, + type ShardAccount, type Wallet, type WcAccount, } from '@/shared/core'; -import { dictionary } from '@/shared/lib/utils'; +import { dictionary, nonNullable } from '@/shared/lib/utils'; import { modelUtils } from '../lib/model-utils'; type DbWallet = Omit; @@ -25,15 +27,21 @@ type CreateParams = { const walletStarted = createEvent(); const watchOnlyCreated = createEvent>(); -const multishardCreated = createEvent>(); +const multishardCreated = createEvent>(); const singleshardCreated = createEvent>(); const multisigCreated = createEvent>(); +const proxiedCreated = createEvent>(); const walletConnectCreated = createEvent>(); const walletRestored = createEvent(); const walletHidden = createEvent(); const walletRemoved = createEvent(); const walletsRemoved = createEvent(); +const selectWallet = createEvent(); +// TODO this is temp solution, accounts should be separated from wallets +const updateAccounts = createEvent<{ walletId: ID; accounts: Account[] }>(); +// TODO this is temp solution, each type of wallet should update own data inside feature +const updateWallet = createEvent<{ walletId: ID; data: NonNullable }>(); const $allWallets = createStore([]); const $wallets = $allWallets.map((wallets) => wallets.filter((x) => !x.isHidden)); @@ -92,12 +100,15 @@ const walletCreatedFx = createEffect(async ({ wallet, accounts }: CreateParams): }); const multishardCreatedFx = createEffect( - async ({ wallet, accounts }: CreateParams): Promise => { + async ({ + wallet, + accounts, + }: CreateParams): Promise => { const dbWallet = await storageService.wallets.create({ ...wallet, isActive: false }); if (!dbWallet) return undefined; - const { base, chains } = modelUtils.groupAccounts(accounts); + const { base, chains, shards } = modelUtils.groupAccounts(accounts); const multishardAccounts = []; @@ -106,14 +117,32 @@ const multishardCreatedFx = createEffect( if (!dbBaseAccount) return undefined; multishardAccounts.push(dbBaseAccount); - if (!chains[index]) continue; - - const accountsPayload = chains[index].map((account) => ({ - ...account, - walletId: dbWallet.id, - baseId: dbBaseAccount.id, - })); - const dbChainAccounts = await storageService.accounts.createAll(accountsPayload); + let accountPayloads: NoID[] = []; + + if (chains[index]) { + accountPayloads = accountPayloads.concat( + chains[index].map((account) => ({ + ...account, + walletId: dbWallet.id, + baseId: dbBaseAccount.id, + })), + ); + } + + if (shards[index]) { + accountPayloads = accountPayloads.concat( + shards[index].map((account) => ({ + ...account, + walletId: dbWallet.id, + })), + ); + } + + if (accountPayloads.length === 0) { + continue; + } + + const dbChainAccounts = await storageService.accounts.createAll(accountPayloads); if (!dbChainAccounts) return undefined; multishardAccounts.push(...dbChainAccounts); @@ -159,6 +188,18 @@ const restoreWalletFx = createEffect(async (wallet: Wallet): Promise => return wallet; }); +const walletSelectedFx = createEffect(async (nextId: ID): Promise => { + const wallets = await storageService.wallets.readAll(); + const inactiveWallets = wallets.filter((wallet) => wallet.isActive).map((wallet) => ({ ...wallet, isActive: false })); + + const [, nextWallet] = await Promise.all([ + storageService.wallets.updateAll(inactiveWallets), + storageService.wallets.update(nextId, { isActive: true }), + ]); + + return nextWallet; +}); + sample({ clock: walletStarted, target: [fetchAllAccountsFx, fetchAllWalletsFx], @@ -174,8 +215,12 @@ sample({ target: $allWallets, }); +const walletCreatedDone = sample({ + clock: [walletCreatedFx.doneData, multishardCreatedFx.doneData], +}).filter({ fn: nonNullable }); + sample({ - clock: [walletConnectCreated, watchOnlyCreated, multisigCreated, singleshardCreated], + clock: [walletConnectCreated, watchOnlyCreated, multisigCreated, singleshardCreated, proxiedCreated], target: walletCreatedFx, }); @@ -185,11 +230,10 @@ sample({ }); sample({ - clock: [walletCreatedFx.doneData, multishardCreatedFx.doneData], + clock: walletCreatedDone, source: $allWallets, - filter: (_, data) => Boolean(data), fn: (wallets, data) => { - return wallets.concat({ ...data!.wallet, accounts: data!.accounts }); + return wallets.concat({ ...data.wallet, accounts: data.accounts }); }, target: $allWallets, }); @@ -273,9 +317,45 @@ sample({ target: $allWallets, }); +sample({ clock: selectWallet, target: walletSelectedFx }); + +sample({ + clock: walletSelectedFx.doneData, + source: $allWallets, + filter: (_, nextId) => Boolean(nextId), + fn: (wallets, nextId) => { + return wallets.map((wallet) => ({ ...wallet, isActive: wallet.id === nextId })); + }, + target: $allWallets, +}); + +sample({ + clock: updateAccounts, + source: $allWallets, + fn: (wallets, { walletId, accounts }) => { + return wallets.map((wallet) => { + if (wallet.id !== walletId) return wallet; + + return { ...wallet, accounts }; + }); + }, + target: $allWallets, +}); + +sample({ + clock: updateWallet, + source: $allWallets, + fn: (wallets, { walletId, data }) => { + return wallets.map((wallet) => { + return wallet.id === walletId ? { ...wallet, ...data } : wallet; + }); + }, + target: $allWallets, +}); + export const walletModel = { $wallets, - $allWallets, + $allWallets: readonly($allWallets), $hiddenWallets, $activeWallet, $isLoadingWallets: fetchAllWalletsFx.pending, @@ -287,7 +367,11 @@ export const walletModel = { singleshardCreated, multisigCreated, walletConnectCreated, - walletCreatedDone: walletCreatedFx.done, + proxiedCreated, + walletCreatedDone, + selectWallet, + updateAccounts, + updateWallet, walletRemoved, walletHidden, walletHiddenSuccess: hideWalletFx.done, @@ -296,4 +380,8 @@ export const walletModel = { walletRestored, walletRestoredSuccess: restoreWalletFx.done, }, + + _test: { + $allWallets, + }, }; diff --git a/src/renderer/entities/walletConnect/model/wallet-connect-model.ts b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts index 14bfc1fdc6..35d0233ddf 100644 --- a/src/renderer/entities/walletConnect/model/wallet-connect-model.ts +++ b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts @@ -7,7 +7,8 @@ import keyBy from 'lodash/keyBy'; import { localStorageService } from '@/shared/api/local-storage'; import { storageService } from '@/shared/api/storage'; import { type Account, type ID, type Wallet, kernelModel } from '@/shared/core'; -import { nonNullable } from '@/shared/lib/utils'; +import { series } from '@/shared/effector'; +import { nonNullable, nullable } from '@/shared/lib/utils'; import { walletModel, walletUtils } from '@/entities/wallet'; import { DEFAULT_APP_METADATA, @@ -23,6 +24,7 @@ import { type InitConnectParams } from '../lib/types'; import { walletConnectUtils } from '../lib/utils'; type SessionTopicParams = { + walletId: ID; accounts: Account[]; topic: string; }; @@ -158,7 +160,7 @@ const logClientIdFx = createEffect(async (client: Client) => { }); const sessionTopicUpdatedFx = createEffect( - async ({ accounts, topic, client }: SessionTopicParams & { client: Client }): Promise => { + async ({ accounts, topic, client, walletId }: SessionTopicParams & { client: Client }) => { const oldSessionTopic = accounts[0]?.signingExtras?.sessionTopic; const oldSession = client.session.get(oldSessionTopic); @@ -175,7 +177,17 @@ const sessionTopicUpdatedFx = createEffect( await disconnectFx({ client, session: oldSession }); } - return updated && updatedAccounts; + if (!updated) { + return { + walletId, + accounts: [], + }; + } + + return { + walletId, + accounts: updatedAccounts, + }; }, ); @@ -239,16 +251,9 @@ sample({ sample({ clock: updateWcAccountsFx.done, - source: walletModel.$allWallets, - filter: (_, { result: accounts }) => Boolean(accounts?.length), - fn: (wallets, { params }) => { - return wallets.map((wallet) => { - if (wallet.id !== params.wallet.id) return wallet; - - return { ...wallet, accounts: params.accounts }; - }); - }, - target: walletModel.$allWallets, + filter: ({ result: accounts }) => nonNullable(accounts) && accounts.length > 0, + fn: ({ params }) => ({ walletId: params.wallet.id, accounts: params.accounts }), + target: walletModel.events.updateAccounts, }); sample({ @@ -373,6 +378,7 @@ sample({ source: { activeWallet: walletModel.$activeWallet, client: $client }, filter: ({ activeWallet, client }) => !!activeWallet && !!client, fn: ({ activeWallet, client }, topic) => ({ + walletId: activeWallet!.id, accounts: activeWallet!.accounts, topic, client: client!, @@ -383,7 +389,7 @@ sample({ sample({ clock: sessionTopicUpdated, source: $client, - filter: (client) => Boolean(client), + filter: nonNullable, fn: (client, params) => ({ client: client!, ...params }), target: sessionTopicUpdatedFx, }); @@ -391,19 +397,19 @@ sample({ sample({ clock: sessionTopicUpdatedFx.doneData, source: walletModel.$allWallets, - filter: (_, accounts) => Boolean(accounts?.length), - fn: (wallets, accounts) => { - const walletId = accounts![0].walletId; - const updatedMap = keyBy(accounts, 'id'); + filter: (_, { accounts }) => accounts.length > 0, + fn: (wallets, { accounts, walletId }) => { + const wallet = wallets.find((w) => w.id === walletId); + if (nullable(wallet)) { + return { walletId, accounts }; + } - return wallets.map((wallet) => { - if (wallet.id !== walletId) return wallet; - const accounts = wallet.accounts.map((account) => updatedMap[account.id] || account); + const updatedMap = keyBy(accounts, 'id'); + const updatedAccounts = wallet.accounts.map((account) => updatedMap[account.id] || account); - return { ...wallet, accounts } as Wallet; - }); + return { walletId, accounts: updatedAccounts }; }, - target: walletModel.$allWallets, + target: walletModel.events.updateAccounts, }); sample({ @@ -432,15 +438,14 @@ sample({ }, filter: ({ client }) => Boolean(client), fn: ({ wallets, client }) => { - return wallets.map((wallet) => { - if (walletUtils.isWalletConnectGroup(wallet)) { - wallet.isConnected = walletConnectUtils.isConnectedByAccounts(client!, wallet); - } - - return wallet; - }, []); + return wallets.filter(walletUtils.isWalletConnectGroup).map((wallet) => { + return { + walletId: wallet.id, + data: { isConnected: walletConnectUtils.isConnectedByAccounts(client!, wallet) }, + }; + }); }, - target: walletModel.$allWallets, + target: series(walletModel.events.updateWallet), }); export const walletConnectModel = { diff --git a/src/renderer/features/assets/AssetsChainView/ui/AssetCard/AssetCard.test.tsx b/src/renderer/features/assets/AssetsChainView/ui/AssetCard/AssetCard.test.tsx index 51ffe5916b..fbfb0b9507 100644 --- a/src/renderer/features/assets/AssetsChainView/ui/AssetCard/AssetCard.test.tsx +++ b/src/renderer/features/assets/AssetsChainView/ui/AssetCard/AssetCard.test.tsx @@ -63,7 +63,7 @@ describe('pages/Assets/AssetCard', () => { test('should navigate to receive asset modal', async () => { const scope = fork({ - values: new Map().set(walletModel.$allWallets, [ + values: new Map().set(walletModel._test.$allWallets, [ { walletId: 1, type: WalletType.POLKADOT_VAULT, diff --git a/src/renderer/features/balances/subscription/model/__tests__/balance-sub-model.test.ts b/src/renderer/features/balances/subscription/model/__tests__/balance-sub-model.test.ts index 55c17a2fab..22a6773c9b 100644 --- a/src/renderer/features/balances/subscription/model/__tests__/balance-sub-model.test.ts +++ b/src/renderer/features/balances/subscription/model/__tests__/balance-sub-model.test.ts @@ -18,7 +18,7 @@ describe('features/balances/subscription/model/balance-sub-model', () => { const actions = Promise.all([ allSettled(networkModel.$chains, { scope, params: chains }), - allSettled(walletModel.$allWallets, { scope, params: wallets }), + allSettled(walletModel._test.$allWallets, { scope, params: wallets }), ]); await jest.runAllTimersAsync(); @@ -80,7 +80,7 @@ describe('features/balances/subscription/model/balance-sub-model', () => { const scope = fork(); await setupInitialState(scope); - const action = allSettled(walletModel.$allWallets, { scope, params: newWallets }); + const action = allSettled(walletModel._test.$allWallets, { scope, params: newWallets }); await jest.runAllTimersAsync(); await action; @@ -160,7 +160,7 @@ describe('features/balances/subscription/model/balance-sub-model', () => { '0x02': undefined, }); - const action = allSettled(walletModel.$allWallets, { scope, params: newWallets }); + const action = allSettled(walletModel._test.$allWallets, { scope, params: newWallets }); await jest.runAllTimersAsync(); await action; diff --git a/src/renderer/features/operations/OperationSign/model/__tests__/sign-model.test.ts b/src/renderer/features/operations/OperationSign/model/__tests__/sign-model.test.ts index dd1df628dc..72a1d420fc 100644 --- a/src/renderer/features/operations/OperationSign/model/__tests__/sign-model.test.ts +++ b/src/renderer/features/operations/OperationSign/model/__tests__/sign-model.test.ts @@ -36,7 +36,7 @@ describe('widgets/AddPureProxyModal/model/sign-model', () => { const scope = fork({ values: new Map() .set(networkModel.$apis, { '0x00': testApi }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); const payload = { diff --git a/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts b/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts index f824480733..b7e501723d 100644 --- a/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts +++ b/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts @@ -4,7 +4,7 @@ import { combine, createEffect, createEvent, createStore, sample } from 'effecto import { combineEvents } from 'patronum'; import { AccountType, type ChainId, type HexString, type WcAccount } from '@/shared/core'; -import { toAccountId } from '@/shared/lib/utils'; +import { nonNullable, toAccountId } from '@/shared/lib/utils'; import { networkModel } from '@/entities/network'; import { walletModel, walletUtils } from '@/entities/wallet'; import { type InitReconnectParams, walletConnectModel } from '@/entities/walletConnect'; @@ -112,13 +112,15 @@ sample({ step: $reconnectStep, session: walletConnectModel.$session, }, - filter: ({ step, session }) => { + filter: ({ step, session, signer }) => { return ( (operationSignUtils.isReconnectingStep(step) || operationSignUtils.isConnectedStep(step)) && - operationSignUtils.isTopicExist(session) + operationSignUtils.isTopicExist(session) && + nonNullable(signer) ); }, fn: ({ wallets, signer, session }) => ({ + walletId: signer!.walletId, accounts: walletUtils.getAccountsBy(wallets, (a) => a.walletId === signer?.walletId), topic: session!.topic, }), diff --git a/src/renderer/features/operations/OperationSubmit/model/__tests__/submit-model.test.ts b/src/renderer/features/operations/OperationSubmit/model/__tests__/submit-model.test.ts index 41f5819f0c..b9a2e11ffd 100644 --- a/src/renderer/features/operations/OperationSubmit/model/__tests__/submit-model.test.ts +++ b/src/renderer/features/operations/OperationSubmit/model/__tests__/submit-model.test.ts @@ -28,7 +28,9 @@ const initiatorWallet = { describe('widgets/AddPureProxyModal/model/submit-model', () => { test('should submit extrinsic', async () => { const scope = fork({ - values: new Map().set(networkModel.$apis, { '0x00': testApi }).set(walletModel.$allWallets, [initiatorWallet]), + values: new Map() + .set(networkModel.$apis, { '0x00': testApi }) + .set(walletModel._test.$allWallets, [initiatorWallet]), }); const store = { diff --git a/src/renderer/features/proxies/model/__tests__/proxies-model.test.ts b/src/renderer/features/proxies/model/__tests__/proxies-model.test.ts index f76a982dd3..d716661361 100644 --- a/src/renderer/features/proxies/model/__tests__/proxies-model.test.ts +++ b/src/renderer/features/proxies/model/__tests__/proxies-model.test.ts @@ -88,7 +88,7 @@ describe('features/proxies/model/proxies-model', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [ + .set(walletModel._test.$allWallets, [ { id: 1, accounts: [{ walletId: 1, accountId: '0x01', type: AccountType.CHAIN, chainId: '0x01' }] }, ]) .set(networkModel.$chains, { diff --git a/src/renderer/features/proxies/model/proxies-model.ts b/src/renderer/features/proxies/model/proxies-model.ts index 641ad6e3f9..8a352431d9 100644 --- a/src/renderer/features/proxies/model/proxies-model.ts +++ b/src/renderer/features/proxies/model/proxies-model.ts @@ -14,6 +14,7 @@ import { type NoID, type PartialProxiedAccount, type ProxiedAccount, + type ProxiedWallet, type ProxyAccount, type ProxyDeposits, type ProxyGroup, @@ -30,6 +31,7 @@ import { SigningType, WalletType, } from '@/shared/core'; +import { series } from '@/shared/effector'; import { dictionary } from '@/shared/lib/utils'; import { balanceModel } from '@/entities/balance'; import { networkModel, networkUtils } from '@/entities/network'; @@ -153,7 +155,7 @@ type ProxiedWalletsParams = { chains: Record; }; type ProxiedWalletsResult = { - wallets: Wallet[]; + wallets: ProxiedWallet[]; accounts: ProxiedAccount[]; }; const createProxiedWalletsFx = createEffect( @@ -201,12 +203,12 @@ const createProxiedWalletsFx = createEffect( if (!proxiedCreatedResult) return acc; acc.accounts.push(...proxiedCreatedResult.accounts); - acc.wallets.push(proxiedCreatedResult.wallet as Wallet); + acc.wallets.push(proxiedCreatedResult.wallet as ProxiedWallet); return acc; }, { - wallets: [] as Wallet[], + wallets: [] as ProxiedWallet[], accounts: [] as ProxiedAccount[], }, ); @@ -310,21 +312,22 @@ sample({ sample({ clock: createProxiedWalletsFx.doneData, - source: walletModel.$allWallets, - filter: (_, data) => Boolean(data && data.wallets.length && data.accounts.length), - fn: (wallets, data) => { + filter: ({ wallets, accounts }) => wallets.length > 0 && accounts.length > 0, + fn: (data) => { const accountsMap = dictionary(data.accounts, 'walletId'); - const newWallets = data.wallets.map((wallet) => ({ ...wallet, accounts: [accountsMap[wallet.id]] }) as Wallet); + const newWallets = data.wallets.map((wallet) => { + const account = accountsMap[wallet.id]; - return wallets.concat(newWallets); - }, - target: walletsAdded, -}); + return { + wallet, + accounts: account ? [account] : [], + }; + }); -sample({ - source: walletsAdded, - target: walletModel.$allWallets, + return newWallets; + }, + target: series(walletModel.events.proxiedCreated), }); sample({ diff --git a/src/renderer/features/wallets/DerivationsAddressModal/lib/utils.ts b/src/renderer/features/wallets/DerivationsAddressModal/lib/utils.ts index 2880bde3cf..64a5249084 100644 --- a/src/renderer/features/wallets/DerivationsAddressModal/lib/utils.ts +++ b/src/renderer/features/wallets/DerivationsAddressModal/lib/utils.ts @@ -1,4 +1,4 @@ -import { type ChainAccount, type DraftAccount, type ShardAccount } from '@/shared/core'; +import { type AccountId, type ChainAccount, type DraftAccount, type ShardAccount } from '@/shared/core'; import { toAccountId } from '@/shared/lib/utils'; import { type DdAddressInfoDecoded, @@ -21,10 +21,10 @@ function createDerivationsRequest( })); } -function createDerivedAccounts( +function createDerivedAccounts( derivedKeys: Record, - accounts: DraftAccount[], -): DraftAccount[] { + accounts: DraftAccount[], +): (DraftAccount & { accountId: AccountId })[] { return accounts.map((account) => { const derivationPath = `${account.derivationPath}${cryptoTypeToMultisignerIndex(account.cryptoType)}`; diff --git a/src/renderer/features/wallets/DerivationsAddressModal/ui/DerivationsAddressModal.tsx b/src/renderer/features/wallets/DerivationsAddressModal/ui/DerivationsAddressModal.tsx index c1d06d588c..fc66b0e226 100644 --- a/src/renderer/features/wallets/DerivationsAddressModal/ui/DerivationsAddressModal.tsx +++ b/src/renderer/features/wallets/DerivationsAddressModal/ui/DerivationsAddressModal.tsx @@ -19,7 +19,7 @@ type Props = { isOpen: boolean; rootAccountId: AccountId; keys: DraftAccount[]; - onComplete: (accounts: DraftAccount[]) => void; + onComplete: (accounts: Omit[]) => void; onClose: () => void; }; export const DerivationsAddressModal = ({ isOpen, rootAccountId, keys, onClose, onComplete }: Props) => { diff --git a/src/renderer/features/wallets/ForgetWallet/model/__tests__/forget-wallet-model.test.ts b/src/renderer/features/wallets/ForgetWallet/model/__tests__/forget-wallet-model.test.ts index 7dd65df55f..28d59cf463 100644 --- a/src/renderer/features/wallets/ForgetWallet/model/__tests__/forget-wallet-model.test.ts +++ b/src/renderer/features/wallets/ForgetWallet/model/__tests__/forget-wallet-model.test.ts @@ -97,7 +97,7 @@ describe('features/wallets/ForgetModel', () => { storageService.accounts.deleteAll = jest.fn(); const scope = fork({ - values: new Map().set(walletModel.$allWallets, [wallet]), + values: new Map().set(walletModel._test.$allWallets, [wallet]), }); await allSettled(forgetWalletModel.events.callbacksChanged, { scope, params: { onDeleteFinished: spyCallback } }); @@ -114,7 +114,7 @@ describe('features/wallets/ForgetModel', () => { storageService.accounts.deleteAll = spyDeleteAccounts; const scope = fork({ - values: new Map().set(walletModel.$allWallets, [wallet]), + values: new Map().set(walletModel._test.$allWallets, [wallet]), }); await allSettled(forgetWalletModel.events.callbacksChanged, { scope, params: { onDeleteFinished: () => {} } }); @@ -130,7 +130,7 @@ describe('features/wallets/ForgetModel', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [wallet, proxiedWallet]) + .set(walletModel._test.$allWallets, [wallet, proxiedWallet]) .set(proxyModel.$proxies, { '0x01': [ { @@ -158,6 +158,6 @@ describe('features/wallets/ForgetModel', () => { expect(scope.getState(proxyModel.$proxyGroups)).toEqual([]); expect(scope.getState(proxyModel.$proxies)).toEqual({}); - expect(scope.getState(walletModel.$allWallets)).toEqual([]); + expect(scope.getState(walletModel._test.$allWallets)).toEqual([]); }); }); diff --git a/src/renderer/features/wallets/RenameWallet/model/__tests__/rename-wallet-model.test.ts b/src/renderer/features/wallets/RenameWallet/model/__tests__/rename-wallet-model.test.ts index 0ee92902c9..ec1dc27db1 100644 --- a/src/renderer/features/wallets/RenameWallet/model/__tests__/rename-wallet-model.test.ts +++ b/src/renderer/features/wallets/RenameWallet/model/__tests__/rename-wallet-model.test.ts @@ -23,7 +23,7 @@ describe('entities/wallet/model/wallet-model', () => { test('should validate non-unique wallet name', async () => { const wallets = [walletMock.wallet1, walletMock.wallet2]; const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); await allSettled(renameWalletModel.events.formInitiated, { scope, params: walletMock.wallet1 }); @@ -46,7 +46,7 @@ describe('entities/wallet/model/wallet-model', () => { jest.spyOn(storageService.accounts, 'createAll').mockResolvedValue(updatedWallet.accounts); const scope = fork({ - values: new Map().set(walletModel.$allWallets, [walletMock.wallet1]), + values: new Map().set(walletModel._test.$allWallets, [walletMock.wallet1]), }); await allSettled(renameWalletModel.events.formInitiated, { scope, params: walletMock.wallet1 }); diff --git a/src/renderer/features/wallets/RenameWallet/model/rename-wallet-model.ts b/src/renderer/features/wallets/RenameWallet/model/rename-wallet-model.ts index beb928da5c..2c73fcc39f 100644 --- a/src/renderer/features/wallets/RenameWallet/model/rename-wallet-model.ts +++ b/src/renderer/features/wallets/RenameWallet/model/rename-wallet-model.ts @@ -4,7 +4,7 @@ import { not } from 'patronum'; import { storageService } from '@/shared/api/storage'; import { type Wallet } from '@/shared/core'; -import { nonNullable, splice } from '@/shared/lib/utils'; +import { nonNullable } from '@/shared/lib/utils'; import { walletModel, walletUtils } from '@/entities/wallet'; import { walletConnectModel } from '@/entities/walletConnect'; @@ -43,10 +43,10 @@ const $walletForm = createForm({ validateOn: ['submit'], }); -const renameWalletFx = createEffect(async ({ id, ...rest }: Wallet): Promise => { +const renameWalletFx = createEffect(async ({ id, accounts, ...rest }: Wallet): Promise => { await storageService.wallets.update(id, rest); - return { id, ...rest }; + return { id, accounts, ...rest }; }); sample({ @@ -104,13 +104,13 @@ sample({ sample({ clock: renameWalletFx.doneData, - source: walletModel.$allWallets, - fn: (wallets, updatedWallet) => { - const updatedWalletIndex = wallets.findIndex((w) => w.id === updatedWallet.id); - - return splice(wallets, updatedWallet, updatedWalletIndex); + fn: (updatedWallet) => { + return { + walletId: updatedWallet.id, + data: updatedWallet, + }; }, - target: walletModel.$allWallets, + target: walletModel.events.updateWallet, }); sample({ diff --git a/src/renderer/features/wallets/ShardSelectorModal/model/__tests__/shards-model.test.ts b/src/renderer/features/wallets/ShardSelectorModal/model/__tests__/shards-model.test.ts index cfeb6f6031..e39d177cf6 100644 --- a/src/renderer/features/wallets/ShardSelectorModal/model/__tests__/shards-model.test.ts +++ b/src/renderer/features/wallets/ShardSelectorModal/model/__tests__/shards-model.test.ts @@ -12,7 +12,7 @@ describe('features/wallet/model/shards-model', () => { const { vaultWallet, vaultAccounts, chainsMap } = shardsMock; const scope = fork({ - values: new Map().set(walletModel.$allWallets, [vaultWallet]).set(networkModel.$chains, chainsMap), + values: new Map().set(walletModel._test.$allWallets, [vaultWallet]).set(networkModel.$chains, chainsMap), }); await allSettled(shardsModel.events.structureRequested, { scope, params: true }); @@ -31,7 +31,7 @@ describe('features/wallet/model/shards-model', () => { const { multishardWallet, multishardAccounts, chainsMap } = shardsMock; const scope = fork({ - values: new Map().set(walletModel.$allWallets, [multishardWallet]).set(networkModel.$chains, chainsMap), + values: new Map().set(walletModel._test.$allWallets, [multishardWallet]).set(networkModel.$chains, chainsMap), }); await allSettled(shardsModel.events.structureRequested, { scope, params: true }); diff --git a/src/renderer/features/wallets/WalletSelect/model/__tests__/wallet-select-model.test.ts b/src/renderer/features/wallets/WalletSelect/model/__tests__/wallet-select-model.test.ts index 33e2bcd1f2..0ae652c187 100644 --- a/src/renderer/features/wallets/WalletSelect/model/__tests__/wallet-select-model.test.ts +++ b/src/renderer/features/wallets/WalletSelect/model/__tests__/wallet-select-model.test.ts @@ -49,7 +49,7 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { }; const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); expect(scope.getState(walletSelectModel.$filteredWalletGroups)).toEqual({ @@ -66,7 +66,7 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { test('should set $walletForDetails on walletIdSet', async () => { const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); expect(scope.getState(walletSelectModel.$walletForDetails)).toEqual(undefined); @@ -80,7 +80,7 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { jest.spyOn(storageService.wallets, 'update').mockResolvedValue(2); const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); expect(scope.getState(walletModel.$activeWallet)).toEqual(wallets[0]); @@ -98,7 +98,7 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { const scope = fork(); expect(scope.getState(walletModel.$activeWallet)).toEqual(undefined); - await allSettled(walletModel.$allWallets, { scope, params: inactiveWallets }); + await allSettled(walletModel._test.$allWallets, { scope, params: inactiveWallets }); expect(scope.getState(walletModel.$activeWallet)).toEqual({ ...inactiveWallets[0], isActive: true }); }); @@ -110,7 +110,7 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { const scope = fork(); expect(scope.getState(walletModel.$activeWallet)).toEqual(undefined); - await allSettled(walletModel.$allWallets, { scope, params: wallets }); + await allSettled(walletModel._test.$allWallets, { scope, params: wallets }); expect(spyRead).not.toHaveBeenCalled(); expect(spyUpdateAll).not.toHaveBeenCalled(); @@ -118,20 +118,6 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { expect(scope.getState(walletModel.$activeWallet)).toEqual(wallets[0]); }); - test('should set $activeWallet when $wallets receives new wallet', async () => { - jest.spyOn(storageService.wallets, 'readAll').mockResolvedValue(wallets); - jest.spyOn(storageService.wallets, 'updateAll').mockResolvedValue([1]); - jest.spyOn(storageService.wallets, 'update').mockResolvedValue(newWallet.id); - - const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), - }); - - expect(scope.getState(walletModel.$activeWallet)).toEqual(wallets[0]); - await allSettled(walletModel.$allWallets, { scope, params: wallets.concat(newWallet) }); - expect(scope.getState(walletModel.$activeWallet)).toEqual({ ...newWallet, isActive: true }); - }); - test('should set $activeWallet on $activeWallet removed, respect wallet groups', async () => { const extendedWallets = wallets.concat(newWallet); @@ -140,11 +126,11 @@ describe('features/wallets/WalletSelect/model/wallet-select-model', () => { jest.spyOn(storageService.wallets, 'update').mockResolvedValue(newWallet.id); const scope = fork({ - values: new Map().set(walletModel.$allWallets, extendedWallets), + values: new Map().set(walletModel._test.$allWallets, extendedWallets), }); expect(scope.getState(walletModel.$activeWallet)).toEqual(extendedWallets[0]); - await allSettled(walletModel.$allWallets, { scope, params: extendedWallets.slice(1) }); + await allSettled(walletModel._test.$allWallets, { scope, params: extendedWallets.slice(1) }); expect(scope.getState(walletModel.$activeWallet)).toEqual({ ...extendedWallets[2], isActive: true }); }); }); diff --git a/src/renderer/features/wallets/WalletSelect/model/wallet-select-model.ts b/src/renderer/features/wallets/WalletSelect/model/wallet-select-model.ts index 5f21479a86..613ae46c69 100644 --- a/src/renderer/features/wallets/WalletSelect/model/wallet-select-model.ts +++ b/src/renderer/features/wallets/WalletSelect/model/wallet-select-model.ts @@ -1,8 +1,7 @@ import { default as BigNumber } from 'bignumber.js'; -import { attach, combine, createApi, createEffect, createEvent, createStore, sample } from 'effector'; +import { attach, combine, createApi, createEvent, createStore, sample } from 'effector'; import { once, previous } from 'patronum'; -import { storageService } from '@/shared/api/storage'; import { type Account, type ID, type Wallet } from '@/shared/core'; import { dictionary, getRoundedValue, totalAmount } from '@/shared/lib/utils'; import { balanceModel } from '@/entities/balance'; @@ -17,7 +16,6 @@ export type Callbacks = { const walletIdSet = createEvent(); const queryChanged = createEvent(); -const walletSelected = createEvent(); const $callbacks = createStore(null); const callbacksApi = createApi($callbacks, { @@ -39,20 +37,6 @@ const $isWalletsRemoved = combine( }, ); -const $isWalletsAdded = combine( - { - prevWallets: previous(walletModel.$wallets), - wallets: walletModel.$wallets, - }, - ({ prevWallets, wallets }) => { - if (!prevWallets) return false; - - if (prevWallets.length > 0) return wallets.length > prevWallets.length; - - return wallets.length === 1 && !wallets[0].isActive; - }, -); - const $walletForDetails = combine( { walletId: $walletId, @@ -112,38 +96,10 @@ const $walletBalance = combine( }, ); -const walletSelectedFx = createEffect(async (nextId: ID): Promise => { - const wallets = await storageService.wallets.readAll(); - const inactiveWallets = wallets.filter((wallet) => wallet.isActive).map((wallet) => ({ ...wallet, isActive: false })); - - const [, nextWallet] = await Promise.all([ - storageService.wallets.updateAll(inactiveWallets), - storageService.wallets.update(nextId, { isActive: true }), - ]); - - return nextWallet; -}); - sample({ clock: queryChanged, target: $filterQuery }); sample({ clock: walletIdSet, target: $walletId }); -sample({ - clock: once(walletModel.$wallets), - filter: (wallets) => wallets.every((wallet) => !wallet.isActive), - fn: (wallets) => { - const match = wallets.find((wallet) => wallet.isActive); - if (match) return match.id; - - const groups = walletSelectUtils.getWalletByGroups(wallets); - - return Object.values(groups).flat()[0].id; - }, - target: walletSelectedFx, -}); - -sample({ clock: walletSelected, target: walletSelectedFx }); - sample({ clock: $isWalletsRemoved, source: walletModel.$wallets, @@ -157,24 +113,24 @@ sample({ return Object.values(groups).flat()[0].id; }, - target: walletSelectedFx, + target: walletModel.events.selectWallet, }); sample({ - clock: $isWalletsAdded, + clock: walletModel.events.walletCreatedDone, source: walletModel.$wallets, - filter: (wallets, isWalletsAdded) => { - const wallet = wallets.at(-1); - if (!wallet) return false; + filter: (wallets, { wallet }) => { + const foundWallet = wallets.find((w) => w.id === wallet.id); + if (!foundWallet) return false; - return isWalletsAdded && !walletUtils.isProxied(wallet) && !walletUtils.isMultisig(wallet); + return !walletUtils.isProxied(foundWallet) && !walletUtils.isMultisig(foundWallet); }, - fn: (wallets) => wallets[wallets.length - 1].id, - target: walletSelectedFx, + fn: (_, { wallet }) => wallet.id, + target: walletModel.events.selectWallet, }); sample({ - clock: walletSelected, + clock: walletModel.events.selectWallet, source: walletModel.$activeWallet, filter: (wallet, walletId) => walletId !== wallet?.id, target: attach({ @@ -184,13 +140,14 @@ sample({ }); sample({ - clock: walletSelectedFx.doneData, - source: walletModel.$allWallets, - filter: (_, nextId) => Boolean(nextId), - fn: (wallets, nextId) => { - return wallets.map((wallet) => ({ ...wallet, isActive: wallet.id === nextId })); + clock: once(walletModel.$wallets), + filter: (wallets) => wallets.length > 0 && wallets.every((wallet) => !wallet.isActive), + fn: (wallets) => { + const groups = walletSelectUtils.getWalletByGroups(wallets); + + return Object.values(groups).flat()[0].id; }, - target: walletModel.$allWallets, + target: walletModel.events.selectWallet, }); export const walletSelectModel = { @@ -199,7 +156,7 @@ export const walletSelectModel = { $walletForDetails, events: { - walletSelected, + walletSelected: walletModel.events.selectWallet, walletIdSet, queryChanged, clearData: $filterQuery.reinit, diff --git a/src/renderer/pages/Onboarding/Vault/ManageVault/ManageVault.tsx b/src/renderer/pages/Onboarding/Vault/ManageVault/ManageVault.tsx index afe0950237..eaa5b27edb 100644 --- a/src/renderer/pages/Onboarding/Vault/ManageVault/ManageVault.tsx +++ b/src/renderer/pages/Onboarding/Vault/ManageVault/ManageVault.tsx @@ -115,7 +115,7 @@ export const ManageVault = ({ seedInfo, onBack, onClose, onComplete }: Props) => toggleIsAddressModalOpen(); }; - const handleCreateVault = (accounts: DraftAccount[]) => { + const handleCreateVault = (accounts: Omit[]) => { manageVaultModel.events.vaultCreated({ wallet: { name: walletName.trim(), diff --git a/src/renderer/pages/Onboarding/Vault/ManageVault/model/manage-vault-model.ts b/src/renderer/pages/Onboarding/Vault/ManageVault/model/manage-vault-model.ts index 3fba2e26b2..a1a4ddc9ff 100644 --- a/src/renderer/pages/Onboarding/Vault/ManageVault/model/manage-vault-model.ts +++ b/src/renderer/pages/Onboarding/Vault/ManageVault/model/manage-vault-model.ts @@ -1,7 +1,6 @@ -import { attach, combine, createApi, createEffect, createEvent, createStore, sample } from 'effector'; +import { attach, combine, createApi, createEvent, createStore, sample } from 'effector'; import { createForm } from 'effector-forms'; -import { storageService } from '@/shared/api/storage'; import { type BaseAccount, type ChainAccount, @@ -14,8 +13,7 @@ import { AccountType, ChainType, CryptoType, KeyType } from '@/shared/core'; import { dictionary } from '@/shared/lib/utils'; import { networkModel, networkUtils } from '@/entities/network'; import { type SeedInfo } from '@/entities/transaction'; -import { KEY_NAMES, accountUtils, walletModel } from '@/entities/wallet'; -import { walletSelectModel } from '@/features/wallets'; +import { KEY_NAMES, accountUtils, walletModel, walletUtils } from '@/entities/wallet'; const WALLET_NAME_MAX_LENGTH = 256; @@ -26,7 +24,7 @@ export type Callbacks = { type VaultCreateParams = { root: Omit, 'walletId'>; wallet: Omit, 'isActive' | 'accounts'>; - accounts: DraftAccount[]; + accounts: Omit, 'walletId'>[]; }; const formInitiated = createEvent(); @@ -71,30 +69,6 @@ const $walletForm = createForm({ validateOn: ['submit'], }); -const createVaultFx = createEffect( - async ({ wallet, accounts, root }: VaultCreateParams): Promise => { - const dbWallet = await storageService.wallets.create({ ...wallet, isActive: false }); - - if (!dbWallet) return undefined; - - const dbRootAccount = await storageService.accounts.create({ ...root, walletId: dbWallet.id }); - - if (!dbRootAccount) return undefined; - - const accountsToCreate = accounts.map((account) => ({ - ...account, - ...(accountUtils.isChainAccount(account) && { baseId: dbRootAccount.id }), - walletId: dbWallet.id, - })) as (ChainAccount | ShardAccount)[]; - - const dbAccounts = await storageService.accounts.createAll(accountsToCreate); - - if (!dbAccounts || dbAccounts.length === 0) return undefined; - - return { ...dbWallet, accounts: [dbRootAccount, ...dbAccounts] }; - }, -); - sample({ clock: formInitiated, fn: (seedInfo: SeedInfo[]) => ({ name: seedInfo[0].name.trim() }), @@ -107,7 +81,7 @@ sample({ fn: (chains) => { const defaultChains = networkUtils.getMainRelaychains(Object.values(chains)); - return defaultChains.reduce[]>((acc, chain) => { + return defaultChains.reduce[]>((acc, chain) => { if (!chain.specName) return acc; acc.push({ @@ -148,26 +122,20 @@ sample({ sample({ clock: derivationsImported, target: $keys }); -sample({ clock: vaultCreated, target: createVaultFx }); - -// TODO: should use factory sample({ - clock: createVaultFx.doneData, - source: walletModel.$allWallets, - filter: (_, data) => Boolean(data), - fn: (wallets, data) => wallets.concat(data!), - target: walletModel.$allWallets, -}); - -sample({ - clock: createVaultFx.doneData, - filter: (data: Wallet | undefined): data is Wallet => Boolean(data), - fn: (data) => data.id, - target: walletSelectModel.events.walletSelected, + clock: vaultCreated, + fn: ({ wallet, root, accounts }) => { + return { + wallet, + accounts: [root, ...accounts], + }; + }, + target: walletModel.events.multishardCreated, }); sample({ - clock: createVaultFx.doneData, + clock: walletModel.events.walletCreatedDone, + filter: ({ wallet }) => walletUtils.isPolkadotVault(wallet as Wallet), target: attach({ source: $callbacks, effect: (state) => state?.onSubmit(), diff --git a/src/renderer/processes/multisigs/model/__tests__/multisigs-model.test.ts b/src/renderer/processes/multisigs/model/__tests__/multisigs-model.test.ts index 0dbd650d3d..252e5e5d00 100644 --- a/src/renderer/processes/multisigs/model/__tests__/multisigs-model.test.ts +++ b/src/renderer/processes/multisigs/model/__tests__/multisigs-model.test.ts @@ -69,7 +69,7 @@ describe('features/multisigs/model/multisigs-model', () => { }; const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [ + .set(walletModel._test.$allWallets, [ { id: 1111, accounts: [{ walletId: 1111, accountId: '0x11', type: AccountType.CHAIN, chainId: '0x01' }] }, ]) .set(networkModel.$chains, { @@ -103,7 +103,7 @@ describe('features/multisigs/model/multisigs-model', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [ + .set(walletModel._test.$allWallets, [ { id: 1111, accounts: [{ walletId: 1111, accountId: '0x00', type: AccountType.CHAIN, chainId: '0x01' }] }, ]) .set(networkModel.$chains, { diff --git a/src/renderer/shared/effector/helpers/series.ts b/src/renderer/shared/effector/helpers/series.ts new file mode 100644 index 0000000000..f2358fa7dd --- /dev/null +++ b/src/renderer/shared/effector/helpers/series.ts @@ -0,0 +1,38 @@ +import { type Effect, type Event, type EventCallable, createEvent, createStore, sample } from 'effector'; + +import { nonNullable } from '@/shared/lib/utils'; + +/** + * Triggers target unit on each element of the input list. + * + * ```ts + * const $store = createStore([]); + * const event = createEvent(); + * + * sample({ + * clock: $store, + * target: series(event), + * }); + * + * $store.set([0, 1, 3]); + * // event will be called 3 times, direct equivalent of + * // event(0); event(1); event(2) + * ``` + */ +export const series = (target: EventCallable | Effect) => { + const pop = createEvent(); + const push = createEvent | ArrayLike>(); + + const $queue = createStore([]) + .on(push, (state, payload) => state.concat(Array.from(payload))) + .on(pop, ([, ...rest]) => rest); + const $head = $queue.map((queue) => queue.at(0) ?? null); + const nextHeadRetrieved = $head.updates.filter({ fn: nonNullable }) as Event; + + sample({ + clock: nextHeadRetrieved, + target: [target, pop], + }); + + return push; +}; diff --git a/src/renderer/shared/effector/index.ts b/src/renderer/shared/effector/index.ts index 5d3a495705..9a39b47a4f 100644 --- a/src/renderer/shared/effector/index.ts +++ b/src/renderer/shared/effector/index.ts @@ -2,3 +2,5 @@ export { createDataSource } from './createDataSource'; export { createFeature } from './createFeature'; export { createDataSubscription, createPagesHandler } from './createDataSubscription'; export { attachToFeatureInput } from './attachToFeatureInput'; + +export { series } from './helpers/series'; diff --git a/src/renderer/widgets/AddProxyModal/model/__tests__/add-proxy-model.test.ts b/src/renderer/widgets/AddProxyModal/model/__tests__/add-proxy-model.test.ts index cc521d34f8..a5bff4fbfa 100644 --- a/src/renderer/widgets/AddProxyModal/model/__tests__/add-proxy-model.test.ts +++ b/src/renderer/widgets/AddProxyModal/model/__tests__/add-proxy-model.test.ts @@ -37,7 +37,7 @@ describe('widgets/AddProxyModal/model/add-proxy-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(addProxyModel.events.flowStarted, { scope }); diff --git a/src/renderer/widgets/AddProxyModal/model/__tests__/form-model.test.ts b/src/renderer/widgets/AddProxyModal/model/__tests__/form-model.test.ts index 2beb81090b..ed8d471b47 100644 --- a/src/renderer/widgets/AddProxyModal/model/__tests__/form-model.test.ts +++ b/src/renderer/widgets/AddProxyModal/model/__tests__/form-model.test.ts @@ -23,7 +23,7 @@ describe('widgets/AddPureProxyModal/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(formModel.events.formInitiated, { scope }); diff --git a/src/renderer/widgets/AddPureProxiedModal/model/__tests__/add-pure-proxied-model.test.ts b/src/renderer/widgets/AddPureProxiedModal/model/__tests__/add-pure-proxied-model.test.ts index 04136f3d1e..30a694f3b0 100644 --- a/src/renderer/widgets/AddPureProxiedModal/model/__tests__/add-pure-proxied-model.test.ts +++ b/src/renderer/widgets/AddPureProxiedModal/model/__tests__/add-pure-proxied-model.test.ts @@ -37,7 +37,7 @@ describe('widgets/AddPureProxyModal/model/add-pure-proxied-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(addPureProxiedModel.events.flowStarted, { scope }); diff --git a/src/renderer/widgets/AddPureProxiedModal/model/__tests__/form-model.test.ts b/src/renderer/widgets/AddPureProxiedModal/model/__tests__/form-model.test.ts index 4291057d6a..072944fe4b 100644 --- a/src/renderer/widgets/AddPureProxiedModal/model/__tests__/form-model.test.ts +++ b/src/renderer/widgets/AddPureProxiedModal/model/__tests__/form-model.test.ts @@ -23,7 +23,7 @@ describe('widgets/AddPureProxyModal/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(formModel.events.formInitiated, { scope }); diff --git a/src/renderer/widgets/CreateWallet/model/__tests__/confirm-model.test.ts b/src/renderer/widgets/CreateWallet/model/__tests__/confirm-model.test.ts index cf6a499e14..86b7e0625c 100644 --- a/src/renderer/widgets/CreateWallet/model/__tests__/confirm-model.test.ts +++ b/src/renderer/widgets/CreateWallet/model/__tests__/confirm-model.test.ts @@ -16,7 +16,7 @@ describe('widgets/CreateWallet/model/confirm-model', () => { const scope = fork({ values: new Map() .set(networkModel.$apis, { '0x00': testApi }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); const store = { diff --git a/src/renderer/widgets/CreateWallet/model/__tests__/flow-model.test.ts b/src/renderer/widgets/CreateWallet/model/__tests__/flow-model.test.ts index 2a4830abab..94262e8722 100644 --- a/src/renderer/widgets/CreateWallet/model/__tests__/flow-model.test.ts +++ b/src/renderer/widgets/CreateWallet/model/__tests__/flow-model.test.ts @@ -41,7 +41,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(signatoryModel.events.changeSignatory, { diff --git a/src/renderer/widgets/CreateWallet/model/__tests__/form-model.test.ts b/src/renderer/widgets/CreateWallet/model/__tests__/form-model.test.ts index 6000142915..5187e65b21 100644 --- a/src/renderer/widgets/CreateWallet/model/__tests__/form-model.test.ts +++ b/src/renderer/widgets/CreateWallet/model/__tests__/form-model.test.ts @@ -33,7 +33,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(formModel.$createMultisigForm.fields.name.onChange, { scope, params: '' }); @@ -48,7 +48,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(formModel.$createMultisigForm.fields.threshold.onChange, { scope, params: 1 }); @@ -63,7 +63,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet, multisigWallet]) + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet, multisigWallet]) .set(signatoryModel.$signatories, []), }); @@ -88,7 +88,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet, wrongChainWallet]), + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet, wrongChainWallet]), }); await allSettled(formModel.$createMultisigForm.fields.chain.onChange, { scope, params: testChain }); @@ -105,7 +105,7 @@ describe('widgets/CreateWallet/model/form-model', () => { .set(networkModel.$apis, { '0x00': testApi }) .set(networkModel.$chains, { '0x00': testChain }) .set(networkModel.$connectionStatuses, { '0x00': ConnectionStatus.CONNECTED }) - .set(walletModel.$allWallets, [initiatorWallet, signerWallet, multisigWallet]) + .set(walletModel._test.$allWallets, [initiatorWallet, signerWallet, multisigWallet]) .set(signatoryModel.$signatories, []), }); diff --git a/src/renderer/widgets/CreateWallet/model/__tests__/signatory-model.test.ts b/src/renderer/widgets/CreateWallet/model/__tests__/signatory-model.test.ts index a491b66da0..720f35ae15 100644 --- a/src/renderer/widgets/CreateWallet/model/__tests__/signatory-model.test.ts +++ b/src/renderer/widgets/CreateWallet/model/__tests__/signatory-model.test.ts @@ -55,7 +55,7 @@ describe('widgets/CreateWallet/model/signatory-model', () => { test('should have correct value for $ownSignatoryWallets', async () => { const scope = fork({ - values: new Map().set(walletModel.$allWallets, [initiatorWallet, signerWallet]), + values: new Map().set(walletModel._test.$allWallets, [initiatorWallet, signerWallet]), }); await allSettled(signatoryModel.events.changeSignatory, { diff --git a/src/renderer/widgets/WalletDetails/model/__tests__/vault-details-model.test.ts b/src/renderer/widgets/WalletDetails/model/__tests__/vault-details-model.test.ts index 46c98661c9..ec867a9ff2 100644 --- a/src/renderer/widgets/WalletDetails/model/__tests__/vault-details-model.test.ts +++ b/src/renderer/widgets/WalletDetails/model/__tests__/vault-details-model.test.ts @@ -51,7 +51,7 @@ describe('widgets/WalletDetails/model/vault-details-model', () => { jest.spyOn(storageService.accounts, 'deleteAll').mockResolvedValue([1]); const scope = fork({ - values: new Map().set(walletModel.$allWallets, [wallet]), + values: new Map().set(walletModel._test.$allWallets, [wallet]), }); await allSettled(vaultDetailsModel.events.keysRemoved, { scope, params: [wallet.accounts[0]] }); @@ -72,7 +72,7 @@ describe('widgets/WalletDetails/model/vault-details-model', () => { jest.spyOn(storageService.accounts, 'createAll').mockResolvedValue([newAccount]); const scope = fork({ - values: new Map().set(walletModel.$allWallets, [wallet]), + values: new Map().set(walletModel._test.$allWallets, [wallet]), }); await allSettled(vaultDetailsModel.events.accountsCreated, { scope, params }); diff --git a/src/renderer/widgets/WalletDetails/model/__tests__/wallet-provider-model.test.ts b/src/renderer/widgets/WalletDetails/model/__tests__/wallet-provider-model.test.ts index 7bf51a3c0e..c694d7d511 100644 --- a/src/renderer/widgets/WalletDetails/model/__tests__/wallet-provider-model.test.ts +++ b/src/renderer/widgets/WalletDetails/model/__tests__/wallet-provider-model.test.ts @@ -23,7 +23,7 @@ describe('widgets/WalletDetails/model/wallet-provider-model', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, wallets) + .set(walletModel._test.$allWallets, wallets) .set(networkModel.$chains, chains) .set(proxyModel.$proxies, proxies), }); @@ -41,7 +41,7 @@ describe('widgets/WalletDetails/model/wallet-provider-model', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [multisiigWallet]) + .set(walletModel._test.$allWallets, [multisiigWallet]) .set(proxyModel.$proxies, proxies) .set(networkModel.$chains, chains), }); @@ -56,7 +56,7 @@ describe('widgets/WalletDetails/model/wallet-provider-model', () => { const scope = fork({ values: new Map() - .set(walletModel.$allWallets, [multisiigWallet, ...signatoriesWallets]) + .set(walletModel._test.$allWallets, [multisiigWallet, ...signatoriesWallets]) .set(proxyModel.$proxies, proxies) .set(networkModel.$chains, chains), }); @@ -73,7 +73,7 @@ describe('widgets/WalletDetails/model/wallet-provider-model', () => { const { wallets } = walletProviderMocks; const scope = fork({ - values: new Map().set(walletModel.$allWallets, wallets), + values: new Map().set(walletModel._test.$allWallets, wallets), }); await allSettled(walletSelectModel.events.walletIdSet, { scope, params: wallets[1].id }); diff --git a/src/renderer/widgets/WalletDetails/model/vault-details-model.ts b/src/renderer/widgets/WalletDetails/model/vault-details-model.ts index 81c80552c7..7fe77a03ed 100644 --- a/src/renderer/widgets/WalletDetails/model/vault-details-model.ts +++ b/src/renderer/widgets/WalletDetails/model/vault-details-model.ts @@ -11,8 +11,9 @@ import { type DraftAccount, type ID, type ShardAccount, - type Wallet, } from '@/shared/core'; +import { series } from '@/shared/effector'; +import { nonNullable } from '@/shared/lib/utils'; import { accountUtils, walletModel } from '@/entities/wallet'; import { proxiesModel } from '@/features/proxies'; @@ -86,17 +87,18 @@ sample({ sample({ clock: removeKeysFx.doneData, source: walletModel.$allWallets, - filter: (_, ids) => Boolean(ids), + filter: (_, ids) => nonNullable(ids), fn: (wallets, ids) => { const removeMap = ids!.reduce>((acc, id) => ({ ...acc, [id]: true }), {}); return wallets.map((wallet) => { - const remainingAccounts = wallet.accounts.filter(({ id }) => !removeMap[id]); - - return { ...wallet, accounts: remainingAccounts } as Wallet; + return { + walletId: wallet.id, + accounts: wallet.accounts.filter(({ id }) => !removeMap[id]), + }; }); }, - target: walletModel.$allWallets, + target: series(walletModel.events.updateAccounts), }); sample({ @@ -109,15 +111,16 @@ sample({ clock: accountsCreated, target: createAccountsFx }); sample({ clock: createAccountsFx.done, source: walletModel.$allWallets, - filter: (_, { result }) => Boolean(result), + filter: (_, { result }) => nonNullable(result), fn: (wallets, { params, result }) => { - return wallets.map((wallet) => { - if (wallet.id !== params.walletId) return wallet; + const wallet = wallets.find((w) => w.id === params.walletId); - return { ...wallet, accounts: [...wallet.accounts, ...result!] } as Wallet; - }); + return { + walletId: params.walletId, + accounts: wallet ? [...wallet.accounts, ...result!] : result!, + }; }, - target: walletModel.$allWallets, + target: walletModel.events.updateAccounts, }); sample({ diff --git a/src/renderer/widgets/WalletDetails/model/wc-details-model.ts b/src/renderer/widgets/WalletDetails/model/wc-details-model.ts index 37c0e8f017..139abe3c7d 100644 --- a/src/renderer/widgets/WalletDetails/model/wc-details-model.ts +++ b/src/renderer/widgets/WalletDetails/model/wc-details-model.ts @@ -60,6 +60,7 @@ sample({ return step === ReconnectStep.RECONNECTING && Boolean(wallet) && Boolean(session?.topic); }, fn: ({ wallet, session }) => ({ + walletId: wallet!.id, accounts: wallet!.accounts, topic: session!.topic, }),