diff --git a/packages/apps/dev-wallet/index.html b/packages/apps/dev-wallet/index.html index 2965b8106f..81a5757de9 100644 --- a/packages/apps/dev-wallet/index.html +++ b/packages/apps/dev-wallet/index.html @@ -5,9 +5,24 @@ Kadena Dev Wallet + - +
+ +
+
+ Chainweaver Logo +

Welcome to Chainweaver v3

+

Wallet is Loading ...

+
+
+ diff --git a/packages/apps/dev-wallet/public/boot.css b/packages/apps/dev-wallet/public/boot.css new file mode 100644 index 0000000000..186d3aa206 --- /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: 'Roboto', sans-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/App/app.tsx b/packages/apps/dev-wallet/src/App/app.tsx index 919294f66c..d022771fe5 100644 --- a/packages/apps/dev-wallet/src/App/app.tsx +++ b/packages/apps/dev-wallet/src/App/app.tsx @@ -14,18 +14,18 @@ function Providers({ children }: { children: React.ReactNode }) { } }, []); return ( - - - - - {/* TODO: fixed the issue with prompt and remove this one in favor of the one above */} - - {children} - - - - - + + + + + + {/* TODO: fixed the issue with prompt and remove this one in favor of the one above */} + {children} + + + + + ); } diff --git a/packages/apps/dev-wallet/src/Components/Accounts/WatchAccountDialog.tsx b/packages/apps/dev-wallet/src/Components/Accounts/WatchAccountDialog.tsx index f79c5afed6..94116ea531 100644 --- a/packages/apps/dev-wallet/src/Components/Accounts/WatchAccountDialog.tsx +++ b/packages/apps/dev-wallet/src/Components/Accounts/WatchAccountDialog.tsx @@ -50,43 +50,45 @@ export function WatchAccountsDialog({ contract={contract} /> - - - Watch your contacts - - {contacts.map((contact) => ( - - { - setSelectedContacts((prev) => - prev.find((c) => c.uuid === contact.uuid) - ? prev.filter((c) => c.uuid !== contact.uuid) - : [...prev, contact], - ); - }} - > - c.uuid === contact.uuid) - } - onChange={(isSelected) => { + {contacts.length > 0 && ( + + + Watch your contacts + + {contacts.map((contact) => ( + + { setSelectedContacts((prev) => - isSelected - ? [...prev, contact] - : prev.filter((c) => c.uuid !== contact.uuid), + prev.find((c) => c.uuid === contact.uuid) + ? prev.filter((c) => c.uuid !== contact.uuid) + : [...prev, contact], ); }} > - {' '} - - - - - ))} - + c.uuid === contact.uuid) + } + onChange={(isSelected) => { + setSelectedContacts((prev) => + isSelected + ? [...prev, contact] + : prev.filter((c) => c.uuid !== contact.uuid), + ); + }} + > + {' '} + + + + + ))} + + )} 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..cf52cb3536 --- /dev/null +++ b/packages/apps/dev-wallet/src/Components/CopyButton/CopyButton.tsx @@ -0,0 +1,17 @@ +// import { MonoContentCopy } from '@kadena/kode-icons/system'; +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/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} diff --git a/packages/apps/dev-wallet/src/index.ts b/packages/apps/dev-wallet/src/index.ts index a3fe5cbbac..235362072d 100644 --- a/packages/apps/dev-wallet/src/index.ts +++ b/packages/apps/dev-wallet/src/index.ts @@ -1,7 +1,30 @@ +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 -function bootstrap() { - import('./App/main').then(({ renderApp }) => renderApp()); +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.style.opacity = '1'; + } + }, 200); } bootstrap(); 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/modules/wallet/wallet.provider.tsx b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx index 2df31f4b8f..cb3e1e575d 100644 --- a/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx +++ b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx @@ -197,7 +197,7 @@ function usePassword(profile: IProfile | undefined) { if (unlockOptions.keepOpen === 'never') { return unlockOptions.password; } - setPassword(unlockOptions.password); + await setPassword(unlockOptions.password); if (unlockOptions.keepOpen === 'short-time') { setTimeout( () => { @@ -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/account-discovery/account-dsicovery.tsx b/packages/apps/dev-wallet/src/pages/account-discovery/account-dsicovery.tsx index 6b9513c864..c5ebc3c129 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 { 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/create-account/create-account.tsx b/packages/apps/dev-wallet/src/pages/create-account/create-account.tsx index 0622061735..a917685dfd 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,25 +13,38 @@ import { Keyset } from '@/Components/Keyset/Keyset.tsx'; import { MonoAdd } from '@kadena/kode-icons/system'; import { Button, + Card, + Divider, Heading, + Radio, + RadioGroup, Select, SelectItem, Stack, - TabItem, - Tabs, 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 +57,6 @@ export function CreateAccount() { fungibles, keysets, accounts, - askForPassword, } = useWallet(); const filteredAccounts = accounts.filter( @@ -130,177 +142,151 @@ 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 + + + Tip: Since the guard can be changed, this type of account is + more flexible. - - - {keysets - .filter(({ guard }) => guard.keys.length >= 2) - .map((keyset) => { - const disabled = usedKeysets.includes(keyset.uuid); + + + + + + {contract && ( + + {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 ( createAccountByKeyset(keyset)} - disabled={disabled} - title={ - disabled - ? 'Already used' - : 'Use this keyset for Account' + selected={ + selectedItem?.type === 'keyset' && + selectedItem?.item.uuid === keyset.uuid + } + onClick={() => + setSelectedItem({ + item: keyset, + type: 'keyset', + }) } > ); })} + - - - Keyset Management - - - - - - )} + )} + {['k:account', 'w:account'].includes(accountType) && + filteredKeySources.length && ( + + Keys + + {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', + }) + } + > + + + + + + + ); + })} + + + ); + })} + + + )} + + )} + + + + +
- + ); } 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..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', @@ -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']}`, + }, }, }, ]); 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..e5e73b4412 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 @@ -1,10 +1,5 @@ -import { - cardBackgroundColorHover, - cardColor, - cardColorHover, - linkBlockColor, -} from '@/utils/color.ts'; -import { atoms, tokens } from '@kadena/kode-ui/styles'; +import { cardColor, linkBlockColor } from '@/utils/color.ts'; +import { atoms, tokens, vars } from '@kadena/kode-ui/styles'; import { style } from '@vanilla-extract/css'; export const titleClass = style([ @@ -31,12 +26,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 +86,3 @@ export const linkClass = style([ }, }, ]); - - diff --git a/packages/apps/dev-wallet/src/pages/transaction/Transaction.tsx b/packages/apps/dev-wallet/src/pages/transaction/Transaction.tsx index 2f5d07d500..8840ad5fdb 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/Transaction.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/Transaction.tsx @@ -19,7 +19,7 @@ import { isSignedCommand } from '@kadena/pactjs'; import classNames from 'classnames'; import { useCallback, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { ReviewTransaction } from './components/ReviewTransaction'; +import { CommandView } from './components/CommandView'; import { SubmittedStatus } from './components/SubmittedStatus'; import { failureClass, @@ -289,7 +289,7 @@ export function Transaction({ groupId }: { groupId?: string }) { } > - { patchTransaction(tx, { 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..02228e5c92 --- /dev/null +++ b/packages/apps/dev-wallet/src/pages/transaction/components/CommandView.tsx @@ -0,0 +1,136 @@ +import { CopyButton } from '@/Components/CopyButton/CopyButton'; +import { ITransaction } from '@/modules/transaction/transaction.repository'; +import { IPactCommand } from '@kadena/client'; +import { Heading, Stack, Text } from '@kadena/kode-ui'; +import classNames from 'classnames'; +import { useMemo } from 'react'; +import { Label, Value } from './helpers'; +import { Signers } from './Signers'; +import { cardClass, codeClass, textEllipsis } 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)}
+              
+
+ )} + {command.payload.cont.proof && ( + + + Proof + + + + {command.payload.cont.proof} + + + )} + + )} + + 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 36b3c5cf8d..c7e3911d59 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/ExpandedTransaction.tsx @@ -1,16 +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, @@ -18,9 +24,8 @@ 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 } from '@kadena/kode-icons/system'; +import { CommandView } from './CommandView.tsx'; import { statusPassed, TxPipeLine } from './TxPipeLine.tsx'; export function ExpandedTransaction({ @@ -35,10 +40,6 @@ export function ExpandedTransaction({ sendDisabled?: boolean; }) { const { sign } = useWallet(); - const command: IPactCommand = useMemo( - () => JSON.parse(transaction.cmd), - [transaction.cmd], - ); const [contTx] = useAsync( (tx) => @@ -73,14 +74,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 - @@ -89,157 +92,123 @@ export function ExpandedTransaction({ gap={'lg'} flexDirection={'column'} style={{ - width: '400px', - maxWidth: '400px', + 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.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 Details - - {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 && ( + + + + )} + {transaction.continuation?.proof && ( + + + + )} + {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 && ( + + + + ), + ]} +
@@ -250,26 +219,33 @@ export function ExpandedTransaction({ justifyContent={'space-between'} flex={1} > - {!statusPassed(transaction.status, 'signed') && ( - - - - )} - {transaction.status === 'signed' && !sendDisabled && ( - - - - )} - - - + + {statusPassed(transaction.status, 'success') && + (!transaction.continuation?.autoContinue || + (contTx && statusPassed(contTx.status, 'success'))) && ( + + Transaction is successful + + )}
); } + +const JsonView = ({ title, data }: { title: string; data: any }) => ( + + + + {title} + + +
+        {data && typeof data === 'object'
+          ? JSON.stringify(data, null, 2)
+          : data}
+      
+
+
+); 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 7c728cf010..7abab5b7b9 100644 --- a/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx +++ b/packages/apps/dev-wallet/src/pages/transaction/components/TxPipeLine.tsx @@ -10,8 +10,11 @@ import { 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,10 +43,24 @@ export const getStatusClass = (status: ITransaction['status']) => { export function TxPipeLine({ tx, variant, -}: { - tx: ITransaction; - variant: 'tile' | 'expanded'; -}) { + signAll, + onSubmit, + sendDisabled, +}: + | { + tx: ITransaction; + variant: 'tile'; + signAll?: () => Promise; + onSubmit?: () => Promise; + sendDisabled?: boolean; + } + | { + tx: ITransaction; + variant: 'expanded'; + signAll: () => Promise; + onSubmit: () => Promise; + sendDisabled?: boolean; + }) { const textSize = variant === 'tile' ? 'smallest' : 'base'; const [contTx] = useAsync( (transaction) => @@ -54,9 +71,37 @@ 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') && ( + + + + + Waiting for sign + + + + + + + )} + {showAfterCont && statusPassed(tx.status, 'signed') && ( @@ -66,17 +111,50 @@ export function TxPipeLine({ )} - {!contTx && statusPassed(tx.status, 'preflight') && ( + {showAfterCont && + variant === 'expanded' && + statusPassed(tx.status, 'signed') && + !statusPassed(tx.status, 'preflight') && ( + + + + + {sendDisabled ? 'Send is pending' : 'Ready to send'} + + + + + + )} + {showAfterCont && statusPassed(tx.status, 'preflight') && ( - + - + {tx.preflight?.result.status === 'success' ? ( + + ) : ( + + )} preflight )} - {!contTx && statusPassed(tx.status, 'submitted') && ( + {showAfterCont && statusPassed(tx.status, 'submitted') && ( @@ -86,7 +164,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..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, @@ -39,13 +35,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..2a031205d8 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({ @@ -77,6 +77,14 @@ export const pendingText = style({ opacity: 0.5, }); +export const textEllipsis = style({ + overflow: 'hidden', + minHeight: '2.2em', + // overflowY: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', +}); + export const tabTextClass = style({ width: '50px', overflow: 'hidden', 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..26aaa6183f 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; @@ -43,6 +44,7 @@ export interface Transfer { type: 'safeTransfer' | 'normalTransfer'; ttl: string; senderAccount?: IAccount; + totalAmount: number; } export type Redistribution = { @@ -62,6 +64,12 @@ export interface TrG { txs: ITransaction[]; } +const Label = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + export function TransferForm({ accountId, onSubmit, @@ -83,7 +91,6 @@ export function TransferForm({ const urlAccount = allAccounts.find((account) => account.uuid === accountId); const { control, - register, watch, setValue, reset, @@ -110,6 +117,7 @@ export function TransferForm({ gasLimit: '2500', type: 'normalTransfer', ttl: (2 * 60 * 60).toString(), + totalAmount: 0, }, }); @@ -154,6 +162,7 @@ export function TransferForm({ gasLimit: activity.data.transferData.gasLimit, type: activity.data.transferData.type, ttl: activity.data.transferData.ttl, + totalAmount: 0, }); evaluateTransactions(); } @@ -173,13 +182,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 +197,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 +237,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; }; }, @@ -292,7 +307,8 @@ export function TransferForm({ render={({ field }) => ( Address:} // label="Account" + placeholder="Select and address" size="sm" selectedKey={field.value} onSelectionChange={withEvaluate(field.onChange)} @@ -337,9 +354,10 @@ export function TransferForm({ control={control} render={({ field }) => ( r.target === rec.chain) - ? `This will trigger balance redistribution` - : '' - } - size="sm" - placeholder="Chains" - selectedKey={field.value} - onSelectionChange={withEvaluate(field.onChange)} + + - {(rec.amount ? availableChains : []).map( - (chain) => ( - - {chain ? ( - - ) : ( - - `chain ${c.chainId}: ${c.amount}`, + { + return ( + + Address:} + onInputChange={(value) => { + 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, + ); + + setValue( + `receivers.${index}.discoveredAccounts`, + discoveredAccounts, + ); + setValue( + `receivers.${index}.discoveryStatus`, + 'done', + ); + }} + size="sm" + onSelectionChange={(value) => { + console.log('value', value); + field.onChange(value || ''); + setValue( + `receivers.${index}.discoveredAccounts`, + [], + ); + setValue( + `receivers.${index}.discoveryStatus`, + 'not-started', + ); + }} + allowsCustomValue + > + {filteredAccounts + .filter( + (account) => + account.address !== + senderAccount?.address, + // && + // !watchReceivers.some( + // (receiver, i) => + // i !== index && + // receiver.address === account.address, + // ), + ) + .map((account) => ( + + + + ))} + + + { + const discoveryStatus = field.value; + const discoveredAccounts = getValues( + `receivers.${index}.discoveredAccounts`, + ); + return ( + <> + {discoveryStatus === + 'in-progress' && ( + + + Discovering... + + + )} + {discoveryStatus === 'done' && + discoveredAccounts.length === + 1 && ( + + )} + + ); + }} + /> + + ); + }} + /> + ( + { + const value = e.target.value; + field.onChange(value); + }} + placeholder="Enter the amount" + startVisual={} + onBlur={evaluateTransactions} + value={field.value} + size="sm" + type="number" + step="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{' '} + + + + )} - + + + {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 - - )} - + }} + /> + (