From a849f625a3931ddce6e335c7b4f26e48ee52a726 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Tue, 10 Sep 2024 12:00:21 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20optimize=20scdo=E3=80=81alephium=20exa?= =?UTF-8?q?mple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/chains/alephium/example.tsx | 87 ++++++++++- .../components/chains/scdo/example.tsx | 147 +++++++++++++++--- .../example/components/chains/scdo/params.ts | 19 ++- .../example/components/chains/scdo/rpc.ts | 40 +++-- .../example/components/chains/scdo/utils.ts | 7 + 5 files changed, 260 insertions(+), 40 deletions(-) create mode 100644 packages/example/components/chains/scdo/utils.ts diff --git a/packages/example/components/chains/alephium/example.tsx b/packages/example/components/chains/alephium/example.tsx index a64843777..f96b6d4cd 100644 --- a/packages/example/components/chains/alephium/example.tsx +++ b/packages/example/components/chains/alephium/example.tsx @@ -12,12 +12,13 @@ import { } from '@alephium/web3-react'; import { verifySignedMessage } from '@alephium/web3'; -import { NodeProvider, TransactionBuilder, buildScriptByteCode, buildContractByteCode, ONE_ALPH, DUST_AMOUNT } from "@alephium/web3" +import { NodeProvider, TransactionBuilder, DUST_AMOUNT } from "@alephium/web3" import * as fetchRetry from 'fetch-retry' import { useState } from 'react'; import { RadioGroup, RadioGroupItem } from '../../ui/radio-group'; import { Label } from '../../ui/label'; import { useToast } from '../../ui/use-toast'; +import { Input } from '../../ui/input'; // 防止限频 const retryFetch = fetchRetry.default(fetch, { @@ -33,6 +34,88 @@ export function Example() { const balance = useBalance(); const { toast } = useToast(); + const getTokenTransferFrom = (chainId: string | undefined, approve: boolean = false) => { + const tokens: { + name: string; + address: string; + }[] = []; + + tokens.push({ + name: 'USDC', + address: '722954d9067c5a5ad532746a024f2a9d7a18ed9b90e27d0a3a504962160b5600', + }); + tokens.push({ + name: 'USDT', + address: '556d9582463fe44fbd108aedc9f409f69086dc78d994b88ea6c9e65f8bf98e00', + }); + + return ( + <> + + + + + + ); + }; + + const tokenTransferFromToTx = async (fromData: Record) => { + const from = wallet?.account?.address ?? ''; + const senderPublicKey = wallet?.account?.publicKey ?? ''; + const to = fromData.toAddress ?? from; + const amount = fromData.amount; + const tokenAddress = fromData.tokenAddress; + + if (!amount) { + return 'Amount is required'; + } + + const builder = TransactionBuilder.from(nodeUrl) + + if (tokenAddress && tokenAddress !== '选择 Token') { + + const buildTxResultScript = await builder.buildTransferTx( + { + signerAddress: from, + destinations: [ + { address: to, attoAlphAmount: amount }, + ] + }, + senderPublicKey + ) + + return JSON.stringify({ + unsignedTx: buildTxResultScript.unsignedTx, + signerAddress: from, + }) + } + + const buildTxResultScript = await builder.buildTransferTx( + { + signerAddress: from, + destinations: [ + { address: to, attoAlphAmount: DUST_AMOUNT, tokens: [{ id: tokenAddress, amount: amount }] }, + ] + }, + senderPublicKey + ) + + return JSON.stringify({ + unsignedTx: buildTxResultScript.unsignedTx, + signerAddress: from, + }) + } + return ( <> @@ -100,6 +183,8 @@ export function Example() { description="" allowCallWithoutProvider={!!wallet} presupposeParams={params.signUnsignedTx(wallet?.account?.address ?? '')} + generateRequestFrom={() => getTokenTransferFrom(wallet?.account?.address ?? '')} + onGenerateRequest={tokenTransferFromToTx} onExecute={async (request: string) => { return wallet.signer.signUnsignedTx(JSON.parse(request)); }} diff --git a/packages/example/components/chains/scdo/example.tsx b/packages/example/components/chains/scdo/example.tsx index c3b98a0cb..55b0a2390 100644 --- a/packages/example/components/chains/scdo/example.tsx +++ b/packages/example/components/chains/scdo/example.tsx @@ -11,6 +11,9 @@ import DappList from '../../../components/DAppList'; import params from './params'; import { Input } from '../../ui/input'; import { ScdoNodeClient } from './rpc'; +import BigNumber from 'bignumber.js'; +import { useToast } from '../../ui/use-toast'; +import { encodeTokenTransferPayload } from './utils'; // https://demo.scdo.org/ export default function Example() { @@ -30,6 +33,7 @@ export default function Example() { const client = new ScdoNodeClient(); const { account, provider } = useWallet(); + const { toast } = useToast(); const onConnectWallet = async (selectedWallet: IKnownWallet) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -49,6 +53,16 @@ export default function Example() { }; const getTokenTransferFrom = (chainId: string | undefined, approve: boolean = false) => { + const tokens: { + name: string; + address: string; + }[] = []; + + tokens.push({ + name: 'Test Token', + address: '1S015daca201b66f96f74b4230916f9db8db0c0002', + }); + return ( <> )} + + ); }; + const tokenTransferFromToTx = async (fromData: Record) => { + const from = account?.address ?? ''; + const to = fromData.toAddress ?? from; + const amount = fromData.amount; + const tokenAddress = fromData.tokenAddress; + + if (!amount) { + return 'Amount is required'; + } + + const nonce = await client.getNonce(from); + + if (tokenAddress && tokenAddress !== '选择 Token') { + const payload = encodeTokenTransferPayload({ address: to, amount }); + + return JSON.stringify({ + accountNonce: nonce, + from: from, + to: tokenAddress, + amount: 0, + gasLimit: 100000, + gasPrice: 1, + payload: payload, + }); + } + + return JSON.stringify({ + accountNonce: nonce, + from: from, + to: to, + amount: new BigNumber(amount).toNumber(), + gasLimit: 21000, + gasPrice: 1, + payload: '', + }); + } + return ( <> @@ -154,14 +212,52 @@ export default function Example() { getTokenTransferFrom(account?.chainId)} + onGenerateRequest={tokenTransferFromToTx} onExecute={async (request: string) => { const tx = JSON.parse(request); return await provider?.request({ - method: 'scdo_estimateGas', + method: 'scdo_signTransaction', params: [tx], }); }} + onValidate={async (request: string, response: string) => { + const res = JSON.parse(response) as { + "Data": { + "Type": number, + "From": string, + "To": string, + "Amount": number, + "AccountNonce": number, + "GasPrice": number, + "GasLimit": number, + "Timestamp": number, + "Payload": string + }, + "Hash": string, + "Signature": { + "Sig": string + } + } + return await provider?.request({ + method: 'scdo_estimateGas', + params: [{ + 'accountNonce': res.Data.AccountNonce, + 'amount': res.Data.Amount, + 'from': res.Data.From, + 'to': res.Data.To, + 'gasLimit': res.Data.GasLimit, + 'gasPrice': res.Data.GasPrice, + 'hash': res.Hash, + 'payload': res.Data.Payload, + 'signature': { + 'Sig': res.Signature.Sig, + }, + + }], + }); + }} /> getTokenTransferFrom(account?.chainId)} - onGenerateRequest={async (fromData: Record) => { - const from = account?.address ?? ''; - const to = fromData.toAddress ?? from; - const amount = fromData.amount; - - if (!amount) { - return 'Amount is required'; + onValidate={async (request: string, response: string) => { + const tx = JSON.parse(response); + const success = await client.pushTransaction(account?.address ?? '', tx); + if (!success) { + toast({ + title: '广播交易失败', + description: '请检查交易是否正确', + variant: 'destructive', + }); } - - const nonce = await client.getNonce(from); - console.log('nonce', nonce); - - return JSON.stringify({ - accountNonce: nonce, - from: from, - to: to, - amount: amount, - gasLimit: 21000, - gasPrice: 1, - payload: '', + return success; + }} + generateRequestFrom={() => getTokenTransferFrom(account?.chainId)} + onGenerateRequest={tokenTransferFromToTx} + /> + getTokenTransferFrom(account?.chainId)} + onGenerateRequest={tokenTransferFromToTx} + onExecute={async (request: string) => { + const tx = JSON.parse(request); + return await provider?.request({ + method: 'scdo_sendTransaction', + params: [tx], }); }} /> { const tx = JSON.parse(request); diff --git a/packages/example/components/chains/scdo/params.ts b/packages/example/components/chains/scdo/params.ts index 29f7b1a3a..c836ad432 100644 --- a/packages/example/components/chains/scdo/params.ts +++ b/packages/example/components/chains/scdo/params.ts @@ -9,7 +9,6 @@ export default { from, gasLimit: 21000, gasPrice: 1, - hash: '0x258791d390b94380b64ad72efb7b434489d8298db770e796f42dc3876de49ca4', payload: '', to, }), @@ -26,15 +25,24 @@ export default { }), }, { - id: 'sendTransaction-contract', - name: 'Contract', + id: 'sendTransaction-erc20-contract', + name: 'ERC20 Token', value: JSON.stringify({ from: from, payload: - '0xa9059cbb000000000000000000000000016cc151292ade2936ca8b1764240061f9673c51000000000000000000000000000000000000000000000000000000000000000a', - to: '', + '0xa9059cbb0000000000000000000000000118a02f993fc7a4348fd36b7f7a596948f02b310000000000000000000000000000000000000000000000000000000000002710', + to: '1S015daca201b66f96f74b4230916f9db8db0c0002', }), }, + // { + // id: 'sendTransaction-big-payload', + // name: 'Big Payload', + // value: JSON.stringify({ + // from: from, + // payload: `0x${'010203040506070809'.repeat(600)}`, + // to: from, + // }), + // }, ], estimateGas: (from: string, to: string) => [ { @@ -46,7 +54,6 @@ export default { 'from': from, 'gasLimit': 21000, 'gasPrice': 1, - 'hash': '0x258791d390b94380b64ad72efb7b434489d8298db770e796f42dc3876de49ca4', 'payload': '', 'signature': { 'Sig': diff --git a/packages/example/components/chains/scdo/rpc.ts b/packages/example/components/chains/scdo/rpc.ts index 7f75a27f7..2e4a23ffd 100644 --- a/packages/example/components/chains/scdo/rpc.ts +++ b/packages/example/components/chains/scdo/rpc.ts @@ -12,18 +12,19 @@ export class ScdoNodeClient { } private getUrl(address: string) { - const shard = { - '1': 'https://mainnet.scdo.org:8137', - '2': 'https://mainnet.scdo.org:8138', - '3': 'https://mainnet.scdo.org:8139', - '4': 'https://mainnet.scdo.org:8136', - }; - - return shard[address.charAt(0) as keyof typeof shard] ?? shard['1']; + const shard = [ + 'https://mainnet.scdo.org:8137', + 'https://mainnet.scdo.org:8138', + 'https://mainnet.scdo.org:8139', + 'https://mainnet.scdo.org:8136', + ]; + + const index = +address.slice(0, 1) - 1; + return shard[index] ?? shard[0]; } public async getNonce(address: string): Promise { - const response = await this.axios.post( + const response = await this.axios.post<{ result: number }>( this.getUrl(address), { jsonrpc: '2.0', @@ -42,6 +43,25 @@ export class ScdoNodeClient { return null; } - return 0; + return response.data.result ?? 0; + } + + public async pushTransaction(address: string, tx: any): Promise { + const response = await this.axios.post<{ result: boolean }>( + this.getUrl(address), + { + jsonrpc: '2.0', + method: 'scdo_addTx', + params: [tx], + id: 1, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + return response.data.result ?? false; } } diff --git a/packages/example/components/chains/scdo/utils.ts b/packages/example/components/chains/scdo/utils.ts new file mode 100644 index 000000000..56c2e9ac5 --- /dev/null +++ b/packages/example/components/chains/scdo/utils.ts @@ -0,0 +1,7 @@ +import { defaultAbiCoder } from '@ethersproject/abi'; + +export function encodeTokenTransferPayload({ address, amount }: { address: string; amount: string }) { + const method = '0xa9059cbb'; + const params = defaultAbiCoder.encode(['address', 'uint256'], [`0x${address.slice(2)}`, amount]); + return `${method}${params.slice(2)}`; +}