import { BigNumber, utils } from "ethers"; import { createContext, ReactNode, useContext, useState } from "react"; import * as encoding from "@walletconnect/encoding"; import { Transaction as EthTransaction } from "@ethereumjs/tx"; import { recoverTransaction } from "@celo/wallet-base"; import { formatDirectSignDoc, stringifySignDocValues, verifyAminoSignature, verifyDirectSignature, } from "cosmos-wallet"; import bs58 from "bs58"; import { verifyMessageSignature } from "solana-wallet"; import { Connection, Keypair, SystemProgram, Transaction as SolanaTransaction, clusterApiUrl, } from "@solana/web3.js"; // @ts-expect-error import TronWeb from "tronweb"; import { eip712, formatTestTransaction, getLocalStorageTestnetFlag, getProviderUrl, hashPersonalMessage, hashTypedDataMessage, verifySignature, } from "../helpers"; import { useWalletConnectClient } from "./ClientContext"; import { DEFAULT_COSMOS_METHODS, DEFAULT_EIP155_METHODS, DEFAULT_SOLANA_METHODS, DEFAULT_POLKADOT_METHODS, DEFAULT_NEAR_METHODS, DEFAULT_MULTIVERSX_METHODS, DEFAULT_TRON_METHODS, DEFAULT_TEZOS_METHODS, DEFAULT_EIP155_OPTIONAL_METHODS, } from "../constants"; import { useChainData } from "./ChainDataContext"; import { rpcProvidersByChainId } from "../../src/helpers/api"; import { signatureVerify, cryptoWaitReady } from "@polkadot/util-crypto"; import { Transaction as MultiversxTransaction, TransactionPayload, Address, SignableMessage, } from "@multiversx/sdk-core"; import { UserVerifier } from "@multiversx/sdk-wallet/out/userVerifier"; /** * Types */ interface IFormattedRpcResponse { method?: string; address?: string; valid: boolean; result: string; } type TRpcRequestCallback = (chainId: string, address: string) => Promise; interface IContext { ping: () => Promise; ethereumRpc: { testSendTransaction: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; testEthSign: TRpcRequestCallback; testSignPersonalMessage: TRpcRequestCallback; testSignTypedData: TRpcRequestCallback; testSignTypedDatav4: TRpcRequestCallback; }; cosmosRpc: { testSignDirect: TRpcRequestCallback; testSignAmino: TRpcRequestCallback; }; solanaRpc: { testSignMessage: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; }; polkadotRpc: { testSignMessage: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; }; nearRpc: { testSignAndSendTransaction: TRpcRequestCallback; testSignAndSendTransactions: TRpcRequestCallback; }; multiversxRpc: { testSignMessage: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; testSignTransactions: TRpcRequestCallback; }; tronRpc: { testSignMessage: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; }; tezosRpc: { testGetAccounts: TRpcRequestCallback; testSignMessage: TRpcRequestCallback; testSignTransaction: TRpcRequestCallback; }; rpcResult?: IFormattedRpcResponse | null; isRpcRequestPending: boolean; isTestnet: boolean; setIsTestnet: (isTestnet: boolean) => void; } /** * Context */ export const JsonRpcContext = createContext({} as IContext); /** * Provider */ export function JsonRpcContextProvider({ children, }: { children: ReactNode | ReactNode[]; }) { const [pending, setPending] = useState(false); const [result, setResult] = useState(); const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag()); const { client, session, accounts, balances, solanaPublicKeys } = useWalletConnectClient(); const { chainData } = useChainData(); const _createJsonRpcRequestHandler = ( rpcRequest: ( chainId: string, 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"); } 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, signature: string, address: string ) => utils.verifyMessage(message, signature).toLowerCase() === address.toLowerCase(); const ping = async () => { 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); let valid = false; try { await client.ping({ topic: session.topic }); valid = true; } catch (e) { valid = false; } // display result setResult({ method: "ping", valid, result: valid ? "Ping succeeded" : "Ping failed", }); } catch (e) { console.error(e); setResult(null); } finally { setPending(false); } }; // -------- ETHEREUM/EIP155 RPC METHODS -------- const ethereumRpc = { testSendTransaction: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { const caipAccountAddress = `${chainId}:${address}`; const account = accounts.find( (account) => account === caipAccountAddress ); if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`); const tx = await formatTestTransaction(account); const balance = BigNumber.from(balances[account][0].balance || "0"); if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) { return { method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, address, valid: false, result: "Insufficient funds for intrinsic transaction cost", }; } const result = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, params: [tx], }, }); // format displayed result return { method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, address, valid: true, result, }; } ), testSignTransaction: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { const caipAccountAddress = `${chainId}:${address}`; const account = accounts.find( (account) => account === caipAccountAddress ); if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`); const tx = await formatTestTransaction(account); const signedTx = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TRANSACTION, params: [tx], }, }); const CELO_ALFAJORES_CHAIN_ID = 44787; const CELO_MAINNET_CHAIN_ID = 42220; let valid = false; const [, reference] = chainId.split(":"); if ( reference === CELO_ALFAJORES_CHAIN_ID.toString() || reference === CELO_MAINNET_CHAIN_ID.toString() ) { const [, signer] = recoverTransaction(signedTx); valid = signer.toLowerCase() === address.toLowerCase(); } else { valid = EthTransaction.fromSerializedTx( signedTx as any ).verifySignature(); } return { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TRANSACTION, address, valid, result: signedTx, }; } ), testSignPersonalMessage: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { // test message const message = `My email is john@doe.com - ${Date.now()}`; // encode message (hex) const hexMsg = encoding.utf8ToHex(message, true); // personal_sign params const params = [hexMsg, address]; // send message const signature = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_METHODS.PERSONAL_SIGN, params, }, }); // split chainId const [namespace, reference] = chainId.split(":"); const rpc = rpcProvidersByChainId[Number(reference)]; if (typeof rpc === "undefined") { throw new Error( `Missing rpcProvider definition for chainId: ${chainId}` ); } const hashMsg = hashPersonalMessage(message); const valid = await verifySignature( address, signature, hashMsg, rpc.baseURL ); // format displayed result return { method: DEFAULT_EIP155_METHODS.PERSONAL_SIGN, address, valid, result: signature, }; } ), testEthSign: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { // test message const message = `My email is john@doe.com - ${Date.now()}`; // encode message (hex) const hexMsg = encoding.utf8ToHex(message, true); // eth_sign params const params = [address, hexMsg]; // send message const signature = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN, params, }, }); // split chainId const [namespace, reference] = chainId.split(":"); const rpc = rpcProvidersByChainId[Number(reference)]; if (typeof rpc === "undefined") { throw new Error( `Missing rpcProvider definition for chainId: ${chainId}` ); } const hashMsg = hashPersonalMessage(message); const valid = await verifySignature( address, signature, hashMsg, rpc.baseURL ); // format displayed result return { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN + " (standard)", address, valid, result: signature, }; } ), testSignTypedData: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { const message = JSON.stringify(eip712.example); // eth_signTypedData params const params = [address, message]; // send message const signature = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TYPED_DATA, params, }, }); // split chainId const [namespace, reference] = chainId.split(":"); const rpc = rpcProvidersByChainId[Number(reference)]; if (typeof rpc === "undefined") { throw new Error( `Missing rpcProvider definition for chainId: ${chainId}` ); } const hashedTypedData = hashTypedDataMessage(message); const valid = await verifySignature( address, signature, hashedTypedData, rpc.baseURL ); return { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TYPED_DATA, address, valid, result: signature, }; } ), testSignTypedDatav4: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { const message = JSON.stringify(eip712.example); console.log("eth_signTypedData_v4"); // eth_signTypedData_v4 params const params = [address, message]; // send message const signature = await client!.request({ topic: session!.topic, chainId, request: { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TYPED_DATA_V4, params, }, }); // split chainId const [namespace, reference] = chainId.split(":"); const rpc = rpcProvidersByChainId[Number(reference)]; if (typeof rpc === "undefined") { throw new Error( `Missing rpcProvider definition for chainId: ${chainId}` ); } const hashedTypedData = hashTypedDataMessage(message); const valid = await verifySignature( address, signature, hashedTypedData, rpc.baseURL ); return { method: DEFAULT_EIP155_OPTIONAL_METHODS.ETH_SIGN_TYPED_DATA, address, valid, result: signature, }; } ), }; // -------- COSMOS RPC METHODS -------- const cosmosRpc = { testSignDirect: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { // test direct sign doc inputs const inputs = { fee: [{ amount: "2000", denom: "ucosm" }], pubkey: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW", gasLimit: 200000, accountNumber: 1, sequence: 1, bodyBytes: "0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637", authInfoBytes: "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c", }; // split chainId const [namespace, reference] = chainId.split(":"); // format sign doc const signDoc = formatDirectSignDoc( inputs.fee, inputs.pubkey, inputs.gasLimit, inputs.accountNumber, inputs.sequence, inputs.bodyBytes, reference ); // cosmos_signDirect params const params = { signerAddress: address, signDoc: stringifySignDocValues(signDoc), }; // send message const result = await client!.request<{ signature: string }>({ topic: session!.topic, chainId, request: { method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT, params, }, }); const targetChainData = chainData[namespace][reference]; if (typeof targetChainData === "undefined") { throw new Error(`Missing chain data for chainId: ${chainId}`); } const valid = await verifyDirectSignature( address, result.signature, signDoc ); // format displayed result return { method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT, address, valid, result: result.signature, }; } ), testSignAmino: _createJsonRpcRequestHandler( async (chainId: string, address: string) => { // split chainId const [namespace, reference] = chainId.split(":"); // test amino sign doc const signDoc = { msgs: [], fee: { amount: [], gas: "23" }, chain_id: "foochain", memo: "hello, world", account_number: "7", sequence: "54", }; // cosmos_signAmino params const params = { signerAddress: address, signDoc }; // send message const result = await client!.request<{ signature: string }>({ topic: session!.topic, chainId, request: { method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO, params, }, }); const targetChainData = chainData[namespace][reference]; if (typeof targetChainData === "undefined") { throw new Error(`Missing chain data for chainId: ${chainId}`); } const valid = await verifyAminoSignature( address, result.signature, signDoc ); // format displayed result return { method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO, address, valid, result: result.signature, }; } ), }; // -------- SOLANA RPC METHODS -------- const solanaRpc = { testSignTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { if (!solanaPublicKeys) { throw new Error("Could not find Solana PublicKeys."); } const senderPublicKey = solanaPublicKeys[address]; // rpc.walletconnect.com doesn't support solana testnet yet const connection = new Connection( isTestnet ? clusterApiUrl("testnet") : getProviderUrl(chainId) ); // Using deprecated `getRecentBlockhash` over `getLatestBlockhash` here, since `mainnet-beta` // cluster only seems to support `connection.getRecentBlockhash` currently. const { blockhash } = await connection.getRecentBlockhash(); const transaction = new SolanaTransaction({ feePayer: senderPublicKey, recentBlockhash: blockhash, }).add( SystemProgram.transfer({ fromPubkey: senderPublicKey, toPubkey: Keypair.generate().publicKey, lamports: 1, }) ); try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION, params: { feePayer: transaction.feePayer!.toBase58(), recentBlockhash: transaction.recentBlockhash, instructions: transaction.instructions.map((i) => ({ programId: i.programId.toBase58(), data: Array.from(i.data), keys: i.keys.map((k) => ({ isSigner: k.isSigner, isWritable: k.isWritable, pubkey: k.pubkey.toBase58(), })), })), }, }, }); // We only need `Buffer.from` here to satisfy the `Buffer` param type for `addSignature`. // The resulting `UInt8Array` is equivalent to just `bs58.decode(...)`. transaction.addSignature( senderPublicKey, Buffer.from(bs58.decode(result.signature)) ); const valid = transaction.verifySignatures(); return { method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION, address, valid, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), testSignMessage: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { if (!solanaPublicKeys) { throw new Error("Could not find Solana PublicKeys."); } const senderPublicKey = solanaPublicKeys[address]; // Encode message to `UInt8Array` first via `TextEncoder` so we can pass it to `bs58.encode`. const message = bs58.encode( new TextEncoder().encode( `This is an example message to be signed - ${Date.now()}` ) ); try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE, params: { pubkey: senderPublicKey.toBase58(), message, }, }, }); const valid = verifyMessageSignature( senderPublicKey.toBase58(), result.signature, message ); return { method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE, address, valid, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), }; // -------- POLKADOT RPC METHODS -------- const polkadotRpc = { testSignTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const transactionPayload = { specVersion: "0x00002468", transactionVersion: "0x0000000e", address: `${address}`, blockHash: "0x554d682a74099d05e8b7852d19c93b527b5fae1e9e1969f6e1b82a2f09a14cc9", blockNumber: "0x00cb539c", era: "0xc501", genesisHash: "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e", method: "0x0001784920616d207369676e696e672074686973207472616e73616374696f6e21", nonce: "0x00000000", signedExtensions: [ "CheckNonZeroSender", "CheckSpecVersion", "CheckTxVersion", "CheckGenesis", "CheckMortality", "CheckNonce", "CheckWeight", "ChargeTransactionPayment", ], tip: "0x00000000000000000000000000000000", version: 4, }; try { const result = await client!.request<{ payload: string; signature: string; }>({ chainId, topic: session!.topic, request: { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION, params: { address, transactionPayload, }, }, }); return { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION, address, valid: true, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), testSignMessage: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const message = `This is an example message to be signed - ${Date.now()}`; try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_MESSAGE, params: { address, message, }, }, }); // sr25519 signatures need to wait for WASM to load await cryptoWaitReady(); const { isValid: valid } = signatureVerify( message, result.signature, address ); return { method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_MESSAGE, address, valid, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), }; // -------- NEAR RPC METHODS -------- const nearRpc = { testSignAndSendTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const method = DEFAULT_NEAR_METHODS.NEAR_SIGN_AND_SEND_TRANSACTION; const result = await client!.request({ topic: session!.topic, chainId, request: { method, params: { transaction: { signerId: address, receiverId: "guest-book.testnet", actions: [ { type: "FunctionCall", params: { methodName: "addMessage", args: { text: "Hello from Wallet Connect!" }, gas: "30000000000000", deposit: "0", }, }, ], }, }, }, }); return { method, address, valid: true, result: JSON.stringify((result as any).transaction), }; } ), testSignAndSendTransactions: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const method = DEFAULT_NEAR_METHODS.NEAR_SIGN_AND_SEND_TRANSACTIONS; const result = await client!.request({ topic: session!.topic, chainId, request: { method, params: { transactions: [ { signerId: address, receiverId: "guest-book.testnet", actions: [ { type: "FunctionCall", params: { methodName: "addMessage", args: { text: "Hello from Wallet Connect! (1/2)" }, gas: "30000000000000", deposit: "0", }, }, ], }, { signerId: address, receiverId: "guest-book.testnet", actions: [ { type: "FunctionCall", params: { methodName: "addMessage", args: { text: "Hello from Wallet Connect! (2/2)" }, gas: "30000000000000", deposit: "0", }, }, ], }, ], }, }, }); return { method, address, valid: true, result: JSON.stringify( (result as any).map((r: any) => r.transaction) ), }; } ), }; // -------- MULTIVERSX RPC METHODS -------- const multiversxRpc = { testSignTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const reference = chainId.split(":")[1]; const userAddress = new Address(address); const verifier = UserVerifier.fromAddress(userAddress); const transactionPayload = new TransactionPayload("testdata"); const testTransaction = new MultiversxTransaction({ nonce: 1, value: "10000000000000000000", receiver: Address.fromBech32(address), sender: userAddress, gasPrice: 1000000000, gasLimit: 50000, chainID: reference, data: transactionPayload, }); const transaction = testTransaction.toPlainObject(); try { const result = await client!.request<{ signature: Buffer }>({ chainId, topic: session!.topic, request: { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_TRANSACTION, params: { transaction, }, }, }); const valid = verifier.verify( testTransaction.serializeForSigning(Address.fromBech32(address)), result.signature ); return { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_TRANSACTION, address, valid, result: result.signature.toString(), }; } catch (error: any) { throw new Error(error); } } ), testSignTransactions: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const reference = chainId.split(":")[1]; const userAddress = new Address(address); const verifier = UserVerifier.fromAddress(userAddress); const testTransactionPayload = new TransactionPayload("testdata"); const testTransaction = new MultiversxTransaction({ nonce: 1, value: "10000000000000000000", receiver: Address.fromBech32(address), sender: userAddress, gasPrice: 1000000000, gasLimit: 50000, chainID: reference, data: testTransactionPayload, }); // no data for this Transaction const testTransaction2 = new MultiversxTransaction({ nonce: 2, value: "20000000000000000000", receiver: Address.fromBech32(address), sender: userAddress, gasPrice: 1000000000, gasLimit: 50000, chainID: reference, }); const testTransaction3Payload = new TransactionPayload("third"); const testTransaction3 = new MultiversxTransaction({ nonce: 3, value: "300000000000000000", receiver: Address.fromBech32(address), sender: userAddress, gasPrice: 1000000000, gasLimit: 50000, chainID: reference, data: testTransaction3Payload, }); const transactions = [ testTransaction, testTransaction2, testTransaction3, ].map((transaction) => transaction.toPlainObject()); try { const result = await client!.request<{ signatures: { signature: Buffer }[]; }>({ chainId, topic: session!.topic, request: { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_TRANSACTIONS, params: { transactions, }, }, }); const valid = [ testTransaction, testTransaction2, testTransaction3, ].reduce((acc, current, index) => { return ( acc && verifier.verify( current.serializeForSigning(Address.fromBech32(address)), result.signatures[index].signature ) ); }, true); const resultSignatures = result.signatures.map( (signature: any) => signature.signature ); return { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_TRANSACTIONS, address, valid, result: resultSignatures.join(", "), }; } catch (error: any) { throw new Error(error); } } ), testSignMessage: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const userAddress = new Address(address); const verifier = UserVerifier.fromAddress(userAddress); const testMessage = new SignableMessage({ address: userAddress, message: Buffer.from(`Sign this message - ${Date.now()}`, "ascii"), }); try { const result = await client!.request<{ signature: Buffer }>({ chainId, topic: session!.topic, request: { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_MESSAGE, params: { address, message: testMessage.message.toString(), }, }, }); const valid = verifier.verify( testMessage.serializeForSigning(), result.signature ); return { method: DEFAULT_MULTIVERSX_METHODS.MULTIVERSX_SIGN_MESSAGE, address, valid, result: result.signature.toString(), }; } catch (error: any) { throw new Error(error); } } ), }; // -------- TRON RPC METHODS -------- const tronRpc = { testSignTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { // Nile TestNet, if you want to use in MainNet, change the fullHost to 'https://api.trongrid.io' const fullHost = isTestnet ? "https://nile.trongrid.io/" : "https://api.trongrid.io/"; const tronWeb = new TronWeb({ fullHost, }); // Take USDT as an example: // Nile TestNet: https://nile.tronscan.org/#/token20/TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf // MainNet: https://tronscan.org/#/token20/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t const testContract = isTestnet ? "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf" : "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; const testTransaction = await tronWeb.transactionBuilder.triggerSmartContract( testContract, "approve(address,uint256)", { feeLimit: 200000000 }, [ { type: "address", value: address }, { type: "uint256", value: 0 }, ], address ); try { const { result } = await client!.request<{ result: any }>({ chainId, topic: session!.topic, request: { method: DEFAULT_TRON_METHODS.TRON_SIGN_TRANSACTION, params: { address, transaction: { ...testTransaction, }, }, }, }); return { method: DEFAULT_TRON_METHODS.TRON_SIGN_TRANSACTION, address, valid: true, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), testSignMessage: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const message = "This is a message to be signed for Tron"; try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_TRON_METHODS.TRON_SIGN_MESSAGE, params: { address, message, }, }, }); return { method: DEFAULT_TRON_METHODS.TRON_SIGN_MESSAGE, address, valid: true, result: result.signature, }; } catch (error: any) { throw new Error(error); } } ), }; // -------- TEZOS RPC METHODS -------- const tezosRpc = { testGetAccounts: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_TEZOS_METHODS.TEZOS_GET_ACCOUNTS, params: {}, }, }); return { method: DEFAULT_TEZOS_METHODS.TEZOS_GET_ACCOUNTS, address, valid: true, result: JSON.stringify(result, null, 2), }; } catch (error: any) { throw new Error(error.message); } } ), testSignTransaction: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { try { const result = await client!.request<{ hash: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_TEZOS_METHODS.TEZOS_SEND, params: { account: address, operations: [ { kind: "transaction", amount: "1", // 1 mutez, smallest unit destination: address, // send to ourselves }, ], }, }, }); return { method: DEFAULT_TEZOS_METHODS.TEZOS_SEND, address, valid: true, result: result.hash, }; } catch (error: any) { throw new Error(error.message); } } ), testSignMessage: _createJsonRpcRequestHandler( async ( chainId: string, address: string ): Promise => { const payload = "05010000004254"; try { const result = await client!.request<{ signature: string }>({ chainId, topic: session!.topic, request: { method: DEFAULT_TEZOS_METHODS.TEZOS_SIGN, params: { account: address, payload, }, }, }); return { method: DEFAULT_TEZOS_METHODS.TEZOS_SIGN, address, valid: true, result: result.signature, }; } catch (error: any) { throw new Error(error.message); } } ), }; return ( {children} ); } export function useJsonRpc() { const context = useContext(JsonRpcContext); if (context === undefined) { throw new Error("useJsonRpc must be used within a JsonRpcContextProvider"); } return context; }