Skip to content

Commit

Permalink
Feat: Wallet refactoring (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuul-wq authored Oct 13, 2023
1 parent fd7477e commit aceb70d
Show file tree
Hide file tree
Showing 334 changed files with 2,703 additions and 3,355 deletions.
8 changes: 4 additions & 4 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm githook:pre-commit
##!/usr/bin/env sh
#. "$(dirname -- "$0")/_/husky.sh"
#
#pnpm githook:pre-commit
8 changes: 4 additions & 4 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm githook:pre-push
##!/bin/sh
#. "$(dirname "$0")/_/husky.sh"
#
#pnpm githook:pre-push
37 changes: 18 additions & 19 deletions src/renderer/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
import { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useNavigate, useRoutes } from 'react-router-dom';
import { useUnit } from 'effector-react';

import { FallbackScreen } from '@renderer/components/common';
import { useAccount } from '@renderer/entities/account';
import { CreateWalletProvider } from '@renderer/widgets/CreateWallet';
import { walletModel } from '@renderer/entities/wallet';
import { ROUTES_CONFIG } from '@renderer/pages';
import { Paths } from '@renderer/shared/routes';
import {
ConfirmDialogProvider,
I18Provider,
MatrixProvider,
NetworkProvider,
GraphqlProvider,
MultisigChainProvider,
Paths,
routesConfig,
} from './providers';

const SPLASH_SCREEN_DELAY = 450;

const App = () => {
export const App = () => {
const navigate = useNavigate();
const appRoutes = useRoutes(routesConfig);
const { getAccounts } = useAccount();
const appRoutes = useRoutes(ROUTES_CONFIG);

const [showSplashScreen, setShowSplashScreen] = useState(true);
const [isAccountsLoading, setIsAccountsLoading] = useState(true);
const wallets = useUnit(walletModel.$wallets);
const isLoadingWallets = useUnit(walletModel.$isLoadingWallets);

const [splashScreenLoading, setSplashScreenLoading] = useState(true);

useEffect(() => {
setTimeout(() => setShowSplashScreen(false), SPLASH_SCREEN_DELAY);
setTimeout(() => setSplashScreenLoading(false), SPLASH_SCREEN_DELAY);
}, []);

getAccounts().then((accounts) => {
setIsAccountsLoading(false);
useEffect(() => {
if (isLoadingWallets) return;

if (accounts.length === 0) {
navigate(Paths.ONBOARDING, { replace: true });
}
});
}, []);
const path = wallets.length > 0 ? Paths.ASSETS : Paths.ONBOARDING;
navigate(path, { replace: true });
}, [isLoadingWallets, wallets]);

const getContent = () => {
if (showSplashScreen || isAccountsLoading) return null;
if (splashScreenLoading || isLoadingWallets) return null;

document.querySelector('.splash_placeholder')?.remove();

Expand All @@ -65,5 +66,3 @@ const App = () => {
</I18Provider>
);
};

export default App;
2 changes: 1 addition & 1 deletion src/renderer/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HashRouter as Router } from 'react-router-dom';
import log from 'electron-log';

import { kernelModel } from '@renderer/shared/core';
import App from './App';
import { App } from './App';

import './i18n';
import './index.css';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ApolloClient, ApolloProvider, from, HttpLink, InMemoryCache, Normalized
import { onError } from '@apollo/client/link/error';
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';

import { ChainId } from '@renderer/domain/shared-kernel';
import { chainsService } from '@renderer/entities/network';
import { useSettingsStorage } from '@renderer/entities/settings';
import type { ChainId } from '@renderer/shared/core';

