diff --git a/packages/apps/dev-wallet/src/App/layout-full.tsx b/packages/apps/dev-wallet/src/App/layout-full.tsx new file mode 100644 index 0000000000..be9e466a0c --- /dev/null +++ b/packages/apps/dev-wallet/src/App/layout-full.tsx @@ -0,0 +1,16 @@ +import { Stack } from '@kadena/kode-ui'; +import { FC } from 'react'; +import { Outlet } from 'react-router-dom'; +import { LayoutFullContainerStyle } from './layout-mini.css'; + +export const LayoutFull: FC = () => { + return ( + <> + <Stack className={LayoutFullContainerStyle}> + <Outlet /> + </Stack> + + <div id="modalportal"></div> + </> + ); +}; diff --git a/packages/apps/dev-wallet/src/App/layout-mini.css.ts b/packages/apps/dev-wallet/src/App/layout-mini.css.ts index 06574463f4..25df37c629 100644 --- a/packages/apps/dev-wallet/src/App/layout-mini.css.ts +++ b/packages/apps/dev-wallet/src/App/layout-mini.css.ts @@ -21,3 +21,17 @@ export const containerStyle = style([ height: '100dvh', }, ]); + +export const LayoutFullContainerStyle = style([ + atoms({ + padding: 'sm', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }), + { + margin: '0 auto', + textAlign: 'center', + height: '100dvh', + }, +]); diff --git a/packages/apps/dev-wallet/src/App/routes.tsx b/packages/apps/dev-wallet/src/App/routes.tsx index a9e1c2c3ca..98f1662ae7 100644 --- a/packages/apps/dev-wallet/src/App/routes.tsx +++ b/packages/apps/dev-wallet/src/App/routes.tsx @@ -43,6 +43,7 @@ import { HomePage } from '../pages/home/home-page'; import { SelectProfile } from '../pages/select-profile/select-profile'; import { UnlockProfile } from '../pages/unlock-profile/unlock-profile'; import { getScriptType } from '../utils/window'; +import { LayoutFull } from './layout-full'; import { LayoutMini } from './layout-mini'; import { Layout } from './Layout/Layout'; import { useGlobalState } from './providers/globalState'; @@ -142,12 +143,11 @@ export const Routes: FC = () => { path="/settings/keep-password-policy" element={<KeepPasswordPolicy />} /> - <Route - path="/account-discovery/:keySourceId" - element={<AccountDiscovery />} - /> </Route> </Route> + <Route element={<LayoutFull />}> + <Route path="/account-discovery" element={<AccountDiscovery />} /> + </Route> <Route element={<LayoutMini />}> <Route path="/settings/reveal-phrase" element={<RevealPhrase />} /> <Route diff --git a/packages/apps/dev-wallet/src/config.ts b/packages/apps/dev-wallet/src/config.ts index bd2513423e..4ee2ef1bfd 100644 --- a/packages/apps/dev-wallet/src/config.ts +++ b/packages/apps/dev-wallet/src/config.ts @@ -12,7 +12,7 @@ export const config = { colorList, defaultAccentColor: colorList[0], DB: { - DB_VERSION: 44, + DB_VERSION: 45, DB_NAME: 'dev-wallet', }, ACCOUNTS: { diff --git a/packages/apps/dev-wallet/src/modules/account/account.service.ts b/packages/apps/dev-wallet/src/modules/account/account.service.ts index 7416453436..efb7e1393c 100644 --- a/packages/apps/dev-wallet/src/modules/account/account.service.ts +++ b/packages/apps/dev-wallet/src/modules/account/account.service.ts @@ -47,6 +47,7 @@ import { IRetrievedAccount } from './IRetrievedAccount'; export type IWalletDiscoveredAccount = { chainId: ChainId; + networkUUID: UUID; result: | undefined | { @@ -129,7 +130,7 @@ export const accountDiscovery = ( )( (emit) => async ( - network: INetwork, + networks: INetwork[], keySource: IKeySource, profileId: string, numberOfKeys = 20, @@ -140,7 +141,6 @@ export const accountDiscovery = ( } const keySourceService = await keySourceManager.get(keySource.source); const accounts: IAccount[] = []; - const keysets: IKeySet[] = []; const usedKeys: IKeyItem[] = []; const saveCallbacks: Array<() => Promise<void>> = []; for (let i = 0; i < numberOfKeys; i++) { @@ -150,95 +150,87 @@ export const accountDiscovery = ( } await emit('key-retrieved')(key); const principal = `k:${key.publicKey}`; - const chainResult = await discoverAccount( - principal, - network.networkId, - undefined, - contract, - ) - .on('chain-result', async (data) => { - await emit('chain-result')({ - chainId: data.chainId, - result: data.result - ? { - ...data.result, - guard: data.result.details.guard - ? { - ...data.result.details.guard, - principal: data.result.principal, - } - : undefined, - } - : undefined, + for (const network of networks) { + const chainResult = await discoverAccount( + principal, + network.networkId, + undefined, + contract, + ) + .on('chain-result', async (data) => { + await emit('chain-result')({ + chainId: data.chainId, + networkUUID: network.uuid, + result: data.result + ? { + ...data.result, + guard: data.result.details.guard + ? { + ...data.result.details.guard, + principal: data.result.principal, + } + : undefined, + balance: data.result.details.balance, + } + : undefined, + }); + }) + .execute() + .catch((error) => { + console.log('DISCOVERY_ERROR', error); + return []; }); - }) - .execute(); - if (chainResult.filter(({ result }) => Boolean(result)).length > 0) { - const availableKeyset = await accountRepository.getKeysetByPrincipal( - principal, - profileId, - ); - usedKeys.push(key); - const keyset: IKeySet = availableKeyset || { - uuid: crypto.randomUUID(), - principal, - profileId, - guard: { - keys: [key.publicKey], - pred: 'keys-all', - }, - alias: '', - }; - if (!availableKeyset) { - keysets.push(keyset); + if (chainResult.filter(({ result }) => Boolean(result)).length > 0) { + usedKeys.push(key); + const account: IAccount = { + uuid: crypto.randomUUID(), + profileId, + networkUUID: network.uuid, + contract, + guard: { + keys: [key.publicKey], + pred: 'keys-all', + principal, + }, + address: `k:${key.publicKey}`, + chains: chainResult + .filter(({ result }) => Boolean(result)) + .map(({ chainId, result }) => ({ + chainId: chainId!, + balance: + new PactNumber(result!.details.balance).toDecimal() || + '0.0', + })), + overallBalance: chainResult.reduce( + (acc, { result }) => + result && result.details.balance + ? new PactNumber(result.details.balance) + .plus(acc) + .toDecimal() + : acc, + '0', + ), + }; + accounts.push(account); + saveCallbacks.push(async () => { + if (!keySource.keys.find((k) => k.publicKey === key.publicKey)) { + await keySourceService.createKey( + keySource.uuid, + key.index as number, + ); + } + await accountRepository.addAccount(account); + }); } - const account: IAccount = { - uuid: crypto.randomUUID(), - profileId, - networkUUID: network.uuid, - contract, - keysetId: keyset.uuid, - guard: { - keys: [key.publicKey], - pred: 'keys-all', - principal, - }, - address: `k:${key.publicKey}`, - chains: chainResult - .filter(({ result }) => Boolean(result)) - .map(({ chainId, result }) => ({ - chainId: chainId!, - balance: - new PactNumber(result!.details.balance).toDecimal() || '0.0', - })), - overallBalance: chainResult.reduce( - (acc, { result }) => - result && result.details.balance - ? new PactNumber(result.details.balance).plus(acc).toDecimal() - : acc, - '0', - ), - }; - accounts.push(account); - saveCallbacks.push(async () => { - if (!keySource.keys.find((k) => k.publicKey === key.publicKey)) { - await keySourceService.createKey( - keySource.uuid, - key.index as number, - ); - } - if (!availableKeyset) { - await accountRepository.addKeyset(keyset); - } - await accountRepository.addAccount(account); - }); } } await emit('query-done')(accounts); - await Promise.all(saveCallbacks.map((cb) => cb().catch(console.error))); + for (const cb of saveCallbacks) { + await cb().catch(console.error); + } keySourceService.clearCache(); await emit('accounts-saved')(accounts); diff --git a/packages/apps/dev-wallet/src/modules/communication/communication.provider.tsx b/packages/apps/dev-wallet/src/modules/communication/communication.provider.tsx index 9cc39202e8..8e48423f0f 100644 --- a/packages/apps/dev-wallet/src/modules/communication/communication.provider.tsx +++ b/packages/apps/dev-wallet/src/modules/communication/communication.provider.tsx @@ -137,7 +137,15 @@ export const CommunicationProvider: FC<PropsWithChildren> = ({ children }) => { return () => { handlers.forEach((unsubscribe) => unsubscribe()); }; - }, [navigate, requests, isUnlocked]); + }, [ + navigate, + requests, + isUnlocked, + accounts, + profile, + networks, + activeNetwork, + ]); return ( <communicationContext.Provider value={requests}> diff --git a/packages/apps/dev-wallet/src/modules/db/migration/migrateFrom44to45.ts b/packages/apps/dev-wallet/src/modules/db/migration/migrateFrom44to45.ts new file mode 100644 index 0000000000..430ad78e9d --- /dev/null +++ b/packages/apps/dev-wallet/src/modules/db/migration/migrateFrom44to45.ts @@ -0,0 +1,24 @@ +// check the change log for more details +import { INetwork } from '@/modules/network/network.repository'; +import { getAllItems, putItem } from '../indexeddb'; + +const changeLog = ['Enable mainnet by default']; + +export async function migrateFrom44to45( + db: IDBDatabase, + transaction: IDBTransaction, +) { + console.log('change log:'); + changeLog.forEach((log, index) => console.log(index, log)); + const allNetworks = await getAllItems(db, transaction)<INetwork>('network'); + const update = putItem(db, transaction); + const mainnet = allNetworks.find( + (network) => network.networkId === 'mainnet01', + ); + if (mainnet?.disabled) { + await update<INetwork>('network', { + ...mainnet, + disabled: false, + }); + } +} diff --git a/packages/apps/dev-wallet/src/modules/db/migration/migration.ts b/packages/apps/dev-wallet/src/modules/db/migration/migration.ts index f4141908fd..0f3f9126ca 100644 --- a/packages/apps/dev-wallet/src/modules/db/migration/migration.ts +++ b/packages/apps/dev-wallet/src/modules/db/migration/migration.ts @@ -8,6 +8,7 @@ import { migrateFrom40to41 } from './migrateFrom40to41'; import { migrateFrom41to42 } from './migrateFrom41to42'; import { migrateFrom42to43 } from './migrateFrom42to43'; import { migrateFrom43to44 } from './migrateFrom43to44'; +import { migrateFrom44to45 } from './migrateFrom44to45'; const { DB_NAME, DB_VERSION } = config.DB; @@ -19,6 +20,7 @@ const migrationMap = { 41: migrateFrom41to42, 42: migrateFrom42to43, 43: migrateFrom43to44, + 44: migrateFrom44to45, }; export async function migration(result: { diff --git a/packages/apps/dev-wallet/src/modules/key-source/key-source.repository.ts b/packages/apps/dev-wallet/src/modules/key-source/key-source.repository.ts index 624e714f2a..bec7a6ee17 100644 --- a/packages/apps/dev-wallet/src/modules/key-source/key-source.repository.ts +++ b/packages/apps/dev-wallet/src/modules/key-source/key-source.repository.ts @@ -36,29 +36,68 @@ export interface IWebAuthn { }>; } -export type KeySourceType = IHDBIP44 | IHDChainweaver | IWebAuthn; +export type KeySourceType = (IHDBIP44 | IHDChainweaver | IWebAuthn) & { + isDefault?: boolean; +}; export interface HDWalletRepository { getKeySource: (id: string) => Promise<KeySourceType>; + getKeySources: (profileId: string) => Promise<KeySourceType[]>; addKeySource: (profile: KeySourceType) => Promise<void>; updateKeySource: (profile: KeySourceType) => Promise<void>; + patchKeySource: (id: string, patch: Partial<KeySourceType>) => Promise<void>; + setAsDefault: (id: string, profileId: string) => Promise<void>; } const createKeySourceRepository = ({ getOne, add, update, + getAll, }: IDBService): HDWalletRepository => { + const getKeySource = async (id: string): Promise<KeySourceType> => { + return getOne('keySource', id); + }; + const getKeySources = async (profileId: string): Promise<KeySourceType[]> => { + return getAll('keySource', profileId, 'profileId'); + }; + const addKeySource = async (keySource: KeySourceType): Promise<void> => { + return add('keySource', keySource); + }; + const updateKeySource = async (keySource: KeySourceType): Promise<void> => { + return update('keySource', keySource); + }; + const patchKeySource = async ( + id: string, + patch: Partial<KeySourceType>, + ): Promise<void> => { + const keySource = await getOne('keySource', id); + if (!keySource) return; + return update('keySource', { ...keySource, ...patch }); + }; + const setAsDefault = async (id: string, profileId: string): Promise<void> => { + const keySources: KeySourceType[] = await getAll( + 'keySource', + profileId, + 'profileId', + ); + if (!keySources || !keySources.length) return; + await Promise.all( + keySources + .filter((ks) => ks.uuid !== id) + .map((ks) => update('keySource', { ...ks, isDefault: false })), + ); + const keySource: KeySourceType = await getOne('keySource', id); + return update('keySource', { ...keySource, isDefault: true }); + }; + return { - getKeySource: async (id: string): Promise<KeySourceType> => { - return getOne('keySource', id); - }, - addKeySource: async (keySource: KeySourceType): Promise<void> => { - return add('keySource', keySource); - }, - updateKeySource: async (keySource: KeySourceType): Promise<void> => { - return update('keySource', keySource); - }, + getKeySource, + getKeySources, + addKeySource, + updateKeySource, + patchKeySource, + setAsDefault, }; }; diff --git a/packages/apps/dev-wallet/src/modules/network/network.repository.ts b/packages/apps/dev-wallet/src/modules/network/network.repository.ts index 265ff92c11..8a37492b42 100644 --- a/packages/apps/dev-wallet/src/modules/network/network.repository.ts +++ b/packages/apps/dev-wallet/src/modules/network/network.repository.ts @@ -79,8 +79,6 @@ export const addDefaultNetworks = execInSequence(async () => { uuid: crypto.randomUUID(), networkId: 'mainnet01', name: 'Mainnet', - // make mainnet disabled for now - disabled: true, hosts: [ { url: 'https://api.chainweb.com', diff --git a/packages/apps/dev-wallet/src/modules/wallet/wallet.hook.tsx b/packages/apps/dev-wallet/src/modules/wallet/wallet.hook.tsx index 6cfe7e21a6..69cdbbf672 100644 --- a/packages/apps/dev-wallet/src/modules/wallet/wallet.hook.tsx +++ b/packages/apps/dev-wallet/src/modules/wallet/wallet.hook.tsx @@ -65,7 +65,7 @@ export const useWallet = () => { ); const unlockProfile = useCallback( - async (profileId: string, password: string) => { + async (profileId: string, password: string, unlockSecurity = false) => { console.log('unlockProfile', profileId, password); const profile = await WalletService.unlockProfile(profileId, password); await securityService.clearSecurityPhrase(); @@ -73,7 +73,7 @@ export const useWallet = () => { const res = await setProfile(profile); channel.postMessage({ action: 'switch-profile', payload: profile }); backupDatabase().catch(console.log); - if (profile.options.rememberPassword === 'on-login') { + if (profile.options.rememberPassword === 'on-login' || unlockSecurity) { const sessionEntropy = (await Session.get('sessionId')) as string; if (!sessionEntropy) { return res; @@ -306,7 +306,8 @@ export const useWallet = () => { return keys[0]; } }); - const keySource = context.keySources[0]; + const keySource = + context.keySources.find((ks) => ks.isDefault) || context.keySources[0]; const availableKey = keySource.keys.find( (key) => !usedKeys.includes(key.publicKey), ); diff --git a/packages/apps/dev-wallet/src/modules/wallet/wallet.repository.ts b/packages/apps/dev-wallet/src/modules/wallet/wallet.repository.ts index f44bdc920d..d29fee028b 100644 --- a/packages/apps/dev-wallet/src/modules/wallet/wallet.repository.ts +++ b/packages/apps/dev-wallet/src/modules/wallet/wallet.repository.ts @@ -17,6 +17,7 @@ export interface IKeySource { profileId: string; source: KeySourceType; keys: Array<IKeyItem>; + isDefault?: boolean; } export interface IProfile { diff --git a/packages/apps/dev-wallet/src/pages/account-discovery/account-dsicovery.tsx b/packages/apps/dev-wallet/src/pages/account-discovery/account-dsicovery.tsx index 79190cf7c2..08eff87c97 100644 --- a/packages/apps/dev-wallet/src/pages/account-discovery/account-dsicovery.tsx +++ b/packages/apps/dev-wallet/src/pages/account-discovery/account-dsicovery.tsx @@ -1,7 +1,6 @@ -import { IKeyItem } from '@/modules/wallet/wallet.repository'; -import { Button, Card, Heading, Stack, Text } from '@kadena/kode-ui'; -import { useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { IKeyItem, IKeySource } from '@/modules/wallet/wallet.repository'; +import { Button, Card, Checkbox, Heading, Stack, Text } from '@kadena/kode-ui'; +import { useState } from 'react'; import { ListItem } from '@/Components/ListItem/ListItem'; import { IAccount } from '@/modules/account/account.repository'; @@ -10,20 +9,21 @@ import { accountDiscovery, } from '@/modules/account/account.service'; import { keySourceManager } from '@/modules/key-source/key-source-manager'; +import { keySourceRepository } from '@/modules/key-source/key-source.repository'; import { useWallet } from '@/modules/wallet/wallet.hook'; import { shorten } from '@/utils/helpers'; import { usePatchedNavigate } from '@/utils/usePatchedNavigate'; import { ChainId } from '@kadena/client'; import { MonoKey } from '@kadena/kode-icons/system'; +import { PactNumber } from '@kadena/pactjs'; +import { Label } from '../transaction/components/helpers'; const NUMBER_OF_KEYS_TO_DISCOVER = 20; export function AccountDiscovery() { const navigate = usePatchedNavigate(); - const processRef = useRef<ReturnType<typeof accountDiscovery>>(); - const { profile, keySources, unlockKeySource, activeNetwork } = useWallet(); - const { keySourceId } = useParams(); - const [key, setKey] = useState<IKeyItem>(); + const { profile, keySources, unlockKeySource, networks } = useWallet(); + const [key, setKey] = useState<{ [key: string]: IKeyItem }>({}); const [discoveryStatus, setDiscoveryStatus] = useState< 'idle' | 'discovering' | 'finished' >('idle'); @@ -31,36 +31,38 @@ export function AccountDiscovery() { Array<IWalletDiscoveredAccount | undefined> >([]); const [accounts, setAccounts] = useState<IAccount[]>(); + const [selectedNetworks, setSelectedNetworks] = useState<string[]>( + networks.map((network) => network.uuid), + ); - async function start() { - const keySource = keySources.find((ks) => ks.uuid === keySourceId); - if (!activeNetwork || !keySource || !profile) return; + async function start(keySource: IKeySource) { + if (!selectedNetworks.length || !keySource || !profile) return []; if ( keySource.source !== 'HD-BIP44' && keySource.source !== 'HD-chainweaver' ) { throw new Error('Unsupported key source'); } - setDiscoveryStatus('discovering'); + await unlockKeySource(keySource); - processRef.current = accountDiscovery( - activeNetwork, + const process = accountDiscovery( + networks.filter((network) => selectedNetworks.includes(network.uuid)), keySource, profile.uuid, NUMBER_OF_KEYS_TO_DISCOVER, ) .on('key-retrieved', (data: IKeyItem) => { - setKey(data); + setKey((st) => ({ ...st, [keySource.source]: data })); }) .on('chain-result', (data: IWalletDiscoveredAccount) => { setDiscoveredAccounts((prev) => [...prev, data]); }); - const accounts = await processRef.current.executeTo('query-done'); - if (!accounts || !accounts.length) { - keySourceManager.disconnect(); - } - setDiscoveryStatus('finished'); - setAccounts(accounts); + const accounts: IAccount[] = await process.executeTo('query-done'); + setAccounts((acc = []) => [...acc, ...accounts]); + await process.execute().catch((e) => { + console.log('error', e); + }); + return accounts; } console.log('accounts', discoveredAccounts); @@ -68,70 +70,148 @@ export function AccountDiscovery() { (data) => data?.result, ) as Array<{ chainId: ChainId; + networkUUID: string; result: Exclude<IWalletDiscoveredAccount['result'], undefined>; }>; return ( - <Card> - <Stack margin="md" flexDirection={'column'} gap="md" flex={1}> - <Heading variant="h2">Account Discovery</Heading> - <Text> - You can discover the accounts that you have created with the imported - mnemonic - </Text> - <Stack gap="md"> - <Text bold color="emphasize"> - network: + <Stack style={{ width: '100vw', maxWidth: '1200px' }}> + <Card fullWidth> + <Stack + margin="md" + flexDirection={'column'} + gap="md" + flex={1} + alignItems={'flex-start'} + justifyContent={'flex-start'} + textAlign="left" + > + <Heading variant="h2">Find Your Assets</Heading> + <Text> + We will discover the accounts that you have created with the + imported mnemonic </Text> - <Text>{activeNetwork?.networkId}</Text> - </Stack> + {discoveryStatus === 'idle' && ( + <> + <Stack flexDirection={'column'}> + <Text bold color="emphasize"> + Networks: + </Text> + <Text>Please select the networks you want to query</Text> + </Stack> + <Stack gap="sm" flexDirection={'column'}> + {networks.map((network) => ( + <Checkbox + key={network.networkId} + isSelected={selectedNetworks.includes(network.uuid)} + onChange={(checked) => { + setSelectedNetworks( + checked + ? [...selectedNetworks, network.uuid] + : selectedNetworks.filter((n) => n !== network.uuid), + ); + }} + > + {network.name + ? `${network.networkId} - ${network.name}` + : network.networkId} + </Checkbox> + ))} + </Stack> - {discoveryStatus === 'idle' && ( - <Stack> - <Button - onClick={() => { - start(); - }} - > - Start Discovery - </Button> - </Stack> - )} + <Stack marginBlockStart={'md'}> + <Button + isDisabled={selectedNetworks.length === 0} + onClick={async () => { + setDiscoveryStatus('discovering'); + + const ks = await Promise.all( + keySources.map(async (keySource) => ({ + keySource, + accounts: await start(keySource), + })), + ); + const mostUsedKs = ks.reduce((acc, data) => + acc.accounts.length < data.accounts.length ? data : acc, + ); + if (mostUsedKs.accounts.length) { + keySourceRepository.patchKeySource( + mostUsedKs.keySource.uuid, + { isDefault: true }, + ); + } + setDiscoveryStatus('finished'); + }} + > + Continue + </Button> + </Stack> + </> + )} - {discoveryStatus === 'discovering' && ( - <Card fullWidth> - <Text> We are trying for first 20 keys - only K: account</Text> - <Stack gap={'sm'}> - <Text>checking</Text> - {key && ( - <> - <Text color="emphasize">#{key.index}</Text> - <Text>Address: </Text> - <Text color="emphasize" bold> - k:{key.publicKey} - </Text> - </> + {discoveryStatus === 'discovering' && + keySources.map((keySource) => ( + <Stack gap={'md'} key={keySource.uuid}> + <Text> + {keySource.source} + {key[keySource.source]?.index === undefined + ? '' + : `(${key[keySource.source]?.index})`} + </Text> + <Stack gap={'sm'}> + {key && ( + <> + <Text color="emphasize" bold> + k:{key[keySource.source]?.publicKey} + </Text> + </> + )} + </Stack> + </Stack> + ))} + {discoveryStatus === 'discovering' && ( + <Stack marginBlockStart={'lg'} flexDirection={'column'} gap={'lg'}> + <Heading variant="h4">Discoverd Funds</Heading> + {filteredDiscoveredAccounts.length === 0 ? ( + <Text>Pending</Text> + ) : ( + <Stack flex={1} flexDirection={'column'} gap={'sm'}> + {networks + .filter((n) => selectedNetworks.includes(n.uuid)) + .map((network) => ( + <Stack key={network.uuid} gap={'md'}> + <Label>{network.networkId}</Label> + <Text> + {filteredDiscoveredAccounts + .filter((d) => d.networkUUID === network.uuid) + .reduce((acc, data) => { + return new PactNumber(data.result.balance) + .plus(acc) + .toDecimal(); + }, '0')}{' '} + KDA + </Text> + </Stack> + ))} + </Stack> )} </Stack> - </Card> - )} - {discoveryStatus !== 'idle' && ( - <Stack marginBlockStart={'lg'} flexDirection={'column'} gap={'lg'}> - <Heading variant="h4">Discoverd Accounts Details</Heading> - {filteredDiscoveredAccounts.length === 0 ? ( - <Text>no accounts found yet</Text> - ) : ( + )} + {discoveryStatus === 'finished' && ( + <Stack marginBlockStart={'lg'} flexDirection={'column'} gap={'lg'}> + <Heading variant="h4">Discoverd Accounts</Heading> + {!accounts?.length && <Text>no accounts found</Text>} <Stack flexDirection={'column'} flex={1}> - {filteredDiscoveredAccounts.map((data, index) => ( + {accounts?.map((account, index) => ( <ListItem key={index}> <Stack gap={'md'} flex={1}> <Stack gap={'sm'} flex={1}> - <Text>Chain #{data.chainId}: </Text> - <Text>{data.result.account}</Text> + <Text>{account.address}</Text> </Stack> + <Stack gap={'md'}> - <Text>{data.result.guard.pred}:</Text> - {data.result.guard.keys.map((key) => ( + <Text>{account.guard.pred}:</Text> + {account.guard.keys.map((key) => ( <Stack gap={'xs'} alignItems={'center'}> <Text> <MonoKey /> @@ -139,66 +219,27 @@ export function AccountDiscovery() { <Text variant="code">{shorten(key)}</Text>{' '} </Stack> ))} - <Text>{data.result.balance} KDA</Text> + <Text>{account.overallBalance} KDA</Text> </Stack> </Stack> </ListItem> ))} </Stack> - )} - </Stack> - )} - {accounts && ( - <Stack marginBlockStart={'lg'} flexDirection={'column'} gap={'lg'}> - <Heading variant="h4">Discoverd Accounts</Heading> - {!accounts.length && <Text>no accounts found</Text>} - <Stack flexDirection={'column'} flex={1}> - {accounts.map((account, index) => ( - <ListItem key={index}> - <Stack gap={'md'} flex={1}> - <Stack gap={'sm'} flex={1}> - <Text>{account.address}</Text> - </Stack> - - <Stack gap={'md'}> - <Text>{account.guard.pred}:</Text> - {account.guard.keys.map((key) => ( - <Stack gap={'xs'} alignItems={'center'}> - <Text> - <MonoKey /> - </Text> - <Text variant="code">{shorten(key)}</Text>{' '} - </Stack> - ))} - <Text>{account.overallBalance} KDA</Text> - </Stack> - </Stack> - </ListItem> - ))} - </Stack> - <Stack alignItems={'flex-start'} gap={'sm'}> - <Button - variant="transparent" - onClick={() => { - keySourceManager.disconnect(); - navigate('/'); - }} - > - Discard - </Button> - <Button - onClick={async () => { - await processRef.current?.execute(); - keySourceManager.disconnect(); - navigate('/'); - }} - > - Save Accounts - </Button> + <Stack alignItems={'flex-start'} gap={'sm'}> + <Button + isDisabled={discoveryStatus !== 'finished'} + onClick={async () => { + keySourceManager.disconnect(); + navigate('/'); + }} + > + Go to your assets + </Button> + </Stack> </Stack> - </Stack> - )} - </Stack> - </Card> + )} + </Stack> + </Card> + </Stack> ); } diff --git a/packages/apps/dev-wallet/src/pages/keys/Components/Keys.tsx b/packages/apps/dev-wallet/src/pages/keys/Components/Keys.tsx index f9f6d663de..2933c8b30d 100644 --- a/packages/apps/dev-wallet/src/pages/keys/Components/Keys.tsx +++ b/packages/apps/dev-wallet/src/pages/keys/Components/Keys.tsx @@ -1,6 +1,7 @@ import { ListItem } from '@/Components/ListItem/ListItem.tsx'; import { useHDWallet } from '@/modules/key-source/hd-wallet/hd-wallet.tsx'; import { keySourceManager } from '@/modules/key-source/key-source-manager'; +import { keySourceRepository } from '@/modules/key-source/key-source.repository.ts'; import { WebAuthnService } from '@/modules/key-source/web-authn/webauthn'; import { useWallet } from '@/modules/wallet/wallet.hook'; import { shorten } from '@/utils/helpers.ts'; @@ -63,6 +64,8 @@ export function Keys() { await createHDWallet(profile?.uuid, type, password); }; + const defaultIndex = keySources.findIndex((ks) => ks.isDefault) || 0; + return ( <> <AddKeySourceForm @@ -113,7 +116,7 @@ export function Keys() { </Stack> <Stack flexDirection={'column'} gap="md"> - {keySources.map((keySource) => ( + {keySources.map((keySource, index) => ( <Stack key={keySource.uuid} flexDirection={'column'} @@ -130,7 +133,11 @@ export function Keys() { justifyContent={'space-between'} marginBlockEnd={'sm'} > - <Heading variant="h4">Method: {keySource.source}</Heading> + <Heading variant="h4"> + Method: {keySource.source}{' '} + {defaultIndex === index && <Text>(Default method)</Text>} + </Heading> + <Stack flexDirection={'row'} gap={'sm'}> <Button startVisual={<MonoAdd />} @@ -162,6 +169,15 @@ export function Keys() { ) : ( <ContextMenuDivider /> )} + <ContextMenuItem + label="Set as default method" + onClick={async () => { + await keySourceRepository.setAsDefault( + keySource.uuid, + profile!.uuid, + ); + }} + /> </ContextMenu> </Stack> </Stack> diff --git a/packages/apps/dev-wallet/src/pages/settings/settings.tsx b/packages/apps/dev-wallet/src/pages/settings/settings.tsx index 063e4cfaf9..69422a350c 100644 --- a/packages/apps/dev-wallet/src/pages/settings/settings.tsx +++ b/packages/apps/dev-wallet/src/pages/settings/settings.tsx @@ -128,7 +128,7 @@ export function Settings() { Set automatic backup </UiLink> <UiLink - href={`/account-discovery/${keySources[0].uuid}`} + href={`/account-discovery`} component={Link} variant="outlined" startVisual={<MonoSelectAll />} diff --git a/packages/apps/dev-wallet/src/pages/wallet-recovery/recover-from-mnemonic/recover-from-mnemonic.tsx b/packages/apps/dev-wallet/src/pages/wallet-recovery/recover-from-mnemonic/recover-from-mnemonic.tsx index 6742f07110..6c8a00e945 100644 --- a/packages/apps/dev-wallet/src/pages/wallet-recovery/recover-from-mnemonic/recover-from-mnemonic.tsx +++ b/packages/apps/dev-wallet/src/pages/wallet-recovery/recover-from-mnemonic/recover-from-mnemonic.tsx @@ -1,5 +1,4 @@ import { useGlobalState } from '@/App/providers/globalState'; -import { AuthCard } from '@/Components/AuthCard/AuthCard'; import { displayContentsClass } from '@/Components/Sidebar/style.css'; import { config } from '@/config'; import { useHDWallet } from '@/modules/key-source/hd-wallet/hd-wallet'; @@ -10,18 +9,17 @@ import { PublicKeyCredentialCreate, } from '@/utils/webAuthn'; import { - Box, Button, - Checkbox, + Card, Heading, Stack, Text, TextField, + Link as UiLink, } from '@kadena/kode-ui'; -import { useRef, useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; +import { useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; -import { noStyleLinkClass } from '../../home/style.css'; type Inputs = { mnemonic: string; @@ -39,9 +37,9 @@ export function RecoverFromMnemonic() { const { register, handleSubmit, - control, getValues, setValue, + watch, formState: { isValid, errors }, } = useForm<Inputs>({ defaultValues: { @@ -56,12 +54,26 @@ export function RecoverFromMnemonic() { fromChainweaver: false, }, }); + + useEffect(() => { + const name = getValues('profileName'); + console.log('profileList', profileList, name); + if ( + (!name || name === 'default') && + profileList && + profileList.length > 0 + ) { + setValue('profileName', `profile-${profileList.length + 1}`); + } + }, [profileList, getValues, setValue]); + const formRef = useRef<HTMLFormElement>(null); const [webAuthnCredential, setWebAuthnCredential] = useState<PublicKeyCredentialCreate>(); const { createHDWallet } = useHDWallet(); const [error, setError] = useState(''); const { createProfile, unlockProfile, activeNetwork } = useWallet(); + const [importing, setImporting] = useState(false); async function importWallet({ mnemonic, @@ -69,7 +81,6 @@ export function RecoverFromMnemonic() { password, confirmation, accentColor, - fromChainweaver, }: Inputs) { const is12Words = mnemonic.trim().split(' ').length === 12; if (!is12Words) { @@ -108,17 +119,13 @@ export function RecoverFromMnemonic() { ); // for now we only support slip10 so we just create the keySource and the first account by default for it // later we should change this flow to be more flexible - const keySource = await createHDWallet( - profile.uuid, - fromChainweaver ? 'HD-chainweaver' : 'HD-BIP44', - pass, - ); + await createHDWallet(profile.uuid, 'HD-chainweaver', pass); - setOrigin(`/account-discovery/${keySource.uuid}`); + await createHDWallet(profile.uuid, 'HD-BIP44', pass); - await unlockProfile(profile.uuid, pass); + setOrigin(`/account-discovery`); - // TODO: navigate to the backup recovery phrase page + await unlockProfile(profile.uuid, pass, true); } async function createWebAuthnCredential() { @@ -136,30 +143,32 @@ export function RecoverFromMnemonic() { console.error('Error creating credential'); } } + const profileName = watch('profileName'); return ( - <AuthCard> - <Stack gap={'lg'} flexDirection={'column'}> - <Stack> - <Link to="/wallet-recovery" className={noStyleLinkClass}> - <Button - variant="outlined" - isCompact - type="button" - onPress={() => { - throw new Error('back'); - }} - > - Back - </Button> - </Link> - </Stack> + <Card> + <Stack gap={'lg'} flexDirection={'column'} textAlign="left"> + <Stack></Stack> <form - onSubmit={handleSubmit(importWallet)} + onSubmit={handleSubmit(async (data) => { + setImporting(true); + await importWallet(data); + setImporting(false); + })} className={displayContentsClass} ref={formRef} > {step === 'import' && ( <Stack gap={'lg'} flexDirection={'column'}> + <Stack> + <UiLink + component={Link} + href="/wallet-recovery" + variant="outlined" + isCompact + > + Back + </UiLink> + </Stack> <Heading variant="h5">Import mnemonic</Heading> <Stack flexDirection="column" gap={'lg'}> <Stack flexDirection={'column'} gap={'sm'}> @@ -171,29 +180,14 @@ export function RecoverFromMnemonic() { id="phrase" {...register('mnemonic')} /> - <Box> - <Controller - name="fromChainweaver" - control={control} - render={({ field }) => { - return ( - <Checkbox - isSelected={field.value} - onChange={field.onChange} - > - Generated by Chainweaver v1/v2 - </Checkbox> - ); - }} - ></Controller> - </Box> </Stack> <TextField label="Profile name" id="name" type="text" - defaultValue={getValues('profileName')} + defaultValue={profileName} + value={profileName} {...register('profileName')} /> </Stack> @@ -285,7 +279,12 @@ export function RecoverFromMnemonic() { /> </Stack> <Stack flexDirection="column"> - <Button type="submit" isDisabled={!isValid}> + <Button + type="submit" + isDisabled={!isValid} + isLoading={importing} + loadingLabel="Importing" + > Continue </Button> </Stack> @@ -294,6 +293,6 @@ export function RecoverFromMnemonic() { </form> {error && <Text>{error}</Text>} </Stack> - </AuthCard> + </Card> ); }