From 925fd9344b7d42b943893e3b07bf0b0a746c4f54 Mon Sep 17 00:00:00 2001 From: Hoang Dinh Date: Mon, 9 Dec 2024 17:01:36 +1000 Subject: [PATCH] feat: use a custom form to prompt for KMD password and Mnemonic --- package-lock.json | 50 +++++++++---------- package.json | 6 +-- .../common/components/prompt-form.tsx | 44 ++++++++++++++++ .../common/components/wallet-provider.tsx | 48 ++++++++++++++++-- 4 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/features/common/components/prompt-form.tsx diff --git a/package-lock.json b/package-lock.json index b8f4458a..118503e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,8 +42,8 @@ "@tauri-apps/plugin-dialog": "^2.0.1", "@tauri-apps/plugin-fs": "^2.0.2", "@tauri-apps/plugin-shell": "^2.0.1", - "@txnlab/use-wallet": "^3.10.1", - "@txnlab/use-wallet-react": "^3.10.1", + "@txnlab/use-wallet": "^3.11.0", + "@txnlab/use-wallet-react": "^3.11.0", "@xstate/react": "^4.1.1", "algosdk": "2.9.0", "class-variance-authority": "^0.7.0", @@ -3558,11 +3558,11 @@ } }, "node_modules/@tanstack/react-store": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.5.5.tgz", - "integrity": "sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.6.1.tgz", + "integrity": "sha512-6gOopOpPp1cAXkEyTEv6tMbAywwFunvIdCKN/SpEiButUayjXU+Q5Sp5Y3hREN3VMR4OA5+RI5SPhhJoqP9e4w==", "dependencies": { - "@tanstack/store": "0.5.5", + "@tanstack/store": "0.6.0", "use-sync-external-store": "^1.2.2" }, "funding": { @@ -3570,8 +3570,8 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@tanstack/react-table": { @@ -3595,9 +3595,9 @@ } }, "node_modules/@tanstack/store": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.5.5.tgz", - "integrity": "sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.6.0.tgz", + "integrity": "sha512-+m2OBglsjXcLmmKOX6/9v8BDOCtyxhMmZLsRUDswOOSdIIR9mvv6i0XNKsmTh3AlYU8c1mRcodC8/Vyf+69VlQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -3997,12 +3997,11 @@ } }, "node_modules/@txnlab/use-wallet": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@txnlab/use-wallet/-/use-wallet-3.10.1.tgz", - "integrity": "sha512-FxXPFRJUdOKR9Y9gO4YQDcoQDiPQJVcyuXIMtkVbnSUzLrUlwzZoZmjJFCLJPjXtcqyIqIexOpd17sCZOfhUrA==", - "license": "MIT", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@txnlab/use-wallet/-/use-wallet-3.11.0.tgz", + "integrity": "sha512-Lls2nLMXkt5X5KPwsROmhCKzlJQGmUrSHJFNw8fD/ZHkgHH75e0BxOsgb0CK9py1D0zgZ+ZFTKSEC3bhL78c6g==", "dependencies": { - "@tanstack/store": "0.5.5" + "@tanstack/store": "0.6.0" }, "peerDependencies": { "@agoralabs-sh/avm-web-provider": "^1.7.0", @@ -4011,7 +4010,7 @@ "@perawallet/connect": "^1.3.5", "@perawallet/connect-beta": "^2.0.21", "@walletconnect/modal": "^2.7.0", - "@walletconnect/sign-client": "^2.17.1", + "@walletconnect/sign-client": "^2.17.2", "algosdk": "^2.7.0", "lute-connect": "^1.4.1" }, @@ -4043,24 +4042,23 @@ } }, "node_modules/@txnlab/use-wallet-react": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@txnlab/use-wallet-react/-/use-wallet-react-3.10.1.tgz", - "integrity": "sha512-IhfqG7hfI9AEqMUXgopgOs/nKAbbjElLxg5IS0u92QmcTjOeU7WmtoS1aCVOrnkFyahV3fU6TAFzuvEoHR+2Wg==", - "license": "MIT", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@txnlab/use-wallet-react/-/use-wallet-react-3.11.0.tgz", + "integrity": "sha512-jxnbhf/WNaa/GaHNv3YXLY/rVayAZosgLXkLs5ugOOaL9Ylf8wT9TXuzGhV5aLTXOtW8D45WC+hpUjsQjEk/fQ==", "dependencies": { - "@tanstack/react-store": "0.5.5", - "@txnlab/use-wallet": "3.10.1" + "@tanstack/react-store": "0.6.1", + "@txnlab/use-wallet": "3.11.0" }, "peerDependencies": { "@blockshake/defly-connect": "^1.1.6", - "@magic-ext/algorand": "^23.13.0", + "@magic-ext/algorand": "^23.18.0", "@perawallet/connect": "^1.3.5", "@perawallet/connect-beta": "^2.0.21", "@walletconnect/modal": "^2.7.0", - "@walletconnect/sign-client": "^2.17.1", + "@walletconnect/sign-client": "^2.17.2", "algosdk": "^2.7.0", "lute-connect": "^1.4.1", - "magic-sdk": "^28.13.0", + "magic-sdk": "^28.19.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, diff --git a/package.json b/package.json index 0b3f4440..47ee1dcc 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "@tauri-apps/plugin-dialog": "^2.0.1", "@tauri-apps/plugin-fs": "^2.0.2", "@tauri-apps/plugin-shell": "^2.0.1", - "@txnlab/use-wallet": "^3.10.1", - "@txnlab/use-wallet-react": "^3.10.1", + "@txnlab/use-wallet": "^3.11.0", + "@txnlab/use-wallet-react": "^3.11.0", "@xstate/react": "^4.1.1", "algosdk": "2.9.0", "class-variance-authority": "^0.7.0", @@ -197,4 +197,4 @@ "ws@>7.0.0 <7.5.9": "7.5.10", "path-to-regexp@>= 0.2.0 <8.0.0": "8.0.0" } -} \ No newline at end of file +} diff --git a/src/features/common/components/prompt-form.tsx b/src/features/common/components/prompt-form.tsx new file mode 100644 index 00000000..6d1eb9f0 --- /dev/null +++ b/src/features/common/components/prompt-form.tsx @@ -0,0 +1,44 @@ +import { useCallback } from 'react' +import { zfd } from 'zod-form-data' +import { z } from 'zod' +import { Form } from '@/features/forms/components/form' +import { FormActions } from '@/features/forms/components/form-actions' +import { SubmitButton } from '@/features/forms/components/submit-button' +import { CancelButton } from '@/features/forms/components/cancel-button' + +type Props = { + message: string + type: 'text' | 'password' + onSubmit: (value: string | null) => void + onCancel: () => void +} + +const schema = zfd.formData({ + value: zfd.text(z.string().optional()), +}) + +export function PromptForm({ message, type, onSubmit, onCancel }: Props) { + const submit = useCallback( + async (values: z.infer) => { + onSubmit(values.value ?? '') + }, + [onSubmit] + ) + + return ( +
+ + Save + + } + > + {(helper) => + type === 'text' ? helper.textField({ label: message, field: 'value' }) : helper.passwordField({ label: message, field: 'value' }) + } + + ) +} diff --git a/src/features/common/components/wallet-provider.tsx b/src/features/common/components/wallet-provider.tsx index 57ae401b..31935f88 100644 --- a/src/features/common/components/wallet-provider.tsx +++ b/src/features/common/components/wallet-provider.tsx @@ -3,6 +3,8 @@ import { WalletProviderInner } from './wallet-provider-inner' import { defaultKmdWallet, useSelectedKmdWallet } from '@/features/wallet/data/selected-kmd-wallet' import { NetworkConfigWithId } from '@/features/network/data/types' import { NetworkId, SupportedWallet, WalletId, WalletIdConfig, WalletManager } from '@txnlab/use-wallet-react' +import { DialogBodyProps, useDialogForm } from '../hooks/use-dialog-form' +import { PromptForm } from './prompt-form' type Props = PropsWithChildren<{ networkConfig: NetworkConfigWithId @@ -10,6 +12,18 @@ type Props = PropsWithChildren<{ export function WalletProvider({ networkConfig, children }: Props) { const selectedKmdWallet = useSelectedKmdWallet() + const { open: openKmdPasswordDialog, dialog: kmdPasswordDialog } = useDialogForm({ + dialogHeader: 'Connect KMD Wallet', + dialogBody: (props: DialogBodyProps<{ message: string }, string | null>) => ( + + ), + }) + const { open: openMnemonicDialog, dialog: mnemonicDialog } = useDialogForm({ + dialogHeader: 'Connect Mnemonic Wallet', + dialogBody: (props: DialogBodyProps<{ message: string }, string | null>) => ( + + ), + }) const key = `${networkConfig.id}-${selectedKmdWallet ?? ''}` @@ -24,9 +38,29 @@ export function WalletProvider({ networkConfig, children }: Props) { baseServer: networkConfig.kmd.server, token: networkConfig.kmd.token ?? '', port: String(networkConfig.kmd.port), + promptForPassword: async () => { + const password = await openKmdPasswordDialog({ message: 'Enter KMD Password' }) + if (password == null) { + throw new Error('No password provided') + } + return password + }, }, } satisfies WalletIdConfig) - } else if ([WalletId.MNEMONIC, WalletId.DEFLY, WalletId.PERA, WalletId.EXODUS].includes(id)) { + } else if (id === WalletId.MNEMONIC) { + acc.push({ + id, + options: { + promptForMnemonic: async () => { + const passphrase = await openMnemonicDialog({ message: 'Enter 25-word mnemonic passphrase' }) + if (!passphrase) { + throw new Error('No passphrase provided') + } + return passphrase + }, + }, + }) + } else if ([WalletId.DEFLY, WalletId.PERA, WalletId.EXODUS].includes(id)) { acc.push(id) } else if (id === WalletId.LUTE) { acc.push({ @@ -43,7 +77,7 @@ export function WalletProvider({ networkConfig, children }: Props) { }, [] as unknown as SupportedWallet[] ) - }, [networkConfig.kmd, networkConfig.walletIds, selectedKmdWallet]) + }, [networkConfig.walletIds, networkConfig.kmd, selectedKmdWallet, openKmdPasswordDialog, openMnemonicDialog]) const walletManager = useMemo(() => { return new WalletManager({ @@ -60,8 +94,12 @@ export function WalletProvider({ networkConfig, children }: Props) { return ( // The key prop is super important it governs if the provider is reinitialized - - {children} - + <> + + {children} + + {kmdPasswordDialog} + {mnemonicDialog} + ) }