diff --git a/integration_test/evm_rpc/README.md b/integration_test/evm_rpc/README.md new file mode 100644 index 000000000..6fceca930 --- /dev/null +++ b/integration_test/evm_rpc/README.md @@ -0,0 +1,9 @@ +## TS Tests for evm rpc calls + +Tests uses ethers v6 to make json rpc calls to evm rpcs. Tests are running against local sei chain. + +In order to run the tests you can run: + +``yarn install`` + +``yarn test`` \ No newline at end of file diff --git a/integration_test/evm_rpc/package.json b/integration_test/evm_rpc/package.json new file mode 100644 index 000000000..2982ba889 --- /dev/null +++ b/integration_test/evm_rpc/package.json @@ -0,0 +1,27 @@ +{ + "name": "evm_rpc", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "test": "mocha -r tsx rpcCalls.ts" + }, + "dependencies": { + "@cosmjs/cosmwasm-stargate": "^0.32.4", + "@cosmjs/crypto": "^0.32.4", + "@cosmjs/proto-signing": "^0.32.4", + "@cosmjs/stargate": "^0.32.4", + "@noble/curves": "^1.4.2", + "@sei-js/core": "^3.2.1", + "@sei-js/proto": "^4.0.9", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.7", + "chai": "^5.1.1", + "ethers": "^6.13.1", + "mocha": "^10.6.0", + "ts-mocha": "^10.0.0", + "tsx": "^4.16.2", + "viem": "^2.17.3" + }, + "devDependencies": {} +} diff --git a/integration_test/evm_rpc/rpcCalls.ts b/integration_test/evm_rpc/rpcCalls.ts new file mode 100644 index 000000000..b876fc324 --- /dev/null +++ b/integration_test/evm_rpc/rpcCalls.ts @@ -0,0 +1,367 @@ +import ExpectStatic = Chai.ExpectStatic; +import {ethers, formatEther, toBeHex} from 'ethers'; +import { SigningStargateClient} from '@cosmjs/stargate'; +import {createEvmProvider, createEvmWallet, deployToChain, sendFundsFromEvmClient} from './utils/evmUtils'; +import { + associateWallet, + createSeiProvider, + createSeiWallet, generateEvmAddressFromMnemonic, + generateSeiAddressFromMnemonic, + sendFundsFromSeiClient +} from './utils/seiUtils'; +import {DirectSecp256k1HdWallet} from '@cosmjs/proto-signing'; +import {fundEvmWallet, fundSeiWallet, waitFor} from './utils/cmdUtils'; +import {seiprotocol} from '@sei-js/proto'; +import testConfig from './testConfig.json'; + +/** + * Runs against local sei chain. Generates two random accounts. + * Tests some of the rpc calls. + */ + +describe('Evm Rpc Calls', function (){ + let expect: ExpectStatic; + this.timeout( 4 * 60 * 1000); + const evmRpc = testConfig.evmRpc + const seiRpc = testConfig.seiRpc; + let evmClient: ethers.JsonRpcProvider; + let seiClient: SigningStargateClient; + let seiWallet: DirectSecp256k1HdWallet; + let evmWallet: ethers.HDNodeWallet; + let evmAddress: string; + let seiAddress: string; + let evmWalletSeiAddress: string; + const feeAccount = testConfig.feeAccount; + const chainId = testConfig.chainId; + + before('Tests set up', async () =>{ + const chai = await import('chai'); + ({expect} = chai); + seiWallet = await createSeiWallet(); + evmClient = await createEvmProvider(evmRpc); + seiClient = await createSeiProvider(seiRpc, seiWallet); + evmWallet = await createEvmWallet(evmClient); + evmWalletSeiAddress = await generateSeiAddressFromMnemonic(evmWallet); + await fundEvmWallet(evmWallet, evmRpc); + await fundSeiWallet(seiWallet); + await fundSeiWallet(evmWalletSeiAddress); + await waitFor(2); + evmAddress = await evmWallet.getAddress(); + seiAddress = (await seiWallet.getAccounts())[0].address; + }) + + describe('Association rpc calls tests', function (){ + let txHash: string; + + it('Evm users can associate accounts', async () =>{ + await associateWallet(evmClient, evmWallet); + const queriedSeiAddress = await evmClient.send('sei_getSeiAddress', [evmAddress]); + const seiQueryClient = await seiprotocol.ClientFactory + .createRPCQueryClient({rpcEndpoint: seiRpc}); + const {associated} = await seiQueryClient.seiprotocol.seichain.evm + .seiAddressByEVMAddress({evmAddress: evmAddress}); + const expectedSeiAddress = await generateSeiAddressFromMnemonic(evmWallet); + + expect(associated).to.be.true; + expect(queriedSeiAddress).to.be.eq(expectedSeiAddress); + }); + + it('Sei wallet can implicitly associate accounts', async () =>{ + const expectedSeiAddress = await generateSeiAddressFromMnemonic(evmWallet); + txHash = await sendFundsFromSeiClient(seiClient, seiAddress, expectedSeiAddress); + await waitFor(1); + const seiQueryClient = await seiprotocol.ClientFactory.createRPCQueryClient({rpcEndpoint: seiRpc}); + const {associated} = await seiQueryClient.seiprotocol.seichain.evm.eVMAddressBySeiAddress({seiAddress: seiAddress}); + + const queriedEvmAddress = await evmClient.send('sei_getEVMAddress', [seiAddress]); + const expectedEvmAddress = await generateEvmAddressFromMnemonic(seiWallet); + + expect(associated).to.be.true; + expect(queriedEvmAddress).to.be.eq(expectedEvmAddress); + }); + + it('Users can query evm tx', async () =>{ + const evmAddress = await generateEvmAddressFromMnemonic(seiWallet); + const [txHash,blockNumber, blockHash] = + await sendFundsFromEvmClient(evmWallet, evmAddress); + await waitFor(1); + + const txRecord = await evmClient.send('sei_getCosmosTx', [txHash]); + expect(txRecord).not.to.be.null; + + const txRecordOnCosmos = await evmClient.send('sei_getEvmTx', [txRecord]); + expect(txRecordOnCosmos).not.to.be.null; + }); + }); + + /** + * eth_getBlockReceipts + * eth_getBlockTransactionCountByNumber + * eth_getBlockTransactionCountByHash + * eth_getBlockByHash + * eth_getBlockByNumber + */ + describe('Block rpc calls test', function (){ + let blockNumber: string; + let blockHash: string; + let txHash: string; + let evmSeiAddress: string; + + it('Users will get the block receipts with block number', async () =>{ + evmSeiAddress = await generateEvmAddressFromMnemonic(seiWallet); + ([txHash, blockNumber, blockHash] = + await sendFundsFromEvmClient(evmWallet, evmSeiAddress) as string[]); + const [txReceipt] = await evmClient.send('eth_getBlockReceipts', [ethers.toQuantity(blockNumber)]); + + expect(txReceipt).to.haveOwnProperty('blockHash'); + expect(txReceipt).to.haveOwnProperty('blockNumber'); + expect(txReceipt).to.haveOwnProperty('gasUsed'); + + expect(txReceipt.transactionHash).to.be.eq(txHash); + expect(txReceipt.from).to.be.eq(evmAddress.toLowerCase()); + expect(txReceipt.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users will get the block receipts with block hash', async () =>{ + const [txReceipt] = await evmClient.send('eth_getBlockReceipts', [blockHash]); + + expect(txReceipt).to.haveOwnProperty('blockHash'); + expect(txReceipt).to.haveOwnProperty('blockNumber'); + expect(txReceipt).to.haveOwnProperty('gasUsed'); + + expect(txReceipt.transactionHash).to.be.eq(txHash); + expect(txReceipt.from).to.be.eq(evmAddress.toLowerCase()); + expect(txReceipt.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users will see get block transaction count by number', async () =>{ + const txCount = await evmClient.send('eth_getBlockTransactionCountByNumber', [ethers.toQuantity(blockNumber)]); + expect(parseInt(txCount)).to.be.eq(1); + }); + + it('Users will see transaction count by hash', async () =>{ + const txCount = await evmClient.send('eth_getBlockTransactionCountByHash', [blockHash]); + expect(parseInt(txCount)).to.be.eq(1); + }); + + it('Users will get block details by hash', async () =>{ + const block = await evmClient.send('eth_getBlockByHash', [blockHash, true]); + + expect(block.transactions).to.have.length(1); + expect(block.hash).to.be.eq(blockHash); + expect(parseInt(block.number)).to.be.eq(parseInt(blockNumber)); + }); + + it('Users will get block details by number', async () =>{ + const block = await evmClient.send('eth_getBlockByNumber', [ethers.toQuantity(blockNumber), false]); + + expect(block.transactions).to.have.length(1); + expect(block.hash).to.be.eq(blockHash); + expect(parseInt(block.number)).to.be.eq(parseInt(blockNumber)); + }); + }); + + /** + * eth_BlockNumber + * eth_ChainId + * eth_Coinbase + * eth_Accounts + * eth_GasPrice + * eth_feeHistory + * eth_maxPriorityFeePerGas + */ + describe('Info rpc calls tests', function() { + + it('Users can query latest block number', async () =>{ + const blockNumber = await evmClient.send('eth_blockNumber', []); + expect(parseInt(blockNumber)).to.be.above(0); + }); + + it('Users can query the fee account', async () =>{ + const coinbase = await evmClient.send('eth_coinbase', []); + expect(coinbase).to.be.eq(feeAccount); + }); + + it('Users can query chain id', async () =>{ + const queriedChainId = await evmClient.send('eth_chainId', []); + expect(parseInt(queriedChainId)).to.be.eq(chainId); + }); + + it('Users can query accounts', async () =>{ + const accounts = await evmClient.send('eth_accounts', []); + expect(accounts.length).to.be.above(0); + }); + + it('Users can query gas price', async () =>{ + const gasPrice = await evmClient.send('eth_gasPrice', []); + expect(parseInt(gasPrice)).to.be.above(990000000); + }); + + it('Users can query fee history', async () =>{ + const blockCount = 10; // + const lastBlock = await evmClient.getBlockNumber(); + const rewardPercentiles = [10.0]; + const feeHistory = await evmClient.send('eth_feeHistory', + [ethers.toQuantity(blockCount), ethers.toQuantity(lastBlock), rewardPercentiles]); + + expect(Number(feeHistory.oldestBlock)).to.be.eq((lastBlock - blockCount) + 1); + expect(feeHistory.baseFeePerGas).to.have.length(blockCount); + expect(feeHistory.gasUsedRatio).to.have.length(blockCount); + }); + + it('Users can query max priority fee per gas', async () =>{ + const maxPriority = await evmClient.send('eth_maxPriorityFeePerGas', []); + expect(Number(maxPriority)).to.be.gte(0); + }) + }) + + /** + * eth_getNonce + * eth_getBalance + * eth_getBlockByNumber + * + */ + describe('State rpc calls tests', () =>{ + let latestBlockNumber: number; + let latestBalance: string; + let previousBalance: string; + let previousNonce: number; + + before('Gets the latest block number', async () =>{ + latestBlockNumber = await evmClient.getBlockNumber(); + previousNonce = await evmClient.send('eth_getNonce', [evmAddress]); + }); + + it('Users can query their balance with block number', async() =>{ + const userBalance = await evmClient.getBalance(evmAddress); + latestBalance = await evmClient.send('eth_getBalance', [evmAddress,ethers.toQuantity(latestBlockNumber)]); + expect(userBalance.toString()).to.be.eq(BigInt(latestBalance).toString()); + }); + + it('Users can query their balance from a previous block number', async () =>{ + previousBalance = await evmClient.send('eth_getBalance', [evmAddress, ethers.toQuantity(latestBlockNumber - 3)]); + expect(previousBalance).not.to.be.eq('0x'); + }); + + it('Users can query their balance with block hash', async() =>{ + const {hash} = await evmClient.send('eth_getBlockByNumber', [ethers.toQuantity(latestBlockNumber), false]); + const latestBalanceWithHash = await evmClient.send('eth_getBalance', [evmAddress, hash]); + expect(latestBalanceWithHash).to.be.eq(latestBalance); + }); + + it('Users can query their balance from a previous block with a hash', async () =>{ + const {hash} = await evmClient.send('eth_getBlockByNumber', [ethers.toQuantity(latestBlockNumber - 3), false]); + const previousBalanceWithHash = await evmClient.send('eth_getBalance', [evmAddress, hash]); + expect(previousBalanceWithHash).to.be.eq(previousBalance); + }); + + it('Users can query balance after transfers', async () =>{ + const seiEvmAddress = await generateEvmAddressFromMnemonic(seiWallet); + const [hash, blockNumberOfTransfer] = + await sendFundsFromEvmClient(evmWallet, seiEvmAddress) as [string, string]; + await waitFor(2); + const latestBalanceAfterTransfer = await evmClient.send('eth_getBalance', [evmAddress, 'latest']); + const balanceDifference = BigInt(latestBalance) - BigInt(latestBalanceAfterTransfer); + const difference = formatEther(balanceDifference.toString()); + expect(Number(difference)).to.be.gte(0.1); + + const previousBalanceOnTransferBlock = await evmClient.send('eth_getBalance', + [evmAddress, ethers.toQuantity(Number(blockNumberOfTransfer) - 1)]); + expect(previousBalanceOnTransferBlock).to.be.eq(latestBalance); + }); + + it('Users can query byte code of a contract', async () =>{ + const {address, bytecode} = await deployToChain(evmWallet); + await waitFor(2); + const queriedByteCode = await evmClient.send('eth_getCode', [address, 'latest']); + expect(queriedByteCode).to.be.eq(bytecode); + }); + + it('Users can query next nonce', async () =>{ + const nonce = await evmClient.send('eth_getNonce', [evmAddress]); + expect(nonce).to.be.eq(previousNonce + 2); + }); + }); + + /** + * eth_getTransactionReceipt + * eth_getTransactionByHash + * eth_getTransactionByBlockNumberAndIndex + * eth_getTransactionByBlockHashAndIndex + * eth_getTransactionCount + */ + describe('Tx Rpc Tests', function () { + let blockNumber: string; + let blockHash: string; + let evmSeiAddress: string; + let txHash: string; + let usertxCount: string; + + it('Users can get transaction receipt', async () =>{ + evmSeiAddress = await generateEvmAddressFromMnemonic(seiWallet); + const [txHash] = await sendFundsFromEvmClient(evmWallet, evmSeiAddress); + await waitFor(1); + const receipt = await evmClient.send('eth_getTransactionReceipt', [txHash]); + + blockNumber = receipt.blockNumber; + blockHash = receipt.blockHash; + + expect(receipt).to.haveOwnProperty('blockHash'); + expect(receipt).to.haveOwnProperty('blockNumber'); + expect(receipt).to.haveOwnProperty('gasUsed'); + + expect(receipt.from).to.be.eq(evmAddress.toLowerCase()); + expect(receipt.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users can query tx count from block number', async () =>{ + usertxCount = await evmClient.send('eth_getTransactionCount', [evmAddress, ethers.toQuantity(blockNumber)]); + expect(parseInt(usertxCount)).to.be.gte(1); + }); + + it('Users can get transaction by hash', async () =>{ + ([txHash] = await sendFundsFromEvmClient(evmWallet, evmSeiAddress) as [string]); + await waitFor(1); + const txDetails = await evmClient.send('eth_getTransactionByHash', [txHash]); + expect(txDetails).to.haveOwnProperty('blockHash'); + expect(txDetails).to.haveOwnProperty('blockNumber'); + + expect(txDetails.from).to.be.eq(evmAddress.toLowerCase()); + expect(txDetails.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users can query tx from block number and index', async () =>{ + const txDetails = await evmClient.send('eth_getTransactionByBlockNumberAndIndex', + [ethers.toQuantity(blockNumber), ethers.toQuantity(0)]); + + expect(txDetails).to.haveOwnProperty('blockHash'); + expect(txDetails).to.haveOwnProperty('blockNumber'); + + expect(txDetails.from).to.be.eq(evmAddress.toLowerCase()); + expect(txDetails.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users can query tx from block hash and index', async () =>{ + const txDetails = await evmClient.send('eth_getTransactionByBlockHashAndIndex', + [blockHash, ethers.toQuantity(0)]); + + expect(txDetails).to.haveOwnProperty('blockHash'); + expect(txDetails).to.haveOwnProperty('blockNumber'); + + expect(txDetails.from).to.be.eq(evmAddress.toLowerCase()); + expect(txDetails.to).to.be.eq(evmSeiAddress.toLowerCase()); + }); + + it('Users cant query unexisting indexes', async () =>{ + const txDetails = await evmClient.send('eth_getTransactionByBlockHashAndIndex', [blockHash, ethers.toQuantity(5)]); + expect(txDetails).to.be.null; + }); + + it('Users can query tx count from block hash', async () =>{ + const block = await evmClient.send('eth_getBlockByNumber', ['latest', false]); + const txCount = await evmClient.send('eth_getTransactionCount', [evmAddress, block.hash]); + expect(parseInt(txCount)).to.be.above(1); + expect(parseInt(usertxCount)).to.be.eq(parseInt(txCount) - 1); + }); + }); +}) \ No newline at end of file diff --git a/integration_test/evm_rpc/testConfig.json b/integration_test/evm_rpc/testConfig.json new file mode 100644 index 000000000..7ad44ad5d --- /dev/null +++ b/integration_test/evm_rpc/testConfig.json @@ -0,0 +1,6 @@ +{ + "evmRpc": "http://127.0.0.1:8545", + "seiRpc": "http://127.0.0.1:26657", + "feeAccount": "0x27f7b8b8b5a4e71e8e9aa671f4e4031e3773303f", + "chainId": 713715 +} \ No newline at end of file diff --git a/integration_test/evm_rpc/tsconfig.json b/integration_test/evm_rpc/tsconfig.json new file mode 100644 index 000000000..eca36a891 --- /dev/null +++ b/integration_test/evm_rpc/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "module": "commonjs", /* Specify what module code is generated. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "strict": true, /* Enable all strict type-checking options. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/integration_test/evm_rpc/utils/cmdUtils.ts b/integration_test/evm_rpc/utils/cmdUtils.ts new file mode 100644 index 000000000..7125b6a32 --- /dev/null +++ b/integration_test/evm_rpc/utils/cmdUtils.ts @@ -0,0 +1,35 @@ +import * as util from 'node:util'; +const exec = util.promisify(require('node:child_process').exec); + +import {ethers} from 'ethers'; +import {DirectSecp256k1HdWallet} from '@cosmjs/proto-signing'; + + +export async function fundEvmWallet(receiverWallet: ethers.HDNodeWallet, rpc: string){ + const {stdout} = await exec('seid keys show admin --address'); + const address = await receiverWallet.getAddress(); + await exec(`seid tx evm send ${address} 10000000000000000000000 --from admin --fees 24000use --evm-rpc=${rpc}`); + console.log('Funded on evm'); +} + +export async function fundSeiWallet(receiverWallet: DirectSecp256k1HdWallet | string){ + let address: string; + if(receiverWallet instanceof DirectSecp256k1HdWallet){ + const [accountData, _] = await receiverWallet.getAccounts(); + address = accountData.address; + } else { + address = receiverWallet; + } + const {stdout} = await exec('seid keys show admin --address'); + const senderAddress = stdout.trim().replaceAll(' ', ''); + console.log('Funding sei address'); + await exec(`seid tx bank send ${senderAddress} ${address} 10000000000usei --from admin --fees 24200usei -y`); +} + +export async function waitFor(seconds: number){ + return new Promise((resolve) =>{ + return setTimeout(() =>{ + resolve(); + }, seconds * 1000) + }) +} \ No newline at end of file diff --git a/integration_test/evm_rpc/utils/evmUtils.ts b/integration_test/evm_rpc/utils/evmUtils.ts new file mode 100644 index 000000000..b9d847d1d --- /dev/null +++ b/integration_test/evm_rpc/utils/evmUtils.ts @@ -0,0 +1,33 @@ +import {ethers} from 'ethers'; +import abi from '../../../contracts/artifacts/src/BoxV2.sol/BoxV2.json'; + + +export async function createEvmProvider(rpcUrl: string) { + return new ethers.JsonRpcProvider(rpcUrl) +} + +export async function createEvmWallet(evmClient: ethers.JsonRpcProvider) { + return ethers.Wallet.createRandom(evmClient); +} + +export async function sendFundsFromEvmClient(wallet: ethers.HDNodeWallet, recipientAddress: string) { + const tx = { + to: recipientAddress, + value: ethers.parseUnits('0.1', 'ether'), + }; + + const txResponse = await wallet.sendTransaction(tx); + + const receipt = await txResponse.wait(); + return [receipt?.hash, receipt?.blockNumber, receipt?.blockHash]; +} + + +export async function deployToChain(evmWallet: ethers.HDNodeWallet){ + const contractFactory = new ethers.ContractFactory(abi.abi, abi.bytecode, evmWallet); + const contract = await contractFactory.deploy(); + return { + address: contract.target, + bytecode: abi.deployedBytecode + }; +} diff --git a/integration_test/evm_rpc/utils/seiUtils.ts b/integration_test/evm_rpc/utils/seiUtils.ts new file mode 100644 index 000000000..758800429 --- /dev/null +++ b/integration_test/evm_rpc/utils/seiUtils.ts @@ -0,0 +1,70 @@ +import {Random, stringToPath} from '@cosmjs/crypto'; +import {coins, DirectSecp256k1HdWallet} from '@cosmjs/proto-signing'; +import {coin, SigningStargateClient} from '@cosmjs/stargate'; +import {ethers, toBeHex} from 'ethers'; +import {cosmos} from '@sei-js/proto'; +import {secp256k1} from '@noble/curves/secp256k1'; +import {hexToNumber, numberToHex} from 'viem'; +import {waitFor} from './cmdUtils'; +import {Coin} from '@sei-js/proto/dist/types/codegen/cosmos/base/v1beta1/coin'; + +export async function createSeiProvider(rpcUrl: string, wallet: DirectSecp256k1HdWallet){ + return await SigningStargateClient.connectWithSigner(rpcUrl, wallet); +} + +export async function createSeiWallet(){ + return await DirectSecp256k1HdWallet.generate(24, { + prefix: 'sei' + }); +} + +export async function associateWallet(evmProvider: ethers.JsonRpcProvider, evmWallet: ethers.HDNodeWallet){ + const message = "account association"; + const signature = await evmWallet.signMessage( + message, + ); + const { r, s } = secp256k1.Signature.fromCompact(signature.slice(2, 130)); + const v = hexToNumber(`0x${signature.slice(130)}`); + + const messageLength = Buffer.from(message, "utf8").length; + const messageToSign = `\x19Ethereum Signed Message:\n${messageLength}${message}`; + const request = { + r: numberToHex(r), + s: numberToHex(s), + v: numberToHex(v - 27), + custom_message: messageToSign, + }; + await evmProvider.send('sei_associate', [request]); + await waitFor(2); +} + +export async function signMessage(evmWallet: ethers.HDNodeWallet){ + const customMessage = 'associate wallets'; + const sign = await evmWallet.signMessage(customMessage); + const values = ethers.Signature.from(sign); + const {r,v,s} = values; + return {r, v, s}; +} + +export async function generateEvmAddressFromMnemonic(seiWallet: DirectSecp256k1HdWallet){ + const evmWallet = ethers.HDNodeWallet.fromPhrase(seiWallet.mnemonic, '', 'm/44\'/118\'/0\'/0/0'); + return await evmWallet.getAddress() +} + +export async function generateSeiAddressFromMnemonic(evmWallet: ethers.HDNodeWallet){ + const mnemonic = evmWallet.mnemonic!.phrase; + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: 'sei', + hdPaths: [stringToPath('m/44\'/60\'/0\'/0/0')] + }); + return (await wallet.getAccounts())[0].address; +} + +export async function sendFundsFromSeiClient(signingClient: SigningStargateClient, senderWallet: string, receiverAddress: string){ + const fee = { + amount: coins(24000, "usei"), // fee amount + gas: "250000", // gas limit + }; const transferAmount = coin('100000', 'usei'); + const receipt = await signingClient.sendTokens(senderWallet, receiverAddress, [transferAmount], fee); + return receipt.transactionHash; +} \ No newline at end of file