type GraphqlContextProps = {
changeClient: (chainId: ChainId) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { act, render, screen } from '@testing-library/react';

import { Matrix } from '@renderer/shared/api/matrix';
import { ConnectionType } from '@renderer/domain/connection';
import { ConnectionType } from '@renderer/shared/core';
import { MatrixProvider } from './MatrixContext';

jest.mock('@renderer/shared/api/matrix', () => ({ Matrix: jest.fn().mockReturnValue({}) }));

jest.mock('@renderer/entities/account', () => ({
useAccount: jest.fn().mockReturnValue({
getAccounts: jest.fn().mockReturnValue([]),
}),
}));

jest.mock('@renderer/entities/multisig', () => ({
useMultisigTx: jest.fn().mockReturnValue({
getMultisigTxs: jest.fn(),
Expand All @@ -26,12 +20,6 @@ jest.mock('@renderer/entities/multisig', () => ({
}),
}));

jest.mock('@renderer/entities/contact', () => ({
useContact: jest.fn().mockReturnValue({
getContacts: jest.fn().mockReturnValue([]),
}),
}));

jest.mock('@renderer/entities/notification', () => ({
useNotification: jest.fn().mockReturnValue({
addNotification: jest.fn(),
Expand Down
78 changes: 40 additions & 38 deletions src/renderer/app/providers/context/MatrixContext/MatrixContext.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { createContext, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react';
import { useUnit } from 'effector-react';

import { createMultisigAccount, getMultisigAccountId, MultisigAccount, useAccount } from '@renderer/entities/account';
import { getCreatedDateFromApi, toAddress, validateCallData } from '@renderer/shared/lib/utils';
import { AccountId, Address, CallHash, ChainId, SigningType } from '@renderer/domain/shared-kernel';
import { useMultisigEvent, useMultisigTx } from '@renderer/entities/multisig';
import { Signatory } from '@renderer/entities/signatory';
import { useContact } from '@renderer/entities/contact';
import { MultisigNotificationType, useNotification } from '@renderer/entities/notification';
import { useMultisigChainContext } from '@renderer/app/providers';
import { useNetworkContext } from '../NetworkContext';
import { contactModel } from '@renderer/entities/contact';
import type { Signatory, MultisigAccount, AccountId, Address, CallHash, ChainId } from '@renderer/shared/core';
import {
ApprovePayload,
BaseMultisigPayload,
Expand All @@ -30,6 +29,8 @@ import {
SigningStatus,
useTransaction,
} from '@renderer/entities/transaction';
import { walletModel, accountUtils } from '@renderer/entities/wallet';
import { WalletType, SigningType, CryptoType, ChainType, AccountType } from '@renderer/shared/core';

type MatrixContextProps = {
matrix: ISecureMessenger;
Expand All @@ -39,10 +40,11 @@ type MatrixContextProps = {
const MatrixContext = createContext<MatrixContextProps>({} as MatrixContextProps);

export const MatrixProvider = ({ children }: PropsWithChildren) => {
const { getContacts } = useContact();
const contacts = useUnit(contactModel.$contacts);
const accounts = useUnit(walletModel.$accounts);

const { addTask } = useMultisigChainContext();
const { getMultisigTx, addMultisigTx, updateMultisigTx, updateCallData } = useMultisigTx({ addTask });
const { getAccounts, addAccount, updateAccount, setActiveAccount } = useAccount();
const { decodeCallData } = useTransaction();
const { connections } = useNetworkContext();
const { addNotification } = useNotification();
Expand Down Expand Up @@ -79,19 +81,18 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {
const onInvite = async (payload: InvitePayload) => {
console.info('💛 ===> onInvite', payload);

const { roomId, content, sender } = payload;
const { roomId, content } = payload;
const { accountId, threshold, signatories, accountName, creatorAccountId } = content.mstAccount;

try {
validateMstAccount(accountId, signatories, threshold);

const accounts = await getAccounts();
const mstAccount = accounts.find((a) => a.accountId === accountId) as MultisigAccount;

if (!mstAccount) {
console.log(`No multisig account ${accountId} found. Joining room and adding wallet`);

await joinRoom(roomId, content, sender === matrix.userId);
await joinRoom(roomId, content);
await addNotification({
smpRoomId: roomId,
multisigAccountId: accountId,
Expand Down Expand Up @@ -121,41 +122,47 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {
};

const validateMstAccount = (accountId: AccountId, signatories: AccountId[], threshold: number) => {
const isValid = accountId === getMultisigAccountId(signatories, threshold);
const isValid = accountId === accountUtils.getMultisigAccountId(signatories, threshold);

if (!isValid) {
throw new Error(`Multisig address ${accountId} can't be derived from signatories and threshold`);
}
};

const createMstAccount = async (roomId: string, extras: SpektrExtras, makeActive: boolean) => {
const createMstAccount = (roomId: string, extras: SpektrExtras) => {
const { signatories, threshold, accountName, creatorAccountId } = extras.mstAccount;

const contacts = await getContacts();
const contactsMap = contacts.reduce<Record<AccountId, [Address, string]>>((acc, contact) => {
acc[contact.accountId] = [contact.address, contact.name];

return acc;
}, {});

const mstSignatories = signatories.map((accountId) => ({
accountId,
address: contactsMap[accountId]?.[0] || toAddress(accountId),
name: contactsMap[accountId]?.[1],
}));

const mstAccount = createMultisigAccount({
threshold,
creatorAccountId,
name: accountName,
signatories: mstSignatories,
matrixRoomId: roomId,
isActive: false,
});

await addAccount(mstAccount).then((id) => {
if (!makeActive) return;

setActiveAccount(id);
walletModel.events.multisigCreated({
wallet: {
name: accountName,
type: WalletType.MULTISIG,
signingType: SigningType.MULTISIG,
},
accounts: [
{
threshold,
creatorAccountId,
accountId: accountUtils.getMultisigAccountId(signatories, threshold),
signatories: mstSignatories,
name: accountName,
matrixRoomId: roomId,
cryptoType: CryptoType.SR25519,
chainType: ChainType.SUBSTRATE,
type: AccountType.MULTISIG,
},
],
});
};

Expand All @@ -176,8 +183,9 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {
console.log(`Leave old ${mstAccount.matrixRoomId}, join new room ${roomId}`);
await matrix.leaveRoom(mstAccount.matrixRoomId);
await matrix.joinRoom(roomId);
await updateAccount<MultisigAccount>({
...mstAccount,

walletModel.events.multisigAccountUpdated({
id: mstAccount.id,
name: accountName,
matrixRoomId: roomId,
creatorAccountId,
Expand All @@ -188,10 +196,10 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {
}
};

const joinRoom = async (roomId: string, extras: SpektrExtras, makeActive: boolean) => {
const joinRoom = async (roomId: string, extras: SpektrExtras): Promise<void> => {
try {
await matrix.joinRoom(roomId);
await createMstAccount(roomId, extras, makeActive);
createMstAccount(roomId, extras);
} catch (error) {
console.error(error);
}
Expand All @@ -202,9 +210,9 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {

if (!validateMatrixEvent(content, extras)) return;

const multisigAccount = await getMultisigAccount(extras.mstAccount.accountId);
const multisigAccount = accounts.find((a) => a.accountId === extras.mstAccount.accountId);

if (!multisigAccount) return;
if (!multisigAccount || !accountUtils.isMultisigAccount(multisigAccount)) return;

const multisigTx = await getMultisigTx(
multisigAccount.accountId,
Expand Down Expand Up @@ -233,18 +241,12 @@ export const MatrixProvider = ({ children }: PropsWithChildren) => {
): boolean => {
const { accountId, threshold, signatories } = extras.mstAccount;
const senderIsSignatory = signatories.some((accountId) => accountId === senderAccountId);
const mstAccountIsValid = accountId === getMultisigAccountId(signatories, threshold);
const mstAccountIsValid = accountId === accountUtils.getMultisigAccountId(signatories, threshold);
const callDataIsValid = !callData || validateCallData(callData, callHash);

return senderIsSignatory && mstAccountIsValid && callDataIsValid;
};

const getMultisigAccount = async (accountId: AccountId): Promise<MultisigAccount | undefined> => {
const accounts = await getAccounts<MultisigAccount>({ signingType: SigningType.MULTISIG });

return accounts.find((a) => a.accountId === accountId) as MultisigAccount;
};

const createEvent = async (
payload: ApprovePayload | FinalApprovePayload,
eventStatus: SigningStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { createContext, PropsWithChildren, useContext, useEffect } from 'react';
import { VoidFn } from '@polkadot/api/types';
import { Event } from '@polkadot/types/interfaces';
import { useUnit } from 'effector-react';

import { useChainSubscription } from '@renderer/entities/chain';
import { useNetworkContext } from '../NetworkContext';
import { useMultisigTx, useMultisigEvent } from '@renderer/entities/multisig';
import { useAccount, MultisigAccount } from '@renderer/entities/account';
import { MultisigTxFinalStatus, SigningStatus } from '@renderer/entities/transaction';
import { toAddress, getCreatedDateFromApi } from '@renderer/shared/lib/utils';
import { ChainId } from '@renderer/domain/shared-kernel';
import { useDebounce, useTaskQueue } from '@renderer/shared/lib/hooks';
import { ConnectionStatus } from '@renderer/domain/connection';
import { Task } from '@renderer/shared/lib/hooks/useTaskQueue';
import type { MultisigAccount, ChainId } from '@renderer/shared/core';
import { ConnectionStatus } from '@renderer/shared/core';
import { walletModel, accountUtils } from '@renderer/entities/wallet';

type MultisigChainContextProps = {
addTask: (task: Task) => void;
Expand All @@ -33,13 +34,15 @@ export const MultisigChainProvider = ({ children }: PropsWithChildren) => {
updateCallData,
updateCallDataFromChain,
} = useMultisigTx({ addTask });
const { getActiveMultisigAccount } = useAccount();
const activeAccounts = useUnit(walletModel.$activeAccounts);

const { updateEvent, getEvents, addEventWithQueue } = useMultisigEvent({ addTask });

const { subscribeEvents } = useChainSubscription();
const debouncedConnections = useDebounce(connections, 1000);

const account = getActiveMultisigAccount();
const activeAccount = activeAccounts.at(0);
const account = activeAccount && accountUtils.isMultisigAccount(activeAccount) ? activeAccount : undefined;

const txs = getLiveAccountMultisigTxs(account?.accountId ? [account.accountId] : []);

Expand Down
Loading

0 comments on commit aceb70d

Please sign in to comment.