Skip to content

Commit

Permalink
refactor: accounts and wallets separation (#2885)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnthecat authored Jan 3, 2025
1 parent 13398fb commit c49fa39
Show file tree
Hide file tree
Showing 318 changed files with 2,883 additions and 1,956 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ module.exports = {
},
{
from: 'entities',
allow: ['app', 'shared', 'entities', /* TODO fix */ 'features'],
allow: ['app', 'shared', 'entities', 'domains', /* TODO fix */ 'features'],
},
{
from: 'domains',
Expand All @@ -334,7 +334,7 @@ module.exports = {
},
{
from: 'widgets',
allow: ['app', 'shared', 'entities', 'features', /* TODO fix */ 'pages', 'widgets'],
allow: ['app', 'shared', 'entities', 'features', 'domains', /* TODO fix */ 'pages', 'widgets'],
},
],
},
Expand Down
15 changes: 0 additions & 15 deletions .graphqlconfig

This file was deleted.

20 changes: 17 additions & 3 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,31 @@
"trailingComma": "all",
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-jsdoc", "prettier-plugin-css-order", "prettier-plugin-tailwindcss"],
"plugins": [
"prettier-plugin-jsdoc",
"prettier-plugin-css-order",
"prettier-plugin-tailwindcss"
],
"tailwindConfig": "./tailwind.config.ts",
"tailwindFunctions": ["cnTw", "cn"],
"tailwindFunctions": [
"cnTw",
"cn"
],
"jsdocCommentLineStrategy": "keep",
"jsdocPreferCodeFences": true,
"jsdocSeparateTagGroups": true,
"jsdocPrintWidth": 80,
"proseWrap": "always",
"overrides": [
{
"files": ["./src/renderer/domains/**/*.ts", "./src/renderer/shared/pallet/**/*.ts", "./src/renderer/features/fellowship-*/**/*.{ts,tsx}"],
"files": [
"./src/renderer/domains/**/*.ts",
"./src/renderer/shared/pallet/**/*.ts",
"./src/renderer/features/fellowship-*/**/*.{ts,tsx}",
"./src/renderer/features/wallet-*/**/*.{ts,tsx}",
"./src/renderer/shared/ui-kit/**/*.{ts,tsx}",
"./src/renderer/shared/ui-entities/**/*.{ts,tsx}"
],
"options": {
"arrowParens": "avoid"
}
Expand Down
2 changes: 2 additions & 0 deletions eslint-local-rules/rules/enforce-di-naming-convention.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const {
const IDENTIFIERS_SUFFIXES = {
createSlot: 'Slot',
createPipeline: 'Pipeline',
createAsyncPipeline: 'AsyncPipeline',
createAnyOf: 'AnyOf',
};

const fixName = (name, suffix) => camelCase(name.replace(new RegExp(suffix, 'gi'), '').replace(/\$/g, '') + suffix);
Expand Down
19 changes: 19 additions & 0 deletions graphql.config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
projects:
SubQuery Governance:
schema: https://subquery-governance-polkadot-prod.novasama-tech.org
include:
- src/renderer/shared/api/governance/**

SubQuery Proxy:
schema: https://subquery-proxy-polkadot-prod.novasama-tech.org
include:
- src/renderer/entities/multisig/**
- src/renderer/entities/proxy/**

SubQuery History:
schema: https://subquery-history-polkadot-prod.novasama-tech.org

SubQuery Collectives:
schema: https://subquery-collectives-polkadot-prod.novasama-tech.org
include:
- src/renderer/domains/collectives/**
6 changes: 6 additions & 0 deletions src/renderer/app/modelInit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { kernelModel } from '@/shared/core';
import { accounts } from '@/domains/network';
import { basketModel } from '@/entities/basket';
import { governanceModel } from '@/entities/governance';
import { multisigsModel } from '@/entities/multisig';
Expand All @@ -19,8 +20,13 @@ import { proxiesModel } from '@/features/proxies';
import { settingsNavigationFeature } from '@/features/settings-navigation';
import { stakingNavigationFeature } from '@/features/staking-navigation';
import { walletSelectFeature } from '@/features/wallet-select';
import { walletWatchOnlyFeature } from '@/features/wallet-watch-only';

export const initModel = () => {
accounts.populate();

walletWatchOnlyFeature.start();

assetsNavigationFeature.start();
stakingNavigationFeature.start();
governanceNavigationFeature.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { type ChainId, type MultisigAccount, MultisigTxFinalStatus, type Signing
import { useDebounce, useTaskQueue } from '@/shared/lib/hooks';
import { type Task } from '@/shared/lib/hooks/useTaskQueue';
import { getCreatedDateFromApi, toAddress } from '@/shared/lib/utils';
import { pjsSchema } from '@/shared/polkadotjs-schemas';
import { subscriptionService } from '@/entities/chain';
import { useMultisigEvent, useMultisigTx } from '@/entities/multisig';
import { networkModel, networkUtils } from '@/entities/network';
Expand Down Expand Up @@ -107,7 +108,7 @@ export const MultisigChainProvider = ({ children }: PropsWithChildren) => {

if (!tx) return;

const accountId = event.data[0].toHex();
const accountId = pjsSchema.helpers.toAccountId(event.data[0].toHex());

addEventWithQueue(
{
Expand Down
12 changes: 4 additions & 8 deletions src/renderer/domains/collectives/model/members/service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { type Account, type Chain, type Wallet } from '@/shared/core';
import { dictionary } from '@/shared/lib/utils';
import { accountUtils } from '@/entities/wallet';
import { type AnyAccount } from '@/domains/network';

import { type CoreMember, type Member } from './types';

const findMatchingMember = (wallet: Wallet, accounts: Account[], chain: Chain, members: Member[]) => {
const walletAccounts = accounts.filter(account => {
return accountUtils.isNonBaseVaultAccount(account, wallet) && accountUtils.isChainAndCryptoMatch(account, chain);
});
const accountsDictionary = dictionary(walletAccounts, 'accountId');
const findMatchingMember = (accounts: AnyAccount[], members: Member[]) => {
const accountsDictionary = dictionary(accounts, 'accountId');

return members.find(member => member.accountId in accountsDictionary) ?? null;
};

const findMatchingAccount = (accounts: Account[], member: Member) => {
const findMatchingAccount = (accounts: AnyAccount[], member: Member) => {
return accounts.find(a => a.accountId === member.accountId) ?? null;
};

Expand Down
5 changes: 3 additions & 2 deletions src/renderer/domains/collectives/model/voting/service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { type Account, type Chain, TransactionType } from '@/shared/core';
import { type Chain, TransactionType } from '@/shared/core';
import { toAddress } from '@/shared/lib/utils';
import { type ReferendumId } from '@/shared/pallet/referenda';
import { type AnyAccount } from '@/domains/network';
import { type CollectivePalletsType } from '../../lib/types';

import { type VotingTransaction } from './types';

type VoteTransactionParams = {
pallet: CollectivePalletsType;
account: Account;
account: AnyAccount;
chain: Chain;
rank: number;
referendumId: ReferendumId;
Expand Down
112 changes: 112 additions & 0 deletions src/renderer/domains/network/accounts/model.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { allSettled, fork } from 'effector';

import { CryptoType, SigningType } from '@/shared/core';
import { createAccountId } from '@/shared/mocks';

import { accountsDomainModel } from './model';
import { type AnyAccount, type AnyAccountDraft } from './types';

const accounts: AnyAccount[] = [
{
id: 'test',
type: 'chain',
accountId: createAccountId('1'),
chainId: '0x01',
name: '',
walletId: 0,
signingType: SigningType.POLKADOT_VAULT,
cryptoType: CryptoType.SR25519,
},
{
id: 'test 2',
type: 'universal',
accountId: createAccountId('2'),
name: '',
walletId: 0,
signingType: SigningType.POLKADOT_VAULT,
cryptoType: CryptoType.SR25519,
},
];

describe('accounts model', () => {
it('should populate accounts', async () => {
const scope = fork({
handlers: [[accountsDomainModel.populate, () => accounts]],
});

expect(scope.getState(accountsDomainModel.$populated)).toEqual(false);

await allSettled(accountsDomainModel.populate, { scope });

expect(scope.getState(accountsDomainModel.$list)).toEqual(accounts);
expect(scope.getState(accountsDomainModel.$populated)).toEqual(true);
});

it('should create new accounts', async () => {
const scope = fork({
handlers: [[accountsDomainModel.createAccounts, (accounts: AnyAccount[]) => accounts]],
});

await allSettled(accountsDomainModel.createAccounts, { scope, params: accounts });

expect(scope.getState(accountsDomainModel.$list)).toEqual(accounts);
});

it('should successfully update account', async () => {
const scope = fork({
values: [[accountsDomainModel.__test.$list, accounts]],
handlers: [[accountsDomainModel.__test.updateAccountFx, () => true]],
});

const draft: AnyAccountDraft = {
accountId: createAccountId('1'),
chainId: '0x01',
walletId: 0,
type: 'chain',
name: 'test',
};

await allSettled(accountsDomainModel.updateAccount, {
scope,
params: draft,
});

expect(scope.getState(accountsDomainModel.$list)).toEqual([{ ...accounts[0], ...draft }, accounts[1]]);
});

it('should skip update if account is not defined', async () => {
const scope = fork({
values: [[accountsDomainModel.__test.$list, accounts]],
handlers: [[accountsDomainModel.__test.updateAccountFx, () => false]],
});

const draft: AnyAccountDraft = {
accountId: createAccountId('3'),
chainId: '0x01',
walletId: 0,
type: 'chain',
name: 'test',
};

await allSettled(accountsDomainModel.updateAccount, {
scope,
params: draft,
});

expect(scope.getState(accountsDomainModel.$list)).toEqual(accounts);
});

it('should create delete accounts', async () => {
const scope = fork({
handlers: [
[accountsDomainModel.populate, () => accounts],
[accountsDomainModel.deleteAccounts, (accounts: AnyAccount[]) => accounts],
],
});

await allSettled(accountsDomainModel.populate, { scope });
await allSettled(accountsDomainModel.deleteAccounts, { scope, params: accounts.slice(0, 1) });

expect(scope.getState(accountsDomainModel.$list)).toEqual(accounts.slice(1, 2));
});
});
107 changes: 107 additions & 0 deletions src/renderer/domains/network/accounts/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { attach, createEffect, createStore, restore, sample } from 'effector';
import { once, readonly } from 'patronum';

import { storageService } from '@/shared/api/storage';
import { merge, nonNullable, nullable } from '@/shared/lib/utils';

import { accountsService } from './service';
import { type AnyAccount, type AnyAccountDraft } from './types';

const $accounts = createStore<AnyAccount[]>([]);

const $populated = restore(
once($accounts.updates).map(() => true),
false,
);

const populateFx = createEffect((): Promise<AnyAccount[]> => storageService.accounts2.readAll());

const createAccountsFx = createEffect(async (accounts: AnyAccount[]): Promise<AnyAccount[]> => {
return storageService.accounts2
.createAll(accounts.map(a => ({ ...a, id: accountsService.uniqId(a) })))
.then(x => x ?? []);
});

const updateAccountFx = createEffect(async (account: AnyAccountDraft | null): Promise<boolean> => {
if (nullable(account)) return false;

const id = accountsService.uniqId(account);

return storageService.accounts2.update(id, account).then(nonNullable);
});

const updateAccount = attach({
source: $accounts,
mapParams: (draft: AnyAccountDraft, accounts) => {
if (accounts.find(a => accountsService.uniqId(a) === accountsService.uniqId(draft))) {
return draft;
}

return null;
},
effect: updateAccountFx,
});

const deleteAccountsFx = createEffect(async (accounts: AnyAccount[]) => {
// TODO set correct id
await storageService.accounts2.deleteAll(accounts.map(accountsService.uniqId));

return accounts;
});

sample({
clock: populateFx.doneData,
target: $accounts,
});

sample({
clock: createAccountsFx.doneData,
source: $accounts,
fn: (accounts, newAccounts) =>
merge({
a: accounts,
b: newAccounts,
mergeBy: accountsService.uniqId,
}),
target: $accounts,
});

sample({
clock: updateAccount.done,
source: $accounts,
filter: (_, { result: successful }) => successful,
fn: (accounts, { params: draft }) => {
const draftId = accountsService.uniqId(draft);

return accounts.map(a =>
accountsService.uniqId(a) === draftId ? ({ ...a, ...draft } as AnyAccount) : a,
) as AnyAccount[];
},
target: $accounts,
});

sample({
clock: deleteAccountsFx.done,
source: $accounts,
fn: (accounts, { result: deletedAccounts }) => {
const deletedIds = deletedAccounts.map(accountsService.uniqId);

return accounts.filter(a => !deletedIds.includes(accountsService.uniqId(a)));
},
target: $accounts,
});

export const accountsDomainModel = {
$list: readonly($accounts),
$populated: readonly($populated),

populate: populateFx,
createAccounts: createAccountsFx,
updateAccount,
deleteAccounts: deleteAccountsFx,

__test: {
$list: $accounts,
updateAccountFx,
},
};
Loading

0 comments on commit c49fa39

Please sign in to comment.