From 445b2048ded83bbffac01a95aa263501bfa64188 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Wed, 23 Oct 2024 18:37:43 +0200 Subject: [PATCH 01/11] feat(dw): add welcome message + fix(dw): transfer form --- packages/apps/dev-wallet/index.html | 54 +- packages/apps/dev-wallet/src/index.ts | 19 +- .../src/modules/wallet/wallet.provider.tsx | 1 + .../components/ExpandedTransaction.tsx | 52 +- .../pages/transaction/components/TxList.tsx | 5 +- .../transaction/components/TxPipeLine.tsx | 43 +- .../pages/transaction/components/TxTile.tsx | 7 - .../pages/transaction/components/style.css.ts | 2 +- .../pages/transfer-v2/Steps/TransferForm.tsx | 715 ++++++++++-------- .../src/pages/transfer-v2/transfer-v2.tsx | 23 +- .../src/pages/transfer/style.css.ts | 4 +- 11 files changed, 539 insertions(+), 386 deletions(-) diff --git a/packages/apps/dev-wallet/index.html b/packages/apps/dev-wallet/index.html index 2965b8106f..8e8f083ecd 100644 --- a/packages/apps/dev-wallet/index.html +++ b/packages/apps/dev-wallet/index.html @@ -5,9 +5,61 @@ Kadena Dev Wallet + - +
+ +
+
+ Chainweaver Logo +

Welcome to Chainweaver v3

+

Wallet is Loading ...

