From 685c7f8fd227b915a936f7e35798e18caf82251f Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Fri, 16 May 2025 13:15:57 +0200 Subject: [PATCH 1/5] Added polkadot/api and rewrite sendTransaction --- advanced/dapps/react-dapp-v2/package.json | 1 + .../src/contexts/JsonRpcContext.tsx | 43 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/advanced/dapps/react-dapp-v2/package.json b/advanced/dapps/react-dapp-v2/package.json index d1f3e0c58..9903c4238 100644 --- a/advanced/dapps/react-dapp-v2/package.json +++ b/advanced/dapps/react-dapp-v2/package.json @@ -21,6 +21,7 @@ "@multiversx/sdk-wallet": "4.2.0", "@noble/curves": "^1.8.1", "@noble/secp256k1": "^2.2.3", + "@polkadot/api": "^15.10.2", "@polkadot/util-crypto": "^10.1.2", "@solana/web3.js": "^1.36.0", "@walletconnect/core": "^2.19.1", diff --git a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index 8891bdcb5..0999d6a7b 100644 --- a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -4,6 +4,7 @@ import * as encoding from "@walletconnect/encoding"; import { Transaction as EthTransaction } from "@ethereumjs/tx"; import { recoverTransaction } from "@celo/wallet-base"; import * as bitcoin from "bitcoinjs-lib"; +import { ApiPromise, WsProvider } from "@polkadot/api"; import { formatDirectSignDoc, @@ -84,6 +85,7 @@ import { } from "../helpers/bip122"; import { getAddressFromAccount } from "@walletconnect/utils"; import { BIP122_DUST_LIMIT } from "../chains/bip122"; +import { PolkadotChainData } from "../chains/polkadot"; /** * Types @@ -966,18 +968,28 @@ export function JsonRpcContextProvider({ chainId: string, address: string ): Promise => { + // Initialize API + + const [namespace, reference] = chainId.split(":"); + const targetChainData = chainData[namespace][reference]; + const wsProvider = new WsProvider(targetChainData.rpc); + const api = await ApiPromise.create({ provider: wsProvider }); + + const call = api.tx.balances.transfer(address, 1000000000000); // 1 DOT + + const runtime = await api.rpc.state.getRuntimeVersion(); + const blockHash = await api.rpc.chain.getBlockHash(); + const blockNumber = await api.rpc.chain.getHeader(); + const transactionPayload = { - specVersion: "0x00002468", - transactionVersion: "0x0000000e", + specVersion: runtime.specVersion.toHex(), + transactionVersion: runtime.transactionVersion.toHex(), address: `${address}`, - blockHash: - "0x554d682a74099d05e8b7852d19c93b527b5fae1e9e1969f6e1b82a2f09a14cc9", - blockNumber: "0x00cb539c", + blockHash: blockHash.toHex(), + blockNumber: blockNumber.number.toHex(), era: "0xc501", - genesisHash: - "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e", - method: - "0x0001784920616d207369676e696e672074686973207472616e73616374696f6e21", + genesisHash: api.genesisHash.toHex(), + method: call.method.toHex(), nonce: "0x00000000", signedExtensions: [ "CheckNonZeroSender", @@ -990,7 +1002,7 @@ export function JsonRpcContextProvider({ "ChargeTransactionPayment", ], tip: "0x00000000000000000000000000000000", - version: 4, + version: api.extrinsicVersion }; const result = await client!.request<{ @@ -1008,6 +1020,17 @@ export function JsonRpcContextProvider({ }, }); + // result = { signature: '0x...' } + const extrinsic = api.createType('Extrinsic', call); + extrinsic.addSignature(address, `0x${result.signature.replaceAll('0x', '')}`, { + ...transactionPayload, + specVersion: api.runtimeVersion.specVersion, + transactionVersion: api.runtimeVersion.transactionVersion, + }); + + const txHash = api.registry.hash(extrinsic.toU8a()).toHex(); + console.log('Transaction hash:', txHash); + return { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION, address, From b0eb5d649c6e016358e644c4529c76efd9e08b36 Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Tue, 20 May 2025 13:14:34 +0200 Subject: [PATCH 2/5] Fix for linter error. --- .../src/contexts/JsonRpcContext.tsx | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index 0999d6a7b..319533ffb 100644 --- a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -197,29 +197,29 @@ export function JsonRpcContextProvider({ address: string ) => Promise ) => - async (chainId: string, address: string) => { - if (typeof client === "undefined") { - throw new Error("WalletConnect is not initialized"); - } - if (typeof session === "undefined") { - throw new Error("Session is not connected"); - } + async (chainId: string, address: string) => { + if (typeof client === "undefined") { + throw new Error("WalletConnect is not initialized"); + } + if (typeof session === "undefined") { + throw new Error("Session is not connected"); + } - try { - setPending(true); - const result = await rpcRequest(chainId, address); - setResult(result); - } catch (err: any) { - console.error("RPC request failed: ", err); - setResult({ - address, - valid: false, - result: err?.message ?? err, - }); - } finally { - setPending(false); - } - }; + try { + setPending(true); + const result = await rpcRequest(chainId, address); + setResult(result); + } catch (err: any) { + console.error("RPC request failed: ", err); + setResult({ + address, + valid: false, + result: err?.message ?? err, + }); + } finally { + setPending(false); + } + }; const _verifyEip155MessageSignature = ( message: string, @@ -885,11 +885,11 @@ export function JsonRpcContextProvider({ ...key, pubkey: key.pubkey.toBase58(), })), - data: bs58.encode(instruction.data), + data: bs58.encode(new Uint8Array(instruction.data)), })), partialSignatures: transaction.signatures.map((sign) => ({ pubkey: sign.publicKey.toBase58(), - signature: bs58.encode(sign.signature!), + signature: bs58.encode(new Uint8Array(sign.signature!)), })), transaction: transaction .serialize({ verifySignatures: false }) @@ -1587,9 +1587,8 @@ export function JsonRpcContextProvider({ } const pactCommand = new PactCommand(); - pactCommand.code = `(coin.transfer "${ - kadenaAccount.account - }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; + pactCommand.code = `(coin.transfer "${kadenaAccount.account + }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; pactCommand .setMeta( @@ -1640,9 +1639,8 @@ export function JsonRpcContextProvider({ } const pactCommand = new PactCommand(); - pactCommand.code = `(coin.transfer "${ - kadenaAccount.account - }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; + pactCommand.code = `(coin.transfer "${kadenaAccount.account + }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; pactCommand .setMeta( @@ -1872,7 +1870,7 @@ export function JsonRpcContextProvider({ }; const addresses = session?.sessionProperties?.[ - `bip122_${DEFAULT_BIP122_METHODS.BIP122_GET_ACCOUNT_ADDRESSES}` + `bip122_${DEFAULT_BIP122_METHODS.BIP122_GET_ACCOUNT_ADDRESSES}` ]; let result; if (addresses) { From 4f4b8bf0ba47404ea35439ce2533e01db2e3b5e7 Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Fri, 23 May 2025 09:49:57 +0200 Subject: [PATCH 3/5] revert unwanted changes --- .../src/contexts/JsonRpcContext.tsx | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index 319533ffb..303cb7d58 100644 --- a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -197,29 +197,29 @@ export function JsonRpcContextProvider({ address: string ) => Promise ) => - async (chainId: string, address: string) => { - if (typeof client === "undefined") { - throw new Error("WalletConnect is not initialized"); - } - if (typeof session === "undefined") { - throw new Error("Session is not connected"); - } + async (chainId: string, address: string) => { + if (typeof client === "undefined") { + throw new Error("WalletConnect is not initialized"); + } + if (typeof session === "undefined") { + throw new Error("Session is not connected"); + } - try { - setPending(true); - const result = await rpcRequest(chainId, address); - setResult(result); - } catch (err: any) { - console.error("RPC request failed: ", err); - setResult({ - address, - valid: false, - result: err?.message ?? err, - }); - } finally { - setPending(false); - } - }; + try { + setPending(true); + const result = await rpcRequest(chainId, address); + setResult(result); + } catch (err: any) { + console.error("RPC request failed: ", err); + setResult({ + address, + valid: false, + result: err?.message ?? err, + }); + } finally { + setPending(false); + } + }; const _verifyEip155MessageSignature = ( message: string, @@ -1022,7 +1022,8 @@ export function JsonRpcContextProvider({ // result = { signature: '0x...' } const extrinsic = api.createType('Extrinsic', call); - extrinsic.addSignature(address, `0x${result.signature.replaceAll('0x', '')}`, { + const normalizedSignature = result.signature.replaceAll('0x', ''); + extrinsic.addSignature(address, `0x${normalizedSignature}`, { ...transactionPayload, specVersion: api.runtimeVersion.specVersion, transactionVersion: api.runtimeVersion.transactionVersion, @@ -1587,8 +1588,9 @@ export function JsonRpcContextProvider({ } const pactCommand = new PactCommand(); - pactCommand.code = `(coin.transfer "${kadenaAccount.account - }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; + pactCommand.code = `(coin.transfer "${ + kadenaAccount.account + }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; pactCommand .setMeta( @@ -1639,8 +1641,9 @@ export function JsonRpcContextProvider({ } const pactCommand = new PactCommand(); - pactCommand.code = `(coin.transfer "${kadenaAccount.account - }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; + pactCommand.code = `(coin.transfer "${ + kadenaAccount.account + }" "k:abcabcabcabc" ${new PactNumber(1).toDecimal()})`; pactCommand .setMeta( @@ -1870,7 +1873,7 @@ export function JsonRpcContextProvider({ }; const addresses = session?.sessionProperties?.[ - `bip122_${DEFAULT_BIP122_METHODS.BIP122_GET_ACCOUNT_ADDRESSES}` + `bip122_${DEFAULT_BIP122_METHODS.BIP122_GET_ACCOUNT_ADDRESSES}` ]; let result; if (addresses) { From 18fb5c43869247413537e5fa628f00ceb2540c74 Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Fri, 23 May 2025 10:52:06 +0200 Subject: [PATCH 4/5] change properly using polkadot api --- .../src/contexts/JsonRpcContext.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index 303cb7d58..d625188d6 100644 --- a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -969,39 +969,41 @@ export function JsonRpcContextProvider({ address: string ): Promise => { // Initialize API - const [namespace, reference] = chainId.split(":"); const targetChainData = chainData[namespace][reference]; const wsProvider = new WsProvider(targetChainData.rpc); const api = await ApiPromise.create({ provider: wsProvider }); - const call = api.tx.balances.transfer(address, 1000000000000); // 1 DOT + // Wait for API to be ready + await api.isReady; + + // Create transfer transaction + const transferAmount = 1000000000000; // 1 DOT + const call = api.tx.balances.transferKeepAlive(address, transferAmount); - const runtime = await api.rpc.state.getRuntimeVersion(); - const blockHash = await api.rpc.chain.getBlockHash(); - const blockNumber = await api.rpc.chain.getHeader(); + // Get the latest block info + const [runtime, blockHash, blockNumber] = await Promise.all([ + api.rpc.state.getRuntimeVersion(), + api.rpc.chain.getBlockHash(), + api.rpc.chain.getHeader() + ]); + // Get the nonce for the address + const nonce = await api.rpc.system.accountNextIndex(address); + + // Create the transaction payload using the API's built-in functionality const transactionPayload = { specVersion: runtime.specVersion.toHex(), transactionVersion: runtime.transactionVersion.toHex(), - address: `${address}`, + address, blockHash: blockHash.toHex(), blockNumber: blockNumber.number.toHex(), - era: "0xc501", + era: api.registry.createType('ExtrinsicEra', { current: blockNumber.number.toNumber(), period: 64 }).toHex(), genesisHash: api.genesisHash.toHex(), method: call.method.toHex(), - nonce: "0x00000000", - signedExtensions: [ - "CheckNonZeroSender", - "CheckSpecVersion", - "CheckTxVersion", - "CheckGenesis", - "CheckMortality", - "CheckNonce", - "CheckWeight", - "ChargeTransactionPayment", - ], - tip: "0x00000000000000000000000000000000", + nonce: nonce.toHex(), + signedExtensions: api.registry.signedExtensions, + tip: api.registry.createType('Compact', 0).toHex(), version: api.extrinsicVersion }; From 65a07578becad6d56650de3a775b6bd352314b3a Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Fri, 23 May 2025 15:20:41 +0200 Subject: [PATCH 5/5] added possible code to read and check extrinsic hash --- .../src/contexts/JsonRpcContext.tsx | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index d625188d6..9a147cb0c 100644 --- a/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -1033,6 +1033,67 @@ export function JsonRpcContextProvider({ const txHash = api.registry.hash(extrinsic.toU8a()).toHex(); console.log('Transaction hash:', txHash); + + // Test transaction hash recreation + // Get a known block + // const knownBlock = await api.rpc.chain.getBlock('0x598680c4e967f0708e99461525aef1fb432eb702ec8f479e5148cedf8bd831f7'); + // if (knownBlock) { + // // This is a known transaction hash from Polkadot mainnet on that block + // const knownTxHash = '0x7ed875350c126762c56ddb9428400e6ef4d950e9b66e2e9a16ab361fd4a018e3'; + + // // Get block hash and block number from the block containing the extrinsic + // const blockHash = knownBlock.block.header.hash; + // const blockNumber = knownBlock.block.header.number; + + // const metadataAt = await api.rpc.state.getMetadata(blockHash.toHex()); + // api.registry.setMetadata(metadataAt); + // const signedExtensions = api.registry.signedExtensions; + + // // Get latest extrinsic in the block, which is the known extrinsic + // const knownExtrinsic = knownBlock.block.extrinsics[knownBlock.block.extrinsics.length - 1]; + // // const extrinsicSignature = knownExtrinsic.signature.toHuman(); + // // console.log('Extrinsic signature:', extrinsicSignature); + // const extrinsicHash = api.registry.hash(knownExtrinsic.toU8a()).toHex(); + // console.log('Hash matches:', extrinsicHash === knownTxHash); + // // console.log('Extrinsic details:', knownExtrinsic.toHuman()); + // console.log('Extrinsic JSON:', JSON.stringify(knownExtrinsic.toHuman(), null, 2)); + // console.log('Signer public key:', knownExtrinsic.signer.toHex()); + + // // Get the runtime version at that block + // const runtimeAtBlock = await api.rpc.state.getRuntimeVersion(blockHash); + // // Extract original transaction payload + // const originalCall = knownExtrinsic.method; + + // const originalPayload = { + // 'specVersion': runtimeAtBlock.specVersion.toHex(), + // 'transactionVersion': runtimeAtBlock.transactionVersion.toHex(), + // 'address': knownExtrinsic.signer.toString(), + // 'blockHash': blockHash.toHex(), + // 'blockNumber': blockNumber.toHex(), + // 'era': knownExtrinsic.era.toHex(), + // 'genesisHash': api.genesisHash.toHex(), // TODO CHECK THIS + // 'method': originalCall.toHex(), + // 'nonce': knownExtrinsic.nonce.toHex(), + // 'signedExtensions': signedExtensions, + // 'tip': knownExtrinsic.tip?.toHex() || '0x00', + // 'version': knownExtrinsic.version, + // }; + // console.log('Original payload JSON:', JSON.stringify(originalPayload, null, 2)); + + // // Create a new extrinsic from the original payload + // const newExtrinsic = api.createType('Extrinsic', originalCall); + // const cryptoTypePrefix = '01'; // Sr25519 + // const rawSig = knownExtrinsic.signature.toHex().replace(/^0x/, ''); + // const fullSignature = `${cryptoTypePrefix}${rawSig}`; + + // const payload = api.registry.createType('ExtrinsicPayload', originalPayload).toJSON(); + + // newExtrinsic.addSignature(knownExtrinsic.signer.toString(), `0x${fullSignature}`, payload); + + // const newTxHash = api.registry.hash(newExtrinsic.toU8a()).toHex(); + // console.log('New transaction hash:', newTxHash); + // console.log('Hash matches:', newTxHash === knownTxHash); + // } return { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION,