From 599ef34163fc8577139dbc1566706a02b50e4db0 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 06:13:30 -0500 Subject: [PATCH 1/8] feat(lc-dapp): transfers --- examples/light-client-dapp/package.json | 4 +- .../src/components/Transfer.tsx | 48 ++++++---- examples/light-client-dapp/src/hooks/index.ts | 1 + .../src/hooks/useTransfer.ts | 89 +++++++++++++++++++ pnpm-lock.yaml | 15 ++++ 5 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 examples/light-client-dapp/src/hooks/useTransfer.ts diff --git a/examples/light-client-dapp/package.json b/examples/light-client-dapp/package.json index b4b3ed558..e4cd5c42c 100644 --- a/examples/light-client-dapp/package.json +++ b/examples/light-client-dapp/package.json @@ -20,12 +20,14 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-select": "^5.8.0", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "uuid": "^9.0.1" }, "devDependencies": { "@substrate/unstable-wallet-provider": "workspace:^", "@types/react": "^18.2.60", "@types/react-dom": "^18.2.19", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.0.2", "@typescript-eslint/parser": "^7.1.0", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 4225a3611..5b6013015 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -1,14 +1,8 @@ import { FormEvent, useCallback, useEffect, useMemo, useState } from "react" -import { ss58Decode } from "@polkadot-labs/hdkd-helpers" import { UnstableWallet } from "@substrate/unstable-wallet-provider" -import { mergeUint8, toHex } from "@polkadot-api/utils" import Select from "react-select" -import { useSystemAccount } from "../hooks" -import { getObservableClient } from "@polkadot-api/client" -import { ConnectProvider, createClient } from "@polkadot-api/substrate-client" -import { Enum, SS58String } from "@polkadot-api/substrate-bindings" -import { getDynamicBuilder } from "@polkadot-api/metadata-builders" -import { firstValueFrom, filter, map } from "rxjs" +import { useSystemAccount, useTransfer } from "../hooks" +import { lastValueFrom, tap } from "rxjs" type Props = { provider: UnstableWallet.Provider @@ -64,7 +58,9 @@ const createTransfer = ( export const Transfer = ({ provider }: Props) => { const [accounts, setAccounts] = useState([]) - const [destination, setDestination] = useState("") + const [destination, setDestination] = useState( + "5CofVLAGjwvdGXvBiP6ddtZYMVbhT5Xke8ZrshUpj2ZXAnND", + ) const [amount, setAmount] = useState(0n) const [selectedAccount, setSelectedAccount] = useState<{ value: string @@ -78,6 +74,10 @@ export const Transfer = ({ provider }: Props) => { connect, selectedAccount ? selectedAccount.value : null, ) + const { transfer, subscriptions: transferSubscriptions } = useTransfer( + { ...provider, connect }, + chainId, + ) const balance = accountStorage?.data.free ?? 0n @@ -87,7 +87,7 @@ export const Transfer = ({ provider }: Props) => { }) }, [provider]) - const [isCreatingTransaction, setIsCreatingTransaction] = useState(false) + const [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false) const handleOnSubmit = useCallback( async (e: FormEvent) => { e.preventDefault() @@ -95,20 +95,30 @@ export const Transfer = ({ provider }: Props) => { return } - setIsCreatingTransaction(true) + setIsSubmittingTransaction(true) try { - const tx = await provider.createTx( - chainId, - toHex(ss58Decode(selectedAccount.value)[0]), - await createTransfer(connect, destination, amount), + const sender = selectedAccount.value + const { txId, tx } = await transfer(sender, destination, amount) + console.log({ txId, tx }) + + lastValueFrom( + transferSubscriptions[txId].pipe( + tap({ + next: (e) => { + console.log(`event:`, e) + }, + error: (e) => { + console.error(`ERROR:`, e) + }, + }), + ), ) - console.log({ tx }) } catch (error) { console.error(error) } - setIsCreatingTransaction(false) + setIsSubmittingTransaction(false) }, - [provider, selectedAccount, connect, destination, amount], + [selectedAccount, transfer, destination, amount, transferSubscriptions], ) const accountOptions = accounts.map((account) => ({ @@ -147,7 +157,7 @@ export const Transfer = ({ provider }: Props) => {
diff --git a/examples/light-client-dapp/src/hooks/index.ts b/examples/light-client-dapp/src/hooks/index.ts index e23feee2e..3029e3930 100644 --- a/examples/light-client-dapp/src/hooks/index.ts +++ b/examples/light-client-dapp/src/hooks/index.ts @@ -1,2 +1,3 @@ export * from "./useProvider" export * from "./useSystemAccount" +export * from "./useTransfer" diff --git a/examples/light-client-dapp/src/hooks/useTransfer.ts b/examples/light-client-dapp/src/hooks/useTransfer.ts new file mode 100644 index 000000000..8d575dc5b --- /dev/null +++ b/examples/light-client-dapp/src/hooks/useTransfer.ts @@ -0,0 +1,89 @@ +import { getObservableClient } from "@polkadot-api/client" +import { Enum } from "@polkadot-api/substrate-bindings" +import type { SS58String } from "@polkadot-api/substrate-bindings" +import { + ConnectProvider, + TxEvent, + createClient, +} from "@polkadot-api/substrate-client" +import { getDynamicBuilder } from "@polkadot-api/metadata-builders" +import { + firstValueFrom, + filter, + map, + Observable, + tap, + ReplaySubject, + Subject, + takeUntil, +} from "rxjs" +import { mergeUint8, toHex } from "@polkadot-api/utils" +import { UnstableWallet } from "@substrate/unstable-wallet-provider" +import { v4 as uuidv4 } from "uuid" +import { ss58Decode } from "@polkadot-labs/hdkd-helpers" + +const AccountId = (value: SS58String) => + Enum< + { + type: "Id" + value: SS58String + }, + "Id" + >("Id", value) + +type Provider = UnstableWallet.Provider & { connect: ConnectProvider } + +export const useTransfer = (provider: Provider, chainId: string) => { + const subscriptions: Record> = {} + + const transfer = async ( + sender: SS58String, + destination: SS58String, + amount: bigint, + ) => { + const client = getObservableClient(createClient(provider.connect)) + const { metadata$ } = client.chainHead$() + + const callData = await firstValueFrom( + metadata$.pipe( + filter(Boolean), + map((metadata) => { + const dynamicBuilder = getDynamicBuilder(metadata) + const { location, args } = dynamicBuilder.buildCall( + "Balances", + "transfer_allow_death", + ) + + return toHex( + mergeUint8( + new Uint8Array(location), + args.enc({ + dest: AccountId(destination), + value: amount, + }), + ), + ) + }), + ), + ) + + console.log("sender", sender) + + const tx = await provider.createTx( + chainId, + toHex(ss58Decode(sender)[0]), + callData, + ) + const txId = uuidv4() + const txEvents = new ReplaySubject() + + subscriptions[txId] = txEvents + + const destroy$ = new Subject() + client.tx$(tx).pipe(tap(txEvents), takeUntil(destroy$)).subscribe() + + return { txId, tx, destroy$ } + } + + return { subscriptions, transfer } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 561d4f9a9..403000e70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.1 + uuid: + specifier: ^9.0.1 + version: 9.0.1 devDependencies: '@substrate/unstable-wallet-provider': specifier: workspace:^ @@ -123,6 +126,9 @@ importers: '@types/react-dom': specifier: ^18.2.19 version: 18.2.19 + '@types/uuid': + specifier: ^9.0.8 + version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^7.0.2 version: 7.0.2(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3) @@ -3883,6 +3889,10 @@ packages: resolution: {integrity: sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==} dev: false + /@types/uuid@9.0.8: + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + dev: true + /@types/webextension-polyfill@0.10.7: resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==} dev: true @@ -11097,6 +11107,11 @@ packages: hasBin: true dev: true + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} From 18f7456b9b35703b80444be8496a503b4adbb149 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 10:29:57 -0500 Subject: [PATCH 2/8] more --- .../src/components/Transfer.tsx | 76 +++++++------------ .../src/hooks/useTransfer.ts | 4 +- 2 files changed, 29 insertions(+), 51 deletions(-) diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 5b6013015..7dce8f173 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -13,49 +13,6 @@ type Props = { const chainId = "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e" -const AccountId = (value: SS58String) => - Enum< - { - type: "Id" - value: SS58String - }, - "Id" - >("Id", value) - -// TODO: Extract to hook that creates and submits the tx while also managing -// the tx lifecycle -const createTransfer = ( - provider: ConnectProvider, - destination: string, - amount: bigint, -) => { - const client = getObservableClient(createClient(provider)) - const { metadata$ } = client.chainHead$() - - return firstValueFrom( - metadata$.pipe( - filter(Boolean), - map((metadata) => { - const dynamicBuilder = getDynamicBuilder(metadata) - const { location, args } = dynamicBuilder.buildCall( - "Balances", - "transfer_allow_death", - ) - - return toHex( - mergeUint8( - new Uint8Array(location), - args.enc({ - dest: AccountId(destination), - value: amount, - }), - ), - ) - }), - ), - ) -} - export const Transfer = ({ provider }: Props) => { const [accounts, setAccounts] = useState([]) const [destination, setDestination] = useState( @@ -78,6 +35,8 @@ export const Transfer = ({ provider }: Props) => { { ...provider, connect }, chainId, ) + const [transactionStatus, setTransactionStatus] = useState("") + const [finalizedHash, setFinalizedHash] = useState("") const balance = accountStorage?.data.free ?? 0n @@ -96,19 +55,30 @@ export const Transfer = ({ provider }: Props) => { } setIsSubmittingTransaction(true) + setTransactionStatus("") try { const sender = selectedAccount.value - const { txId, tx } = await transfer(sender, destination, amount) - console.log({ txId, tx }) + const { txId, destroy$ } = await transfer(sender, destination, amount) + + const cleanup = () => { + destroy$.next() + destroy$.complete() + delete transferSubscriptions[txId] + } lastValueFrom( transferSubscriptions[txId].pipe( tap({ - next: (e) => { - console.log(`event:`, e) + next: (e): void => { + setTransactionStatus(e.type) + if (e.type === "finalized") { + setFinalizedHash(e.block.hash) + cleanup() + } }, error: (e) => { - console.error(`ERROR:`, e) + setTransactionStatus(e.type) + cleanup() }, }), ), @@ -161,6 +131,16 @@ export const Transfer = ({ provider }: Props) => { > Transfer + {transactionStatus ? ( +

+ Transaction Status: {`${transactionStatus}`} +

+ ) : null} + {finalizedHash ? ( +

+ Finalized Hash: {`${finalizedHash}`} +

+ ) : null}
diff --git a/examples/light-client-dapp/src/hooks/useTransfer.ts b/examples/light-client-dapp/src/hooks/useTransfer.ts index 8d575dc5b..ba2be23bf 100644 --- a/examples/light-client-dapp/src/hooks/useTransfer.ts +++ b/examples/light-client-dapp/src/hooks/useTransfer.ts @@ -67,8 +67,6 @@ export const useTransfer = (provider: Provider, chainId: string) => { ), ) - console.log("sender", sender) - const tx = await provider.createTx( chainId, toHex(ss58Decode(sender)[0]), @@ -79,7 +77,7 @@ export const useTransfer = (provider: Provider, chainId: string) => { subscriptions[txId] = txEvents - const destroy$ = new Subject() + const destroy$ = new Subject() client.tx$(tx).pipe(tap(txEvents), takeUntil(destroy$)).subscribe() return { txId, tx, destroy$ } From ef50d708007f36b280ee71b1c196df44962dcba4 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 10:31:13 -0500 Subject: [PATCH 3/8] more --- examples/light-client-dapp/src/components/Transfer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 7dce8f173..02670e420 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -56,6 +56,8 @@ export const Transfer = ({ provider }: Props) => { setIsSubmittingTransaction(true) setTransactionStatus("") + setFinalizedHash("") + try { const sender = selectedAccount.value const { txId, destroy$ } = await transfer(sender, destination, amount) From 7726e664327d08b76db21be5405cbba8507edf3d Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 11:08:51 -0500 Subject: [PATCH 4/8] gogo --- .../src/components/Transfer.tsx | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 02670e420..8eda1f542 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -8,6 +8,11 @@ type Props = { provider: UnstableWallet.Provider } +type FinalizedTransaction = { + blockHash: string + index: number +} + // FIXME: use dynamic chainId // Westend chainId const chainId = @@ -36,7 +41,8 @@ export const Transfer = ({ provider }: Props) => { chainId, ) const [transactionStatus, setTransactionStatus] = useState("") - const [finalizedHash, setFinalizedHash] = useState("") + const [finalizedTransaction, setFinalizedTransaction] = + useState() const balance = accountStorage?.data.free ?? 0n @@ -56,7 +62,7 @@ export const Transfer = ({ provider }: Props) => { setIsSubmittingTransaction(true) setTransactionStatus("") - setFinalizedHash("") + setFinalizedTransaction(null) try { const sender = selectedAccount.value @@ -74,7 +80,11 @@ export const Transfer = ({ provider }: Props) => { next: (e): void => { setTransactionStatus(e.type) if (e.type === "finalized") { - setFinalizedHash(e.block.hash) + e.block.index + setFinalizedTransaction({ + blockHash: e.block.hash, + index: e.block.index, + }) cleanup() } }, @@ -98,13 +108,7 @@ export const Transfer = ({ provider }: Props) => { label: account.address, })) - // TODO: handle form fields and submission with react - // TODO: fetch accounts from extension // TODO: validate destination address - // TODO: use PAPI to encode the transaction calldata - // TODO: transfer should trigger an extension popup that signs the transaction - // TODO: extract transaction submission into a hook - // TODO: follow transaction submission events until it is finalized return (
Transfer funds
@@ -138,10 +142,20 @@ export const Transfer = ({ provider }: Props) => { Transaction Status: {`${transactionStatus}`}

) : null} - {finalizedHash ? ( -

- Finalized Hash: {`${finalizedHash}`} -

+ {finalizedTransaction ? ( +
+

+ Finalized Block Hash:{" "} + + {`${finalizedTransaction.blockHash}`} + +

+

+ Transaction Index: {finalizedTransaction.index} +

+
) : null} From faa9ceb13bc489702bd8ea211953d3150d619ae1 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 14:27:44 -0500 Subject: [PATCH 5/8] use single tx --- examples/light-client-dapp/package.json | 4 +--- .../src/components/Transfer.tsx | 21 ++++------------ .../src/hooks/useTransfer.ts | 24 ++++--------------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/examples/light-client-dapp/package.json b/examples/light-client-dapp/package.json index ca1c45363..ffc4d5ba0 100644 --- a/examples/light-client-dapp/package.json +++ b/examples/light-client-dapp/package.json @@ -20,14 +20,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-select": "^5.8.0", - "rxjs": "^7.8.1", - "uuid": "^9.0.1" + "rxjs": "^7.8.1" }, "devDependencies": { "@substrate/unstable-wallet-provider": "workspace:^", "@types/react": "^18.2.60", "@types/react-dom": "^18.2.19", - "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 8eda1f542..11ed2263b 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -36,10 +36,7 @@ export const Transfer = ({ provider }: Props) => { connect, selectedAccount ? selectedAccount.value : null, ) - const { transfer, subscriptions: transferSubscriptions } = useTransfer( - { ...provider, connect }, - chainId, - ) + const { transfer } = useTransfer({ ...provider, connect }, chainId) const [transactionStatus, setTransactionStatus] = useState("") const [finalizedTransaction, setFinalizedTransaction] = useState() @@ -66,16 +63,10 @@ export const Transfer = ({ provider }: Props) => { try { const sender = selectedAccount.value - const { txId, destroy$ } = await transfer(sender, destination, amount) - - const cleanup = () => { - destroy$.next() - destroy$.complete() - delete transferSubscriptions[txId] - } + const { txEvents } = await transfer(sender, destination, amount) - lastValueFrom( - transferSubscriptions[txId].pipe( + await lastValueFrom( + txEvents.pipe( tap({ next: (e): void => { setTransactionStatus(e.type) @@ -85,12 +76,10 @@ export const Transfer = ({ provider }: Props) => { blockHash: e.block.hash, index: e.block.index, }) - cleanup() } }, error: (e) => { setTransactionStatus(e.type) - cleanup() }, }), ), @@ -100,7 +89,7 @@ export const Transfer = ({ provider }: Props) => { } setIsSubmittingTransaction(false) }, - [selectedAccount, transfer, destination, amount, transferSubscriptions], + [selectedAccount, transfer, destination, amount], ) const accountOptions = accounts.map((account) => ({ diff --git a/examples/light-client-dapp/src/hooks/useTransfer.ts b/examples/light-client-dapp/src/hooks/useTransfer.ts index ba2be23bf..4812bb3ba 100644 --- a/examples/light-client-dapp/src/hooks/useTransfer.ts +++ b/examples/light-client-dapp/src/hooks/useTransfer.ts @@ -7,19 +7,9 @@ import { createClient, } from "@polkadot-api/substrate-client" import { getDynamicBuilder } from "@polkadot-api/metadata-builders" -import { - firstValueFrom, - filter, - map, - Observable, - tap, - ReplaySubject, - Subject, - takeUntil, -} from "rxjs" +import { firstValueFrom, filter, map, tap, ReplaySubject } from "rxjs" import { mergeUint8, toHex } from "@polkadot-api/utils" import { UnstableWallet } from "@substrate/unstable-wallet-provider" -import { v4 as uuidv4 } from "uuid" import { ss58Decode } from "@polkadot-labs/hdkd-helpers" const AccountId = (value: SS58String) => @@ -34,8 +24,6 @@ const AccountId = (value: SS58String) => type Provider = UnstableWallet.Provider & { connect: ConnectProvider } export const useTransfer = (provider: Provider, chainId: string) => { - const subscriptions: Record> = {} - const transfer = async ( sender: SS58String, destination: SS58String, @@ -72,16 +60,12 @@ export const useTransfer = (provider: Provider, chainId: string) => { toHex(ss58Decode(sender)[0]), callData, ) - const txId = uuidv4() const txEvents = new ReplaySubject() - subscriptions[txId] = txEvents - - const destroy$ = new Subject() - client.tx$(tx).pipe(tap(txEvents), takeUntil(destroy$)).subscribe() + client.tx$(tx).pipe(tap(txEvents)).subscribe() - return { txId, tx, destroy$ } + return { tx, txEvents } } - return { subscriptions, transfer } + return { transfer } } From 206804479437250e3315df3dda25a841c7f2178f Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 1 Mar 2024 14:30:32 -0500 Subject: [PATCH 6/8] fix lockfile --- pnpm-lock.yaml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3819ba458..b8af7b935 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,9 +113,6 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.1 - uuid: - specifier: ^9.0.1 - version: 9.0.1 devDependencies: '@substrate/unstable-wallet-provider': specifier: workspace:^ @@ -126,9 +123,6 @@ importers: '@types/react-dom': specifier: ^18.2.19 version: 18.2.19 - '@types/uuid': - specifier: ^9.0.8 - version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^7.1.0 version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3) @@ -3888,10 +3882,6 @@ packages: resolution: {integrity: sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==} dev: false - /@types/uuid@9.0.8: - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - dev: true - /@types/webextension-polyfill@0.10.7: resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==} dev: true @@ -11053,11 +11043,6 @@ packages: hasBin: true dev: true - /uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - dev: false - /verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} From 5dc5fddf1871e8baa9e5ee08e95bd5da4e18d0f3 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 4 Mar 2024 14:08:32 -0500 Subject: [PATCH 7/8] fix: updates --- .../src/components/Transfer.tsx | 50 ++++++------- examples/light-client-dapp/src/hooks/index.ts | 1 - .../src/hooks/useTransfer.ts | 71 ------------------- examples/light-client-dapp/src/transaction.ts | 67 +++++++++++++++++ 4 files changed, 89 insertions(+), 100 deletions(-) delete mode 100644 examples/light-client-dapp/src/hooks/useTransfer.ts create mode 100644 examples/light-client-dapp/src/transaction.ts diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 11ed2263b..75059b9b6 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -1,8 +1,9 @@ import { FormEvent, useCallback, useEffect, useMemo, useState } from "react" import { UnstableWallet } from "@substrate/unstable-wallet-provider" import Select from "react-select" -import { useSystemAccount, useTransfer } from "../hooks" -import { lastValueFrom, tap } from "rxjs" +import { transaction, transferAllowDeathCallData } from "../transaction" +import { lastValueFrom, mergeMap, tap } from "rxjs" +import { useSystemAccount } from "../hooks" type Props = { provider: UnstableWallet.Provider @@ -36,7 +37,6 @@ export const Transfer = ({ provider }: Props) => { connect, selectedAccount ? selectedAccount.value : null, ) - const { transfer } = useTransfer({ ...provider, connect }, chainId) const [transactionStatus, setTransactionStatus] = useState("") const [finalizedTransaction, setFinalizedTransaction] = useState() @@ -61,35 +61,29 @@ export const Transfer = ({ provider }: Props) => { setTransactionStatus("") setFinalizedTransaction(null) - try { - const sender = selectedAccount.value - const { txEvents } = await transfer(sender, destination, amount) + const sender = selectedAccount.value + const connectProvider = { ...provider, connect } - await lastValueFrom( - txEvents.pipe( - tap({ - next: (e): void => { - setTransactionStatus(e.type) - if (e.type === "finalized") { - e.block.index - setFinalizedTransaction({ - blockHash: e.block.hash, - index: e.block.index, - }) - } - }, - error: (e) => { - setTransactionStatus(e.type) - }, - }), + await lastValueFrom( + transferAllowDeathCallData(connectProvider, destination, amount).pipe( + mergeMap((callData) => + transaction(connectProvider, chainId, sender, callData), ), - ) - } catch (error) { - console.error(error) - } + tap(({ txEvent }) => { + setTransactionStatus(txEvent.type) + if (txEvent.type === "finalized") { + setFinalizedTransaction({ + blockHash: txEvent.block.hash, + index: txEvent.block.index, + }) + } + }), + ), + ) + setIsSubmittingTransaction(false) }, - [selectedAccount, transfer, destination, amount], + [selectedAccount, provider, connect, destination, amount], ) const accountOptions = accounts.map((account) => ({ diff --git a/examples/light-client-dapp/src/hooks/index.ts b/examples/light-client-dapp/src/hooks/index.ts index 3029e3930..e23feee2e 100644 --- a/examples/light-client-dapp/src/hooks/index.ts +++ b/examples/light-client-dapp/src/hooks/index.ts @@ -1,3 +1,2 @@ export * from "./useProvider" export * from "./useSystemAccount" -export * from "./useTransfer" diff --git a/examples/light-client-dapp/src/hooks/useTransfer.ts b/examples/light-client-dapp/src/hooks/useTransfer.ts deleted file mode 100644 index 4812bb3ba..000000000 --- a/examples/light-client-dapp/src/hooks/useTransfer.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { getObservableClient } from "@polkadot-api/client" -import { Enum } from "@polkadot-api/substrate-bindings" -import type { SS58String } from "@polkadot-api/substrate-bindings" -import { - ConnectProvider, - TxEvent, - createClient, -} from "@polkadot-api/substrate-client" -import { getDynamicBuilder } from "@polkadot-api/metadata-builders" -import { firstValueFrom, filter, map, tap, ReplaySubject } from "rxjs" -import { mergeUint8, toHex } from "@polkadot-api/utils" -import { UnstableWallet } from "@substrate/unstable-wallet-provider" -import { ss58Decode } from "@polkadot-labs/hdkd-helpers" - -const AccountId = (value: SS58String) => - Enum< - { - type: "Id" - value: SS58String - }, - "Id" - >("Id", value) - -type Provider = UnstableWallet.Provider & { connect: ConnectProvider } - -export const useTransfer = (provider: Provider, chainId: string) => { - const transfer = async ( - sender: SS58String, - destination: SS58String, - amount: bigint, - ) => { - const client = getObservableClient(createClient(provider.connect)) - const { metadata$ } = client.chainHead$() - - const callData = await firstValueFrom( - metadata$.pipe( - filter(Boolean), - map((metadata) => { - const dynamicBuilder = getDynamicBuilder(metadata) - const { location, args } = dynamicBuilder.buildCall( - "Balances", - "transfer_allow_death", - ) - - return toHex( - mergeUint8( - new Uint8Array(location), - args.enc({ - dest: AccountId(destination), - value: amount, - }), - ), - ) - }), - ), - ) - - const tx = await provider.createTx( - chainId, - toHex(ss58Decode(sender)[0]), - callData, - ) - const txEvents = new ReplaySubject() - - client.tx$(tx).pipe(tap(txEvents)).subscribe() - - return { tx, txEvents } - } - - return { transfer } -} diff --git a/examples/light-client-dapp/src/transaction.ts b/examples/light-client-dapp/src/transaction.ts new file mode 100644 index 000000000..d48e1d09b --- /dev/null +++ b/examples/light-client-dapp/src/transaction.ts @@ -0,0 +1,67 @@ +import { getObservableClient } from "@polkadot-api/client" +import { Enum } from "@polkadot-api/substrate-bindings" +import type { SS58String } from "@polkadot-api/substrate-bindings" +import { ConnectProvider, createClient } from "@polkadot-api/substrate-client" +import { getDynamicBuilder } from "@polkadot-api/metadata-builders" +import { filter, map, mergeMap, first } from "rxjs" +import { mergeUint8, toHex } from "@polkadot-api/utils" +import { UnstableWallet } from "@substrate/unstable-wallet-provider" +import { ss58Decode } from "@polkadot-labs/hdkd-helpers" +import { fromPromise } from "rxjs/internal/observable/innerFrom" + +const AccountId = (value: SS58String) => + Enum< + { + type: "Id" + value: SS58String + }, + "Id" + >("Id", value) + +type Provider = UnstableWallet.Provider & { connect: ConnectProvider } + +export const transaction = ( + provider: Provider, + chainId: string, + from: SS58String, + callData: string, +) => { + const client = getObservableClient(createClient(provider.connect)) + + return fromPromise( + provider.createTx(chainId, toHex(ss58Decode(from)[0]), callData), + ).pipe( + mergeMap((tx) => client.tx$(tx).pipe(map((txEvent) => ({ tx, txEvent })))), + ) +} + +export const transferAllowDeathCallData = ( + provider: Provider, + destination: SS58String, + amount: bigint, +) => { + const client = getObservableClient(createClient(provider.connect)) + const { metadata$ } = client.chainHead$() + + return metadata$.pipe( + filter(Boolean), + map((metadata) => { + const dynamicBuilder = getDynamicBuilder(metadata) + const { location, args } = dynamicBuilder.buildCall( + "Balances", + "transfer_allow_death", + ) + + return toHex( + mergeUint8( + new Uint8Array(location), + args.enc({ + dest: AccountId(destination), + value: amount, + }), + ), + ) + }), + first(), + ) +} From 8c45ac846e67bb0f5439c44bb5fc40e737e574e7 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 4 Mar 2024 14:38:16 -0500 Subject: [PATCH 8/8] fixes --- .../src/components/Transfer.tsx | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/examples/light-client-dapp/src/components/Transfer.tsx b/examples/light-client-dapp/src/components/Transfer.tsx index 75059b9b6..917b5f173 100644 --- a/examples/light-client-dapp/src/components/Transfer.tsx +++ b/examples/light-client-dapp/src/components/Transfer.tsx @@ -40,6 +40,7 @@ export const Transfer = ({ provider }: Props) => { const [transactionStatus, setTransactionStatus] = useState("") const [finalizedTransaction, setFinalizedTransaction] = useState() + const [error, setError] = useState<{ type: string; error: string }>() const balance = accountStorage?.data.free ?? 0n @@ -64,22 +65,32 @@ export const Transfer = ({ provider }: Props) => { const sender = selectedAccount.value const connectProvider = { ...provider, connect } - await lastValueFrom( - transferAllowDeathCallData(connectProvider, destination, amount).pipe( - mergeMap((callData) => - transaction(connectProvider, chainId, sender, callData), + try { + await lastValueFrom( + transferAllowDeathCallData(connectProvider, destination, amount).pipe( + mergeMap((callData) => + transaction(connectProvider, chainId, sender, callData), + ), + tap(({ txEvent }) => { + setTransactionStatus(txEvent.type) + if (txEvent.type === "finalized") { + setFinalizedTransaction({ + blockHash: txEvent.block.hash, + index: txEvent.block.index, + }) + } + if (txEvent.type === "invalid" || txEvent.type === "dropped") { + setError({ type: txEvent.type, error: txEvent.error }) + } + }), ), - tap(({ txEvent }) => { - setTransactionStatus(txEvent.type) - if (txEvent.type === "finalized") { - setFinalizedTransaction({ - blockHash: txEvent.block.hash, - index: txEvent.block.index, - }) - } - }), - ), - ) + ) + } catch (err) { + if (err instanceof Error) { + setError({ type: "error", error: err.message }) + } + console.error(err) + } setIsSubmittingTransaction(false) }, @@ -140,6 +151,11 @@ export const Transfer = ({ provider }: Props) => {

) : null} + {error ? ( +

+ Error: {`type: ${error.type}, error: ${error.error}`} +

+ ) : null}