+
+
+ diff --git a/packages/apps/dev-wallet/src/index.ts b/packages/apps/dev-wallet/src/index.ts index a3fe5cbbac..5b5d85fb16 100644 --- a/packages/apps/dev-wallet/src/index.ts +++ b/packages/apps/dev-wallet/src/index.ts @@ -1,7 +1,22 @@ +const getTheme = () => localStorage.getItem('theme') || 'dark'; + // the entry file for the dev wallet app // TODO: we need to do setup app here like service worker, etc -function bootstrap() { - import('./App/main').then(({ renderApp }) => renderApp()); +async function bootstrap() { + import('./App/main').then(async ({ renderApp }) => { + renderApp(); + globalThis.addEventListener('wallet-loaded', function () { + document.getElementById('welcome-message')?.remove(); + }); + }); + // show welcome message if wallet is not loaded after 200ms + setTimeout(() => { + const welcomeMessage = document.getElementById('welcome-message'); + if (welcomeMessage) { + welcomeMessage.classList.add(`theme-${getTheme()}`); + welcomeMessage.style.opacity = '1'; + } + }, 200); } bootstrap(); diff --git a/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx index 2df31f4b8f..968586c359 100644 --- a/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx +++ b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx @@ -487,6 +487,7 @@ export const WalletProvider: FC = ({ children }) => { await setProfile(profile, true); } setContextValue((ctx) => ({ ...ctx, loaded: true })); + globalThis.dispatchEvent(new CustomEvent('wallet-loaded')); }; loadSession(); }, [retrieveProfileList, session, setProfile]); diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx index 36b3c5cf8d..73d6b5cdf6 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx @@ -5,6 +5,7 @@ import { DialogFooter, DialogHeader, Heading, + Notification, Stack, } from '@kadena/kode-ui'; import yaml from 'js-yaml'; @@ -89,15 +90,19 @@ export function ExpandedTransaction({ gap={'lg'} flexDirection={'column'} style={{ - width: '400px', - maxWidth: '400px', + flexBasis: '500px', }} className={panelClass} > Tx Status - + {!statusPassed(transaction.status, 'preflight') && ( @@ -252,21 +257,36 @@ export function ExpandedTransaction({ > {!statusPassed(transaction.status, 'signed') && ( - + )} - {transaction.status === 'signed' && !sendDisabled && ( - - - - )} - - - + + {transaction.status === 'signed' && !sendDisabled && ( + + )} + {statusPassed(transaction.status, 'success') && + (!transaction.continuation?.autoContinue || + (contTx && statusPassed(contTx.status, 'success'))) && ( + + Transaction is successful + + )} + + + + diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/TxList.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/TxList.tsx index 9c79471e42..f22add3269 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxList.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxList.tsx @@ -82,6 +82,7 @@ export function TxList({ hash: tx.hash, } as ICommand) .then(async (result) => { + console.log('preflight', result); const updatedTx = { ...tx, status: 'preflight', @@ -105,8 +106,8 @@ export function TxList({ hash: tx.hash, } as ICommand) .then(async (request) => { - const updatedTx = { - ...tx, + updatedTx = { + ...updatedTx, status: 'submitted', request, } as ITransaction; diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx index 7c728cf010..290d6ee32e 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx @@ -54,9 +54,29 @@ export function TxPipeLine({ : Promise.resolve(null), [tx], ); + const showAfterCont = !contTx || variant === 'expanded'; return ( - - {!contTx && statusPassed(tx.status, 'signed') && ( + + + + {tx.continuation?.autoContinue ? 'exec' : 'hash'}:{' '} + {shorten(tx.hash, 6)} + + + + {showAfterCont && + variant === 'expanded' && + !statusPassed(tx.status, 'signed') && ( + + + + + ready for sign + + + + )} + {showAfterCont && statusPassed(tx.status, 'signed') && ( @@ -66,7 +86,20 @@ export function TxPipeLine({ )} - {!contTx && statusPassed(tx.status, 'preflight') && ( + {showAfterCont && + variant === 'expanded' && + statusPassed(tx.status, 'signed') && + !statusPassed(tx.status, 'preflight') && ( + + + + + Ready to send + + + + )} + {showAfterCont && statusPassed(tx.status, 'preflight') && ( @@ -76,7 +109,7 @@ export function TxPipeLine({ )} - {!contTx && statusPassed(tx.status, 'submitted') && ( + {showAfterCont && statusPassed(tx.status, 'submitted') && ( @@ -86,7 +119,7 @@ export function TxPipeLine({ )} - {!contTx && + {showAfterCont && statusPassed(tx.status, 'submitted') && (!('result' in tx) || !tx.result) && ( diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx index 66b69507de..9594550b50 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx @@ -39,13 +39,6 @@ export const TxTile = ({ gap={'sm'} > - - - {tx.continuation?.autoContinue ? 'exec' : 'hash'}:{' '} - {shorten(tx.hash, 6)} - - - {tx.status === 'initiated' && ( <> diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/style.css.ts b/packages/apps/dev-wallet/src/pages/transaction/components/style.css.ts index 78867f9842..c9277e4871 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/style.css.ts +++ b/packages/apps/dev-wallet/src/pages/transaction/components/style.css.ts @@ -66,7 +66,7 @@ export const pendingClass = style({ }); export const successClass = style({ - color: vars.colors.$positiveAccent, + color: vars.colors.$positiveSurface, }); export const failureClass = style({ diff --git a/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx b/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx index 5d00cf4ed1..63a7790d8d 100644 --- a/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx +++ b/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx @@ -43,6 +43,7 @@ export interface Transfer { type: 'safeTransfer' | 'normalTransfer'; ttl: string; senderAccount?: IAccount; + totalAmount: number; } export type Redistribution = { @@ -83,7 +84,6 @@ export function TransferForm({ const urlAccount = allAccounts.find((account) => account.uuid === accountId); const { control, - register, watch, setValue, reset, @@ -110,6 +110,7 @@ export function TransferForm({ gasLimit: '2500', type: 'normalTransfer', ttl: (2 * 60 * 60).toString(), + totalAmount: 0, }, }); @@ -154,6 +155,7 @@ export function TransferForm({ gasLimit: activity.data.transferData.gasLimit, type: activity.data.transferData.type, ttl: activity.data.transferData.ttl, + totalAmount: 0, }); evaluateTransactions(); } @@ -173,13 +175,14 @@ export function TransferForm({ [senderAccount?.chains], ); - const watchReceivers = watch('receivers'); + // console.log(watchReceivers); + // const watchReceivers = watch('receivers'); - console.log(watchReceivers); + // const totalAmount = watchReceivers.reduce((acc, receiver) => { + // return acc + +receiver.amount; + // }, 0); - const totalAmount = watchReceivers.reduce((acc, receiver) => { - return acc + +receiver.amount; - }, 0); + const totalAmount = watch('totalAmount'); const evaluateTransactions = useCallback(() => { const receivers = getValues('receivers'); @@ -187,6 +190,11 @@ export function TransferForm({ const gasLimit = getValues('gasLimit'); const gasPayer = getValues('gasPayer'); const selectedChain = getValues('chain'); + const totalAmount = receivers.reduce( + (acc, receiver) => acc + +receiver.amount, + 0, + ); + setValue('totalAmount', totalAmount); setRedistribution([]); setError(null); try { @@ -222,7 +230,7 @@ export function TransferForm({ return (...args: T[]) => { const result = cb(...args); clearTimeout(timer.current); - timer.current = setTimeout(evaluateTransactions, 1000); + timer.current = setTimeout(evaluateTransactions, 100); return result; }; }, @@ -389,344 +397,395 @@ export function TransferForm({ */} - - - To - - - {watchReceivers.map((rec, index) => { - const availableChains = ['', ...CHAINS].filter((ch) => { - // if the receiver is not the sender, then transfer is allowed from any chain - if (rec.address !== senderAccount?.address) { - return true; - } - // if the receiver is the sender, then the chains should be selected manually - if (!ch || !senderChain) return false; - // source and target chain should not be the same - return ch !== senderChain; - }); + { return ( - - 1 ? 'row' : 'column'} - gap="sm" - justifyContent={'flex-start'} - > - ( - - { - console.log('value', value); - field.onChange(value || ''); - setValue( - `receivers.${index}.discoveredAccounts`, - [], - ); - setValue( - `receivers.${index}.discoveryStatus`, - 'not-started', - ); - })} - onBlur={async () => { - if (!field.value) return; - if ( - getValues( - `receivers.${index}.discoveryStatus`, - ) === 'done' - ) { - return; - } - setValue( - `receivers.${index}.discoveryStatus`, - 'in-progress', - ); - const discoveredAccounts = await discoverReceiver( - rec.address, - activeNetwork!.networkId, - getValues('fungible'), - mapKeys, - ); + <> + {watchReceivers.map((rec, index) => { + const availableChains = ['', ...CHAINS].filter((ch) => { + // if the receiver is not the sender, then transfer is allowed from any chain + if (rec.address !== senderAccount?.address) { + return true; + } + // if the receiver is the sender, then the chains should be selected manually + if (!ch || !senderChain) return false; - setValue( - `receivers.${index}.discoveredAccounts`, - discoveredAccounts, - ); - setValue( - `receivers.${index}.discoveryStatus`, - 'done', - ); - }} - placeholder={`Receiver ${watchReceivers.length > 1 ? index + 1 : ''}`} - size="sm" - onSelectionChange={(key) => { - field.onChange(key); - if (key) { - const account = filteredAccounts.find( - (account) => account.address === key, - ); - if (account?.keyset) { - setValue( - `receivers.${index}.discoveredAccounts`, - [ - { - ...account, - keyset: account.keyset, - }, - ], - ); - } else { - setValue( - `receivers.${index}.discoveredAccounts`, - [], - ); - setValue( - `receivers.${index}.discoveryStatus`, - 'not-started', - ); - } - } - }} - allowsCustomValue + // source and target chain should not be the same + return ch !== senderChain; + }); + return ( + <> + + - {filteredAccounts - .filter( - (account) => - account.address !== senderAccount?.address, - // && - // !watchReceivers.some( - // (receiver, i) => - // i !== index && - // receiver.address === account.address, - // ), - ) - .map((account) => ( - + Receiver{' '} + {watchReceivers.length > 1 ? `(${index + 1})` : ''} + + + <> + {watchReceivers.length > 1 && ( + + )} + + - )} - {rec.discoveryStatus === 'done' && - rec.discoveredAccounts.length === 1 && ( - - )} - - )} - /> + - - - - ( - r.target === rec.chain, ) - .join('\n')} + ? `This will trigger balance redistribution` + : '' + } + size="sm" + placeholder="Chains" + selectedKey={field.value} + onSelectionChange={withEvaluate( + field.onChange, + )} > - - {rec.chunks.length > 0 && ( - c.chainId) - .join(' , ')} - /> + {(rec.amount ? availableChains : []).map( + (chain) => ( + + {chain ? ( + + ) : ( + + `chain ${c.chainId}: ${c.amount}`, + ) + .join('\n')} + > + + {rec.chunks.length > 0 && ( + c.chainId) + .join(' , ')} + /> + )} + + )} + + ), )} - + )} - - ), + /> + + + + {!availableChains.length && ( + + the receiver is the same as sender, therefor you + can not use automatic chain selection, please set + the both chains manually + + )} + {rec.discoveredAccounts.length > 1 && ( + + + + more than one account found with this address, + please resolve the ambiguity{' '} + + + + + )} + {rec.discoveryStatus === 'done' && + rec.discoveredAccounts.length === 0 && ( + + + + This account is not found on the blockchain + or address book. Please check the address + and try again. or add missing guard by + clicking on{' '} + + + + )} - + + + {error && ( + + Total amount ({totalAmount}) is more than the + available balance, please check your input, also you + should consider the gas fee + )} - /> - - - - {watchReceivers.length > 1 && ( - <> - - - - )} - - - {!availableChains.length && ( - - the receiver is the same as sender, therefor you can not use - automatic chain selection, please set the both chains - manually - - )} - {rec.discoveredAccounts.length > 1 && ( - - - - more than one account found with this address, please - resolve the ambiguity{' '} - - - - - )} - {rec.discoveryStatus === 'done' && - rec.discoveredAccounts.length === 0 && ( - - - - This account is not found on the blockchain or address - book. Please check the address and try again. or add - missing guard by clicking on{' '} + + {index === watchReceivers.length - 1 && ( + - - - - )} - + + )} + + ); + })} + ); - })} - - - - {error && ( - - Total amount ({totalAmount}) is more than the available balance, - please check your input, also you should consider the gas fee - - )} - + }} + /> + - Normal transfer{' '} + Normal transfer Sign by sender ) as any @@ -864,7 +923,7 @@ export function TransferForm({ { ( - Sate transfer{' '} + Safe transfer Sign by both sender and receiver diff --git a/packages/apps/dev-wallet/src/pages/transfer-v2/transfer-v2.tsx b/packages/apps/dev-wallet/src/pages/transfer-v2/transfer-v2.tsx index 2f45311d9e..9e50c268fd 100644 --- a/packages/apps/dev-wallet/src/pages/transfer-v2/transfer-v2.tsx +++ b/packages/apps/dev-wallet/src/pages/transfer-v2/transfer-v2.tsx @@ -3,18 +3,7 @@ import { useWallet } from '@/modules/wallet/wallet.hook'; import { ISigner } from '@kadena/client'; import { MonoSwapHoriz } from '@kadena/kode-icons/system'; -import { - Button, - Dialog, - DialogFooter, - DialogHeader, - Divider, - Heading, - Stack, - Step, - Stepper, - Text, -} from '@kadena/kode-ui'; +import { Divider, Heading, Stack, Step, Stepper, Text } from '@kadena/kode-ui'; import { useCallback, useEffect, useState } from 'react'; import { activityRepository } from '@/modules/activity/activity.repository'; @@ -26,7 +15,6 @@ import { useSearchParams } from 'react-router-dom'; import { ReviewTransaction } from '../transaction/components/ReviewTransaction'; import { TxList } from '../transaction/components/TxList'; import { statusPassed } from '../transaction/components/TxPipeLine'; -import { Result } from './Steps/Result'; import { Redistribution, TrG, @@ -252,14 +240,6 @@ export function TransferV2() { return ( - {step === 'result' && ( - - Process is done! - - - - - )} } @@ -333,7 +313,6 @@ export function TransferV2() { )} {(step === 'sign' || step === 'result') && renderSignStep()} - {step === 'summary' && } ); } diff --git a/packages/apps/dev-wallet/src/pages/transfer/style.css.ts b/packages/apps/dev-wallet/src/pages/transfer/style.css.ts index cdfd271eca..aa555bf721 100644 --- a/packages/apps/dev-wallet/src/pages/transfer/style.css.ts +++ b/packages/apps/dev-wallet/src/pages/transfer/style.css.ts @@ -1,8 +1,8 @@ -import { tokens } from '@kadena/kode-ui/styles'; +import { tokens, vars } from '@kadena/kode-ui/styles'; import { style } from '@vanilla-extract/css'; export const linkClass = style({ - color: tokens.kda.foundation.color.palette.aqua.n50, + color: vars.colors.$positiveSurface, background: 'transparent', textAlign: 'left', border: 'none', From 721e397977b1faf393da9003cb18623ee041554a Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Wed, 23 Oct 2024 19:06:34 +0200 Subject: [PATCH 02/11] fix(dw): welcome message theme --- packages/apps/dev-wallet/index.html | 41 +---------------- packages/apps/dev-wallet/public/boot.css | 45 +++++++++++++++++++ packages/apps/dev-wallet/src/index.ts | 10 ++++- .../pages/transaction/components/TxTile.tsx | 6 +-- 4 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 packages/apps/dev-wallet/public/boot.css diff --git a/packages/apps/dev-wallet/index.html b/packages/apps/dev-wallet/index.html index 8e8f083ecd..81a5757de9 100644 --- a/packages/apps/dev-wallet/index.html +++ b/packages/apps/dev-wallet/index.html @@ -5,46 +5,9 @@ Kadena Dev Wallet - + - +
diff --git a/packages/apps/dev-wallet/public/boot.css b/packages/apps/dev-wallet/public/boot.css new file mode 100644 index 0000000000..9d8009d781 --- /dev/null +++ b/packages/apps/dev-wallet/public/boot.css @@ -0,0 +1,45 @@ +.welcome-wrapper { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100vh; + background: #050e1b; + opacity: 0; +} +.welcome-message-content { + display: flex; + padding: 40px; + justify-content: center; + align-items: center; + flex-direction: column; + background: #14202b; + color: #ffffffb3; + font-family: 'Times New Roman', Times, serif; + font-smooth: antialiased; + gap: 10px; + line-height: 3rem; + text-align: center; + -webkit-font-smoothing: antialiased; +} +body.boot.boot-theme-light .welcome-wrapper { + background: #f5f5f5; +} + +body.boot.boot-theme-light .welcome-wrapper .welcome-message-content { + background: #e7e8e9; + color: #494949; +} +body.boot { + margin: 0; + background: #050e1b; +} +body.boot.boot-theme-light { + background: #f5f5f5; + color: #050e1b; +} diff --git a/packages/apps/dev-wallet/src/index.ts b/packages/apps/dev-wallet/src/index.ts index 5b5d85fb16..235362072d 100644 --- a/packages/apps/dev-wallet/src/index.ts +++ b/packages/apps/dev-wallet/src/index.ts @@ -1,19 +1,27 @@ const getTheme = () => localStorage.getItem('theme') || 'dark'; +const addBootTheme = () => { + document.body.classList.add(`boot-theme-${getTheme()}`); +}; +const removeBootTheme = () => { + document.body.classList.remove(`boot`); + document.body.classList.remove(`boot-theme-${getTheme()}`); +}; // the entry file for the dev wallet app // TODO: we need to do setup app here like service worker, etc async function bootstrap() { + addBootTheme(); import('./App/main').then(async ({ renderApp }) => { renderApp(); globalThis.addEventListener('wallet-loaded', function () { document.getElementById('welcome-message')?.remove(); + removeBootTheme(); }); }); // show welcome message if wallet is not loaded after 200ms setTimeout(() => { const welcomeMessage = document.getElementById('welcome-message'); if (welcomeMessage) { - welcomeMessage.classList.add(`theme-${getTheme()}`); welcomeMessage.style.opacity = '1'; } }, 200); diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx index 9594550b50..6e7757d0b0 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxTile.tsx @@ -1,21 +1,17 @@ import { ITransaction } from '@/modules/transaction/transaction.repository'; -import { shorten } from '@/utils/helpers'; import { - MonoBrightness1, MonoOpenInFull, MonoSignature, MonoViewInAr, } from '@kadena/kode-icons/system'; import { Button, Stack, Text } from '@kadena/kode-ui'; -import classNames from 'classnames'; - import { IPactCommand } from '@kadena/client'; import { Value } from './helpers'; import { codeClass, txTileClass, txTileContentClass } from './style.css'; -import { getStatusClass, TxPipeLine } from './TxPipeLine'; +import { TxPipeLine } from './TxPipeLine'; export const TxTile = ({ tx, From a1fa9139722bb2d218b8ef595d5e39e71f04748a Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 24 Oct 2024 10:51:53 +0200 Subject: [PATCH 03/11] fix(dw): account discovery --- .../src/modules/account/account.service.ts | 47 ++- .../account-discovery/account-dsicovery.tsx | 205 ++++++++--- .../dev-wallet/src/pages/home/style.css.ts | 1 + .../src/pages/import-wallet/import-wallet.tsx | 338 ++++++++++++++---- .../select-profile/select-profile.css.ts | 13 +- 5 files changed, 453 insertions(+), 151 deletions(-) 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 924cd538d4..909cec1fbb 100644 --- a/packages/apps/dev-wallet/src/modules/account/account.service.ts +++ b/packages/apps/dev-wallet/src/modules/account/account.service.ts @@ -116,14 +116,16 @@ export const accountDiscovery = ( const accounts: IAccount[] = []; const keysets: IKeySet[] = []; const usedKeys: IKeyItem[] = []; + const saveCallbacks: Array<() => Promise> = []; for (let i = 0; i < numberOfKeys; i++) { const key = await keySourceService.getPublicKey(keySource, i); if (!key) { return; } await emit('key-retrieved')(key); + const principal = `k:${key.publicKey}`; const chainResult = (await discoverAccount( - `k:${key.publicKey}`, + principal, network.networkId, undefined, contract, @@ -134,10 +136,14 @@ export const accountDiscovery = ( .execute()) as IDiscoveredAccount[]; if (chainResult.filter(({ result }) => Boolean(result)).length > 0) { + const availableKeyset = await accountRepository.getKeysetByPrincipal( + principal, + profileId, + ); usedKeys.push(key); - const keyset: IKeySet = { + const keyset: IKeySet = availableKeyset || { uuid: crypto.randomUUID(), - principal: `k:${key.publicKey}`, + principal, profileId, guard: { keys: [key.publicKey], @@ -145,13 +151,16 @@ export const accountDiscovery = ( }, alias: '', }; - keysets.push(keyset); - accounts.push({ + if (!availableKeyset) { + keysets.push(keyset); + } + const account: IAccount = { uuid: crypto.randomUUID(), profileId, networkUUID: network.uuid, contract, keysetId: keyset.uuid, + keyset, address: `k:${key.publicKey}`, chains: chainResult .filter(({ result }) => Boolean(result)) @@ -166,26 +175,26 @@ export const accountDiscovery = ( : 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); - // store keys; key creation needs to be in sequence so I used a for loop instead of Promise.all - for (const key of usedKeys) { - await keySourceService.createKey(keySource.uuid, key.index as number); - } - - // store accounts - await Promise.all([ - ...accounts.map(async (account) => - accountRepository.addAccount(account), - ), - ...keysets.map(async (keyset) => - accountRepository.addKeyset(keyset).catch(console.log), - ), - ]); + await Promise.all(saveCallbacks.map((cb) => cb().catch(console.error))); keySourceService.clearCache(); await emit('accounts-saved')(accounts); 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 6b9513c864..35d5c4d977 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,27 +1,35 @@ import { IKeyItem } from '@/modules/wallet/wallet.repository'; -import { Box, Heading, Text } from '@kadena/kode-ui'; -import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { Box, Button, Card, Heading, Stack, Text } from '@kadena/kode-ui'; +import { useRef, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { ListItem } from '@/Components/ListItem/ListItem'; +import { IAccount } from '@/modules/account/account.repository'; import { IDiscoveredAccount, accountDiscovery, } from '@/modules/account/account.service'; import { keySourceManager } from '@/modules/key-source/key-source-manager'; import { useWallet } from '@/modules/wallet/wallet.hook'; +import { shorten } from '@/utils/helpers'; +import { ChainId } from '@kadena/client'; +import { MonoKey } from '@kadena/kode-icons/system'; const NUMBER_OF_KEYS_TO_DISCOVER = 20; export function AccountDiscovery() { + const navigate = useNavigate(); + const processRef = useRef>(); const { profile, keySources, unlockKeySource, activeNetwork } = useWallet(); const { keySourceId } = useParams(); const [key, setKey] = useState(); const [discoveryStatus, setDiscoveryStatus] = useState< 'idle' | 'discovering' | 'finished' >('idle'); - const [accounts, setAccounts] = useState< + const [discoveredAccounts, setDiscoveredAccounts] = useState< Array >([]); + const [accounts, setAccounts] = useState(); async function start() { const keySource = keySources.find((ks) => ks.uuid === keySourceId); @@ -34,7 +42,7 @@ export function AccountDiscovery() { } setDiscoveryStatus('discovering'); await unlockKeySource(keySource); - await accountDiscovery( + processRef.current = accountDiscovery( activeNetwork, keySource, profile.uuid, @@ -44,73 +52,152 @@ export function AccountDiscovery() { setKey(data); }) .on('chain-result', (data: IDiscoveredAccount) => { - setAccounts((prev) => [...prev, data]); - }) - .execute(); - keySourceManager.disconnect(); + setDiscoveredAccounts((prev) => [...prev, data]); + }); + const accounts = await processRef.current.executeTo('query-done'); + if (!accounts || !accounts.length) { + keySourceManager.disconnect(); + } setDiscoveryStatus('finished'); + setAccounts(accounts); } - return ( - <> - - Account Discovery + console.log('accounts', discoveredAccounts); + const filteredDiscoveredAccounts = discoveredAccounts.filter( + (data) => data?.result, + ) as Array<{ + chainId: ChainId; + result: Exclude; + }>; - network: {activeNetwork?.networkId} + return ( + + + Account Discovery + + You can discover the accounts that you have created with the imported + mnemonic + + + + network: + + {activeNetwork?.networkId} + {discoveryStatus === 'idle' && ( - - )} - - {discoveryStatus === 'finished' && ( - - Discovery finished - + + + )} {discoveryStatus === 'discovering' && ( - <> + We are trying for first 20 keys - only K: account - - - checking{' '} - {key && ( - - {' '} - #{key.index} Address: `k:{key.publicKey} ` + + checking + {key && ( + <> + #{key.index} + Address: + + k:{key.publicKey} - )} - - - + + )} + + )} - Discoverd Accounts - {accounts.length === 0 ? ( - no accounts found yet - ) : ( -
    - {accounts.map( - (data, index) => - data?.result && ( -
  • - - account: {data.result.account} -
    keys:[{data.result.guard.keys.join(',')}] -
    chainId: {data.chainId} -
    balance: {data.result.balance} -
    -
  • - ), + {discoveryStatus !== 'idle' && ( + + Discoverd Accounts Details + {filteredDiscoveredAccounts.length === 0 ? ( + no accounts found yet + ) : ( + + {filteredDiscoveredAccounts.map((data, index) => ( + + + + Chain #{data.chainId}: + {data.result.account} + + + {data.result.guard.pred}: + {data.result.guard.keys.map((key) => ( + + + + + {shorten(key)}{' '} + + ))} + {data.result.balance} KDA + + + + ))} + )} -
+ + )} + {accounts && ( + + Discoverd Accounts + {!accounts.length && no accounts found} + + {accounts.map((account, index) => ( + + + + {account.address} + + + + {account.keyset?.guard.pred}: + {account.keyset?.guard.keys.map((key) => ( + + + + + {shorten(key)}{' '} + + ))} + {account.overallBalance} KDA + + + + ))} + + + + + + )} -
- + + ); } diff --git a/packages/apps/dev-wallet/src/pages/home/style.css.ts b/packages/apps/dev-wallet/src/pages/home/style.css.ts index b081cd83bb..cea0ce32ba 100644 --- a/packages/apps/dev-wallet/src/pages/home/style.css.ts +++ b/packages/apps/dev-wallet/src/pages/home/style.css.ts @@ -12,6 +12,7 @@ export const panelClass = style([ }), { background: tokens.kda.foundation.color.background.surface.default, + textAlign: 'start', }, ]); diff --git a/packages/apps/dev-wallet/src/pages/import-wallet/import-wallet.tsx b/packages/apps/dev-wallet/src/pages/import-wallet/import-wallet.tsx index 626950c48c..4a7022fef0 100644 --- a/packages/apps/dev-wallet/src/pages/import-wallet/import-wallet.tsx +++ b/packages/apps/dev-wallet/src/pages/import-wallet/import-wallet.tsx @@ -1,21 +1,34 @@ +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'; import { useWallet } from '@/modules/wallet/wallet.hook'; -import { Box, Button, Heading, Stack, Text, TextField } from '@kadena/kode-ui'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; +import { + createCredential, + extractPublicKeyHex, + PublicKeyCredentialCreate, +} from '@/utils/webAuthn'; +import { + Box, + Button, + Checkbox, + Heading, + Stack, + Text, + TextField, +} from '@kadena/kode-ui'; +import { useRef, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { Link } from 'react-router-dom'; +import { noStyleLinkClass } from '../home/style.css'; type Inputs = { - phrase: string; - name: string; + mnemonic: string; + profileName: string; password: string; + confirmation: string; fromChainweaver: boolean; -}; - -const defaultValues: Inputs = { - phrase: '', - name: '', - password: '', - fromChainweaver: false, + accentColor: string; }; export function ImportWallet({ @@ -23,73 +36,266 @@ export function ImportWallet({ }: { setOrigin: (pathname: string) => void; }) { - const { register, handleSubmit } = useForm({ defaultValues }); + const [step, setStep] = useState<'import' | 'set-password'>('import'); + const { profileList } = useWallet(); + const { + register, + handleSubmit, + control, + getValues, + setValue, + formState: { isValid, errors }, + } = useForm({ + defaultValues: { + mnemonic: '', + profileName: + profileList.length === 0 + ? 'default' + : `profile-${profileList.length + 1}`, + accentColor: + config.colorList[profileList.length % config.colorList.length], + password: '', + fromChainweaver: false, + }, + }); + const formRef = useRef(null); + const [webAuthnCredential, setWebAuthnCredential] = + useState(); const { createHDWallet } = useHDWallet(); const [error, setError] = useState(''); - const { createProfile, unlockProfile } = useWallet(); - async function confirm({ phrase, password, name, fromChainweaver }: Inputs) { - const is12Words = phrase.trim().split(' ').length === 12; + const { createProfile, unlockProfile, activeNetwork } = useWallet(); + + async function importWallet({ + mnemonic, + profileName, + password, + confirmation, + accentColor, + fromChainweaver, + }: Inputs) { + const is12Words = mnemonic.trim().split(' ').length === 12; if (!is12Words) { setError('enter 12 words'); return; } - try { - const profile = await createProfile( - name, - password, - undefined, - { - authMode: 'PASSWORD', - rememberPassword: 'session', - }, - phrase, - ); - const keySource = await createHDWallet( - profile.uuid, - fromChainweaver ? 'HD-chainweaver' : 'HD-BIP44', - password, - ); - setOrigin(`/account-discovery/${keySource.uuid}`); - await unlockProfile(profile.uuid, password); - } catch (e) { - setError((e as Error).message); + let pass = password; + if (!activeNetwork) { + return; } - } - return ( - <> - - Import mnemonic -
- - - + if (webAuthnCredential && password === 'WEB_AUTHN_PROTECTED') { + const pk = webAuthnCredential.response.getPublicKey(); + if (!pk) { + throw new Error('Public key not found'); + } + pass = extractPublicKeyHex(pk); + } else if (pass !== confirmation) { + setError('passwords do not match'); + return; + } + const profile = await createProfile( + profileName, + pass, + accentColor, + webAuthnCredential + ? { + authMode: 'WEB_AUTHN', + webAuthnCredential: webAuthnCredential.rawId, + rememberPassword: 'session', + } + : { + authMode: 'PASSWORD', + rememberPassword: 'session', + }, + mnemonic, + ); + // 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, + ); - - - - + setOrigin(`/account-discovery/${keySource.uuid}`); - Profile + await unlockProfile(profile.uuid, pass); - - + // TODO: navigate to the backup recovery phrase page + } - - + async function createWebAuthnCredential() { + const result = await createCredential(); + if (result && result.credential) { + // const pk = result.credential.response.getPublicKey(); + // setPublicKey(pk ? hex(new Uint8Array(extractPublicKeyBytes(pk))) : ''); + setWebAuthnCredential(result.credential); + setValue('password', 'WEB_AUTHN_PROTECTED'); + setValue('confirmation', 'WEB_AUTHN_PROTECTED'); + setTimeout(() => { + formRef.current?.requestSubmit(); + }, 200); + } else { + console.error('Error creating credential'); + } + } + return ( + + + + + + + + + {step === 'import' && ( + + Import mnemonic + + + + Enter the 12 word recovery phrase + + + + { + return ( + + Import from Chainweaver v1/v2 + + ); + }} + > + + - - + + + + + Your system supports{' '} + + WebAuthn + {' '} + so you can create a more secure and more convenient + password-less profile! + + + + + + + + )} + {step === 'set-password' && ( + + + + + Choose a password + + + Carefully select your password as this will be your main + security of your wallet + + + + + { + return ( + getValues('password') === value || + 'Passwords do not match' + ); + }, + })} + isInvalid={!isValid && !!errors.confirmation} + errorMessage={errors.confirmation?.message} + /> + + + + + + )} {error && {error}} -
- + + ); } diff --git a/packages/apps/dev-wallet/src/pages/select-profile/select-profile.css.ts b/packages/apps/dev-wallet/src/pages/select-profile/select-profile.css.ts index 44bb40cf80..89ed1a7898 100644 --- a/packages/apps/dev-wallet/src/pages/select-profile/select-profile.css.ts +++ b/packages/apps/dev-wallet/src/pages/select-profile/select-profile.css.ts @@ -2,9 +2,10 @@ import { cardBackgroundColorHover, cardColor, cardColorHover, + cardHoverColor, linkBlockColor, } from '@/utils/color.ts'; -import { atoms, tokens } from '@kadena/kode-ui/styles'; +import { atoms, tokens, vars } from '@kadena/kode-ui/styles'; import { style } from '@vanilla-extract/css'; export const titleClass = style([ @@ -31,12 +32,12 @@ export const cardClass = style([ }), { cursor: 'pointer', - backgroundColor: cardColor, - border: `1px solid ${cardColor}`, + backgroundColor: tokens.kda.foundation.color.background.base['@focus'], + border: `solid 1px ${tokens.kda.foundation.color.neutral.n5}`, selectors: { [`&:hover`]: { - backgroundColor: cardBackgroundColorHover, - borderColor: cardColorHover, + backgroundColor: tokens.kda.foundation.color.background.base['@hover'], + border: `solid 1px ${vars.colors.$borderSubtle}`, }, }, }, @@ -91,5 +92,3 @@ export const linkClass = style([ }, }, ]); - - From 3e017745451baba7df591893c1741e0b8b2de869 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 24 Oct 2024 14:12:52 +0200 Subject: [PATCH 04/11] feat(dw): create account --- .../pages/create-account/create-account.tsx | 489 +++++++++++------- .../src/pages/create-account/style.css.ts | 4 + 2 files changed, 301 insertions(+), 192 deletions(-) diff --git a/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx b/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx index 0622061735..2e4cfc36b4 100644 --- a/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx +++ b/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx @@ -13,7 +13,11 @@ import { Keyset } from '@/Components/Keyset/Keyset.tsx'; import { MonoAdd } from '@kadena/kode-icons/system'; import { Button, + Card, + Divider, Heading, + Radio, + RadioGroup, Select, SelectItem, Stack, @@ -22,16 +26,27 @@ import { Text, TextField, } from '@kadena/kode-ui'; +import classNames from 'classnames'; import { useState } from 'react'; -import { Link, Navigate, useSearchParams } from 'react-router-dom'; +import { Navigate, useSearchParams } from 'react-router-dom'; import { panelClass } from '../home/style.css.ts'; import { CreateKeySetDialog } from '../keys/Components/CreateKeySetDialog.tsx'; -import { linkClass } from '../transfer/style.css.ts'; import { buttonListClass } from './style.css.ts'; +type AccountType = 'k:account' | 'w:account' | 'r:account'; export function CreateAccount() { - const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); + const [selectedItem, setSelectedItem] = useState< + | { + item: IKeySet; + type: 'keyset'; + } + | { + item: IKeyItem; + type: 'key'; + } + >(); const [created, setCreated] = useState(null); + const [accountType, setAccountType] = useState('k:account'); const [searchParams] = useSearchParams(); const urlContract = searchParams.get('contract'); const [contract, setContract] = useState(urlContract); @@ -44,7 +59,6 @@ export function CreateAccount() { fungibles, keysets, accounts, - askForPassword, } = useWallet(); const filteredAccounts = accounts.filter( @@ -130,217 +144,308 @@ export function CreateAccount() { return ; } + const filteredKeySources = keySources.filter( + (ks) => + (accountType === 'k:account' && ks.source !== 'web-authn') || + (accountType === 'w:account' && ks.source === 'web-authn'), + ); + + const filteredKeysets = keysets.filter( + (keyset) => + keyset.guard.keys.length >= 2 && !usedKeysets.includes(keyset.uuid), + ); + return ( - - Create Account + - - setAlias(e.target.value)} /> - { - - - {!showAdvancedOptions && ( - - )} - - } - {contract && showAdvancedOptions && ( - - - - Create or use a key - - } - > - - {keySources.map((keySource) => ( - - - {keySource.source} - - - - Select on of the following keys to create account - - - - {keySource.keys.map((key) => { - const disabled = usedKeys.includes(key.publicKey); - return ( - createAccountByKey(key)} - disabled={disabled} - title={disabled ? 'Already used' : 'Use this key'} - > - - - - - - {disabled ? 'Already used' : 'Use'} - - - - ); - })} - - - ))} - - Key Management - - - - - Create or use a keyset - - } - > - {showCreateKeyset && ( - setShowCreateKeyset(false)} - onDone={(keyset) => { - createAccountByKeyset(keyset); - }} - /> - )} + + k: account + + This account will be guarded by a single key, this is the most + relevant way if you are creating a personal account; Also this + type of account supposed widely by kadena ecosystem. + + + Tip: The account guard is immutable and can't be changed. + + + + + w: account + + This account will be guarded by a keyset or (single key when + key type is web-authn). This will offers a way to create + shared account or more secure account guarded by several keys. + + + Tip: The account guard is immutable and can't be changed. + + + + + + r: account (not supposed yet) + - - Key Sets - - - - Select on of the following keysets to create account + + This account will be guarded by a keyset reference. this is + more suitable if you want to change the guard later without + creating a new account. Creating this type of account + requires namespace generation so its not supported yet with + most of the wallets - - - {keysets - .filter(({ guard }) => guard.keys.length >= 2) - .map((keyset) => { - const disabled = usedKeysets.includes(keyset.uuid); - return ( - createAccountByKeyset(keyset)} - disabled={disabled} - title={ - disabled - ? 'Already used' - : 'Use this keyset for Account' - } - > + + Tip: Since the guard can be changed, this type of account is + more flexible. + + + + + + + {contract && ( + + + {...[ + ['k:account', 'w:account'].includes(accountType) && + filteredKeySources.length && ( + + Create or use a key + + } + > + + {filteredKeySources.map((keySource) => { + const filteredKeys = keySource.keys.filter( + (key) => !usedKeys.includes(key.publicKey), + ); + return ( - - {} + + + {keySource.source} + + + + + {filteredKeys.length === 0 + ? 'There is not key available to select, create a new one' + : 'Select on of the following keys to create account'} + + + + {keySource.keys + .filter( + (key) => + !usedKeys.includes(key.publicKey), + ) + .map((key) => { + return ( + + setSelectedItem({ + item: key, + type: 'key', + }) + } + > + + + + + + + ); + })} - - ); - })} - - - - - - Keyset Management - - - - - - )} + ); + })} + + + ), + accountType === 'w:account' && ( + + Create or use a keyset + + } + > + {showCreateKeyset && ( + setShowCreateKeyset(false)} + onDone={(keyset) => { + setSelectedItem({ + item: keyset, + type: 'keyset', + }); + }} + /> + )} + + + Key Sets + + + + {filteredKeysets.length === 0 + ? 'There is not keyset available to select, create a new one' + : 'Select on of the following keysets to create account'} + + + + {filteredKeysets.map((keyset) => { + return ( + + setSelectedItem({ + item: keyset, + type: 'keyset', + }) + } + > + + + {} + + + + ); + })} + + + + + ), + ].filter(Boolean) as any} + + + )} + + + + + - + ); } function ButtonItem({ children, + selected = false, ...props }: React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement ->) { +> & { selected?: boolean }) { return ( - ); diff --git a/packages/apps/dev-wallet/src/pages/create-account/style.css.ts b/packages/apps/dev-wallet/src/pages/create-account/style.css.ts index d750e3b05f..ae3d16f2b4 100644 --- a/packages/apps/dev-wallet/src/pages/create-account/style.css.ts +++ b/packages/apps/dev-wallet/src/pages/create-account/style.css.ts @@ -65,6 +65,10 @@ export const buttonListClass = style([ opacity: 0.5, cursor: 'not-allowed', }, + '&.selected': { + background: cardHoverColor, + border: `1px solid ${tokens.kda.foundation.color.border.base['@active']}`, + }, }, }, ]); From ff70e9b57fd003f3a15981944065fdc2eb9c0f14 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 24 Oct 2024 14:32:44 +0200 Subject: [PATCH 05/11] feat(dw): create account --- .../pages/create-account/create-account.tsx | 331 +++++++++--------- .../src/pages/create-account/style.css.ts | 2 +- 2 files changed, 162 insertions(+), 171 deletions(-) diff --git a/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx b/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx index 2e4cfc36b4..1990607923 100644 --- a/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx +++ b/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx @@ -177,7 +177,10 @@ export function CreateAccount() { setAccountType(type as AccountType)} + onChange={(type) => { + setSelectedItem(undefined); + setAccountType(type as AccountType); + }} > {contract && ( - - - {...[ - ['k:account', 'w:account'].includes(accountType) && - filteredKeySources.length && ( - - Create or use a key - - } + + {accountType === 'w:account' && ( + + {showCreateKeyset && ( + setShowCreateKeyset(false)} + onDone={(keyset) => { + setSelectedItem({ + item: keyset, + type: 'keyset', + }); + }} + /> + )} + Keysets + + + Create or use a keyset + + + + {filteredKeysets.length === 0 + ? 'There is not keyset available to select, create a new one' + : 'Select on of the following keysets to create account'} + + + + {filteredKeysets.map((keyset) => { + return ( + + setSelectedItem({ + item: keyset, + type: 'keyset', + }) + } + > - - - {keySource.source} - - - - - {filteredKeys.length === 0 - ? 'There is not key available to select, create a new one' - : 'Select on of the following keys to create account'} - - - - {keySource.keys - .filter( - (key) => - !usedKeys.includes(key.publicKey), - ) - .map((key) => { - return ( - - setSelectedItem({ - item: key, - type: 'key', - }) - } - > - - - - - - - ); - })} + + {} - ); - })} - - - ), - accountType === 'w:account' && ( - - Create or use a keyset - - } - > - {showCreateKeyset && ( - setShowCreateKeyset(false)} - onDone={(keyset) => { - setSelectedItem({ - item: keyset, - type: 'keyset', - }); - }} - /> - )} - - - Key Sets - - - - {filteredKeysets.length === 0 - ? 'There is not keyset available to select, create a new one' - : 'Select on of the following keysets to create account'} - - - - {filteredKeysets.map((keyset) => { - return ( - - setSelectedItem({ - item: keyset, - type: 'keyset', - }) - } - > - - - {} - - - - ); - })} + + {keySource.source} + + + + {filteredKeys.length === 0 + ? 'There is not key available to select, create a new one' + : 'Select on of the following keys to create account'} + + + + {keySource.keys + .filter( + (key) => !usedKeys.includes(key.publicKey), + ) + .map((key) => { + return ( + + setSelectedItem({ + item: key, + type: 'key', + }) + } + > + + + + + + + ); + })} + - - - - ), - ].filter(Boolean) as any} - + ); + })} + + + )} )} @@ -425,7 +413,10 @@ export function CreateAccount() { } }} > - Create Account + <> + Create {accountType}{' '} + {selectedItem?.type ? `with ${selectedItem.type}` : ''} + diff --git a/packages/apps/dev-wallet/src/pages/create-account/style.css.ts b/packages/apps/dev-wallet/src/pages/create-account/style.css.ts index ae3d16f2b4..90b8af4e8c 100644 --- a/packages/apps/dev-wallet/src/pages/create-account/style.css.ts +++ b/packages/apps/dev-wallet/src/pages/create-account/style.css.ts @@ -52,7 +52,7 @@ export const buttonListClass = style([ textDecoration: 'none', }), { - border: 'none', + border: 'solid 1px transparent', cursor: 'pointer', flex: 1, minHeight: '50px', From 10d33a23fd6b6788c242bd1910658a817406e125 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 24 Oct 2024 14:35:49 +0200 Subject: [PATCH 06/11] fix: style --- packages/apps/dev-wallet/src/Components/Keyset/Keyset.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apps/dev-wallet/src/Components/Keyset/Keyset.tsx b/packages/apps/dev-wallet/src/Components/Keyset/Keyset.tsx index 156f35e7c0..1712521b64 100644 --- a/packages/apps/dev-wallet/src/Components/Keyset/Keyset.tsx +++ b/packages/apps/dev-wallet/src/Components/Keyset/Keyset.tsx @@ -5,7 +5,7 @@ import { Stack, Text } from '@kadena/kode-ui'; export function Keyset({ keySet }: { keySet: IKeySet }) { return ( - + {keySet.alias} {keySet.principal} From 5a1430e758bc4cda8d917449c1e86a519b0ea45d Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 24 Oct 2024 19:45:15 +0200 Subject: [PATCH 07/11] feat(dw): transaction page --- packages/apps/dev-wallet/public/boot.css | 2 +- .../src/Components/CopyButton/CopyButton.tsx | 15 + .../transaction/components/CommandView.tsx | 121 +++++++ .../components/ExpandedTransaction.tsx | 310 ++++++++---------- .../pages/transaction/components/Signers.tsx | 1 + .../pages/transaction/components/TxList.tsx | 92 ++++-- .../transaction/components/TxPipeLine.tsx | 57 +++- .../pages/transfer-v2/Steps/TransferForm.tsx | 37 ++- .../src/pages/transfer-v2/Steps/style.css.ts | 9 + .../src/pages/transfer-v2/transfer-v2.tsx | 16 +- 10 files changed, 429 insertions(+), 231 deletions(-) create mode 100644 packages/apps/dev-wallet/src/Components/CopyButton/CopyButton.tsx create mode 100644 packages/apps/dev-wallet/src/pages/transaction/components/CommandView.tsx create mode 100644 packages/apps/dev-wallet/src/pages/transfer-v2/Steps/style.css.ts diff --git a/packages/apps/dev-wallet/public/boot.css b/packages/apps/dev-wallet/public/boot.css index 9d8009d781..186d3aa206 100644 --- a/packages/apps/dev-wallet/public/boot.css +++ b/packages/apps/dev-wallet/public/boot.css @@ -20,7 +20,7 @@ flex-direction: column; background: #14202b; color: #ffffffb3; - font-family: 'Times New Roman', Times, serif; + font-family: 'Roboto', sans-serif; font-smooth: antialiased; gap: 10px; line-height: 3rem; diff --git a/packages/apps/dev-wallet/src/Components/CopyButton/CopyButton.tsx b/packages/apps/dev-wallet/src/Components/CopyButton/CopyButton.tsx new file mode 100644 index 0000000000..c23ef12e33 --- /dev/null +++ b/packages/apps/dev-wallet/src/Components/CopyButton/CopyButton.tsx @@ -0,0 +1,15 @@ +import { MonoContentCopy } from '@kadena/kode-icons/system'; +import { Button } from '@kadena/kode-ui'; + +export const CopyButton = ({ data }: { data: string | object }) => ( + +); diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/CommandView.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/CommandView.tsx new file mode 100644 index 0000000000..0afdc2c372 --- /dev/null +++ b/packages/apps/dev-wallet/src/pages/transaction/components/CommandView.tsx @@ -0,0 +1,121 @@ +import { CopyButton } from '@/Components/CopyButton/CopyButton'; +import { ITransaction } from '@/modules/transaction/transaction.repository'; +import { IPactCommand } from '@kadena/client'; +import { Heading, Stack } from '@kadena/kode-ui'; +import { useMemo } from 'react'; +import { Label, Value } from './helpers'; +import { Signers } from './Signers'; +import { cardClass, codeClass } from './style.css'; + +export function CommandView({ + transaction, + onSign, +}: { + transaction: ITransaction; + onSign: (sig: ITransaction['sigs']) => void; +}) { + const command: IPactCommand = useMemo( + () => JSON.parse(transaction.cmd), + [transaction.cmd], + ); + return ( + + + hash (request-key) + + {transaction.hash} + + {'exec' in command.payload && ( + <> + + Code + {command.payload.exec.code} + + {Object.keys(command.payload.exec.data).length > 0 && ( + + Data +
+                {JSON.stringify(command.payload.exec.data, null, 2)}
+              
+
+ )} + + )} + {'cont' in command.payload && ( + <> + + Continuation + + {command.payload.cont.pactId}- step( + {command.payload.cont.step}) + + + {Object.keys(command.payload.cont.data || {}).length > 0 && ( + + Data +
+                {JSON.stringify(command.payload.cont.data, null, 2)}
+              
+
+ )} + + )} + + Transaction Metadata + + + + {command.networkId} + + + + {command.meta.chainId} + + + + + {command.meta.creationTime} ( + {new Date(command.meta.creationTime! * 1000).toLocaleString()}) + + + + + + {command.meta.ttl} ( + {new Date( + (command.meta.ttl! + command.meta.creationTime!) * 1000, + ).toLocaleString()} + ) + + + + + {command.nonce} + + + + + Gas Info + + + + {command.meta.sender} + + + + {command.meta.gasPrice} + + + + {command.meta.gasLimit} + + + + {command.meta.gasLimit! * command.meta.gasPrice!} KDA + + + + +
+ ); +} diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx index 73d6b5cdf6..2a61bd5a12 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx @@ -1,17 +1,22 @@ -import { ICommand, IPactCommand, IUnsignedCommand } from '@kadena/client'; +import { ICommand, IUnsignedCommand } from '@kadena/client'; import { Button, + ContextMenu, + ContextMenuItem, DialogContent, DialogFooter, DialogHeader, Heading, Notification, Stack, + TabItem, + Tabs, } from '@kadena/kode-ui'; import yaml from 'js-yaml'; -import { useMemo } from 'react'; -import { cardClass, codeClass } from './style.css.ts'; +import { codeClass } from './style.css.ts'; + +import { CopyButton } from '@/Components/CopyButton/CopyButton.tsx'; import { ITransaction, transactionRepository, @@ -19,9 +24,12 @@ import { import { useWallet } from '@/modules/wallet/wallet.hook.tsx'; import { panelClass } from '@/pages/home/style.css.ts'; import { useAsync } from '@/utils/useAsync.tsx'; -import { MonoContentCopy } from '@kadena/kode-icons/system'; -import { Label, Value } from './helpers.tsx'; -import { Signers } from './Signers.tsx'; +import { + MonoMoreVert, + MonoSignature, + MonoViewInAr, +} from '@kadena/kode-icons/system'; +import { CommandView } from './CommandView.tsx'; import { statusPassed, TxPipeLine } from './TxPipeLine.tsx'; export function ExpandedTransaction({ @@ -36,10 +44,6 @@ export function ExpandedTransaction({ sendDisabled?: boolean; }) { const { sign } = useWallet(); - const command: IPactCommand = useMemo( - () => JSON.parse(transaction.cmd), - [transaction.cmd], - ); const [contTx] = useAsync( (tx) => @@ -74,14 +78,16 @@ export function ExpandedTransaction({ const signedTx = (await sign(transaction)) as IUnsignedCommand | ICommand; onSign(signedTx.sigs); }; + const txCommand = { + hash: transaction.hash, + cmd: transaction.cmd, + sigs: transaction.sigs, + }; return ( <> View Transaction - @@ -90,161 +96,117 @@ export function ExpandedTransaction({ gap={'lg'} flexDirection={'column'} style={{ - flexBasis: '500px', + flexBasis: '260px', + minWidth: '260px', }} className={panelClass} > Tx Status - +
- {!statusPassed(transaction.status, 'preflight') && ( - + + - hash (request-key) - {transaction.hash} - - {'exec' in command.payload && ( - <> - - Code - - {command.payload.exec.code} - - - {Object.keys(command.payload.exec.data).length > 0 && ( - - Data -
-                          {JSON.stringify(command.payload.exec.data, null, 2)}
-                        
-
- )} - - )} - {'cont' in command.payload && ( - <> - - Continuation - - {command.payload.cont.pactId}- step( - {command.payload.cont.step}) - - - {Object.keys(command.payload.cont.data || {}).length > - 0 && ( - - Data -
-                          {JSON.stringify(command.payload.cont.data, null, 2)}
-                        
-
- )} - - )} - - Transaction Metadata - + + Command Details - - {command.networkId} - - - - {command.meta.chainId} - - - - - {command.meta.creationTime} ( - {new Date( - command.meta.creationTime! * 1000, - ).toLocaleString()} - ) - - - - - - {command.meta.ttl} ( - {new Date( - (command.meta.ttl! + command.meta.creationTime!) * - 1000, - ).toLocaleString()} - ) - - - - - {command.nonce} + + } + variant="transparent" + isCompact + /> + } + > + + + + + - - Gas Info - - - - {command.meta.sender} - - - - {command.meta.gasPrice} - - - - {command.meta.gasLimit} - - - - - {command.meta.gasLimit! * command.meta.gasPrice!} KDA - +
+ + {transaction.preflight && + (( + + + + ) as any)} + + {transaction.request && ( + + + + )} + {'result' in transaction && transaction.result && ( + + + + )} + {contTx && ( + <> + + + Command Details + -
-
- -
- )} - {statusPassed(transaction.status, 'preflight') && ( - - - Preflight Result -
-                    {JSON.stringify(transaction.preflight, null, 2)}
-                  
-
-
- )} - {statusPassed(transaction.status, 'submitted') && ( - - - Request -
-                    {JSON.stringify(transaction.request, null, 2)}
-                  
-
-
- )} - {statusPassed(transaction.status, 'success') && ( - - - Result -
-                    {JSON.stringify(
-                      'result' in transaction ? transaction.result : {},
-                      null,
-                      2,
-                    )}
-                  
-
-
- )} + + {contTx.preflight && ( + + + + )} + {contTx.request && ( + + + + )} + {'result' in contTx && contTx.result && ( + + + + )} + + )} +
@@ -255,15 +217,19 @@ export function ExpandedTransaction({ justifyContent={'space-between'} flex={1} > - {!statusPassed(transaction.status, 'signed') && ( + {/* {!statusPassed(transaction.status, 'signed') && ( - + - )} + )} */} - {transaction.status === 'signed' && !sendDisabled && ( - - )} + {/* {transaction.status === 'signed' && !sendDisabled && ( + + )} */} {statusPassed(transaction.status, 'success') && (!transaction.continuation?.autoContinue || (contTx && statusPassed(contTx.status, 'success'))) && ( @@ -272,24 +238,20 @@ export function ExpandedTransaction({ )} - - - - ); } + +const JsonView = ({ title, data }: { title: string; data: any }) => ( + + + + {title} + + +
{JSON.stringify(data, null, 2)}
+
+
+); diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/Signers.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/Signers.tsx index 3679d01fac..1cfc233934 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/Signers.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/Signers.tsx @@ -98,6 +98,7 @@ export function Signers({ + {!showExpanded && + !txs.every((tx) => statusPassed(tx.status, 'signed')) && ( + + You can sign all transactions at once. + + + - - )} - {!sendDisabled && + )} + {!showExpanded && + !sendDisabled && txs.every((tx) => statusPassed(tx.status, 'signed')) && txs.find((tx) => tx.status === 'signed') && ( diff --git a/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx b/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx index 290d6ee32e..b7f97c4a95 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx @@ -3,15 +3,20 @@ import { transactionRepository, TransactionStatus, } from '@/modules/transaction/transaction.repository'; +import { useWallet } from '@/modules/wallet/wallet.hook'; import { shorten } from '@/utils/helpers'; import { useAsync } from '@/utils/useAsync'; +import { ICommand, IUnsignedCommand } from '@kadena/client'; import { MonoBrightness1, MonoCheck, MonoClose, MonoLoading, + MonoPauseCircle, + MonoSignature, + MonoViewInAr, } from '@kadena/kode-icons/system'; -import { Stack, Text } from '@kadena/kode-ui'; +import { Button, Stack, Text } from '@kadena/kode-ui'; import classNames from 'classnames'; import { failureClass, pendingClass, successClass } from './style.css'; @@ -40,9 +45,15 @@ export const getStatusClass = (status: ITransaction['status']) => { export function TxPipeLine({ tx, variant, + signAll, + onSubmit, + sendDisabled, }: { tx: ITransaction; variant: 'tile' | 'expanded'; + signAll: () => Promise; + onSubmit: () => Promise; + sendDisabled?: boolean; }) { const textSize = variant === 'tile' ? 'smallest' : 'base'; const [contTx] = useAsync( @@ -62,18 +73,26 @@ export function TxPipeLine({ {tx.continuation?.autoContinue ? 'exec' : 'hash'}:{' '} {shorten(tx.hash, 6)} - {showAfterCont && variant === 'expanded' && !statusPassed(tx.status, 'signed') && ( - + - - ready for sign + + Waiting for sign + + + )} {showAfterCont && statusPassed(tx.status, 'signed') && ( @@ -90,20 +109,40 @@ export function TxPipeLine({ variant === 'expanded' && statusPassed(tx.status, 'signed') && !statusPassed(tx.status, 'preflight') && ( - + - + Ready to send + {!sendDisabled && ( + + )} )} {showAfterCont && statusPassed(tx.status, 'preflight') && ( - + - + {tx.preflight?.result.status === 'success' ? ( + + ) : ( + + )} preflight diff --git a/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx b/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx index 63a7790d8d..af8224f467 100644 --- a/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx +++ b/packages/apps/dev-wallet/src/pages/transfer-v2/Steps/TransferForm.tsx @@ -31,6 +31,7 @@ import { IReceiverAccount } from '../../transfer/utils'; import { AccountItem } from '../Components/AccountItem'; import { Keyset } from '../Components/keyset'; import { CHAINS, IReceiver, discoverReceiver, getTransfers } from '../utils'; +import { labelClass } from './style.css'; export interface Transfer { fungible: string; @@ -63,6 +64,12 @@ export interface TrG { txs: ITransaction[]; } +const Label = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + export function TransferForm({ accountId, onSubmit, @@ -300,7 +307,8 @@ export function TransferForm({ render={({ field }) => ( Address:} // label="Account" + placeholder="Select and address" size="sm" selectedKey={field.value} onSelectionChange={withEvaluate(field.onChange)} @@ -345,9 +354,10 @@ export function TransferForm({ control={control} render={({ field }) => ( Chain:} // label={index === 0 ? 'Chain' : undefined} + placeholder="Select a chain" description={ rec.chain && redistribution.find( @@ -657,7 +671,6 @@ export function TransferForm({ : '' } size="sm" - placeholder="Chains" selectedKey={field.value} onSelectionChange={withEvaluate( field.onChange, @@ -802,8 +815,9 @@ export function TransferForm({ control={control} render={({ field }) => (