diff --git a/broadcast/DeployMultisend.s.sol/11155111/run-1746870410.json b/broadcast/DeployMultisend.s.sol/11155111/run-1746870410.json new file mode 100644 index 0000000..f077b96 --- /dev/null +++ b/broadcast/DeployMultisend.s.sol/11155111/run-1746870410.json @@ -0,0 +1,46 @@ +{ + "transactions": [ + { + "hash": "0x23bfe882d57a8feeb40d999f62daa9cafaa2d3a5c30101c3afe71c196238c2c0", + "transactionType": "CREATE", + "contractName": "EOAMultisend", + "contractAddress": "0xda51ebfbb740d2183e91faf762666b169a1a9a62", + "function": null, + "arguments": null, + "transaction": { + "from": "0xae5a4884d813f334e1b80b26402ec78fadaee7eb", + "gas": "0xc9326", + "value": "0x0", + "input": "0x6080604052348015600e575f5ffd5b50610a8a8061001c5f395ff3fe608060405260043610610042575f3560e01c806309c5eabe146100455780631f6a1eb91461006d5780638d80ff0a14610095578063affed0e0146100b157610043565b5b005b348015610050575f5ffd5b5061006b6004803603810190610066919061072c565b6100db565b005b348015610078575f5ffd5b50610093600480360381019061008e91906107d0565b61014c565b005b6100af60048036038101906100aa919061072c565b610260565b005b3480156100bc575f5ffd5b506100c56102df565b6040516100d29190610861565b60405180910390f35b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610140576040517fded4370e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61014981610260565b50565b5f465f5f81548092919061015f906108a7565b919050558560405160200161017693929190610960565b6040516020818303038152906040528051906020012090505f610198826102e4565b90505f6101e88286868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f82011690508083019250505050505050610317565b90503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461024f576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61025886610260565b505050505050565b805160205b818110156102da578083015160f81c6001820184015160601c60158301850151603584018601516055850187015f855f81146102a857600181146102b7576102bb565b5f5f8585888a5af191506102bb565b5f5ffd5b505f81036102c7575f5ffd5b8260550187019650505050505050610265565b505050565b5f5481565b5f7f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f5281601c52603c5f209050919050565b5f5f5f5f6103258686610341565b9250925092506103358282610396565b82935050505092915050565b5f5f5f6041845103610381575f5f5f602087015192506040870151915060608701515f1a9050610373888285856104f8565b95509550955050505061038f565b5f600285515f1b9250925092505b9250925092565b5f60038111156103a9576103a8610998565b5b8260038111156103bc576103bb610998565b5b03156104f457600160038111156103d6576103d5610998565b5b8260038111156103e9576103e8610998565b5b03610420576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600381111561043457610433610998565b5b82600381111561044757610446610998565b5b0361048b57805f1c6040517ffce698f70000000000000000000000000000000000000000000000000000000081526004016104829190610861565b60405180910390fd5b60038081111561049e5761049d610998565b5b8260038111156104b1576104b0610998565b5b036104f357806040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004016104ea91906109dd565b60405180910390fd5b5b5050565b5f5f5f7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0845f1c1115610534575f6003859250925092506105d5565b5f6001888888886040515f81526020016040526040516105579493929190610a11565b6020604051602081039080840390855afa158015610577573d5f5f3e3d5ffd5b5050506020604051035190505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036105c8575f60015f5f1b935093509350506105d5565b805f5f5f1b935093509350505b9450945094915050565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61063e826105f8565b810181811067ffffffffffffffff8211171561065d5761065c610608565b5b80604052505050565b5f61066f6105df565b905061067b8282610635565b919050565b5f67ffffffffffffffff82111561069a57610699610608565b5b6106a3826105f8565b9050602081019050919050565b828183375f83830152505050565b5f6106d06106cb84610680565b610666565b9050828152602081018484840111156106ec576106eb6105f4565b5b6106f78482856106b0565b509392505050565b5f82601f830112610713576107126105f0565b5b81356107238482602086016106be565b91505092915050565b5f60208284031215610741576107406105e8565b5b5f82013567ffffffffffffffff81111561075e5761075d6105ec565b5b61076a848285016106ff565b91505092915050565b5f5ffd5b5f5ffd5b5f5f83601f8401126107905761078f6105f0565b5b8235905067ffffffffffffffff8111156107ad576107ac610773565b5b6020830191508360018202830111156107c9576107c8610777565b5b9250929050565b5f5f5f604084860312156107e7576107e66105e8565b5b5f84013567ffffffffffffffff811115610804576108036105ec565b5b610810868287016106ff565b935050602084013567ffffffffffffffff811115610831576108306105ec565b5b61083d8682870161077b565b92509250509250925092565b5f819050919050565b61085b81610849565b82525050565b5f6020820190506108745f830184610852565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6108b182610849565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108e3576108e261087a565b5b600182019050919050565b5f819050919050565b61090861090382610849565b6108ee565b82525050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f61093a8261090e565b6109448185610918565b9350610954818560208601610922565b80840191505092915050565b5f61096b82866108f7565b60208201915061097b82856108f7565b60208201915061098b8284610930565b9150819050949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b5f819050919050565b6109d7816109c5565b82525050565b5f6020820190506109f05f8301846109ce565b92915050565b5f60ff82169050919050565b610a0b816109f6565b82525050565b5f608082019050610a245f8301876109ce565b610a316020830186610a02565b610a3e60408301856109ce565b610a4b60608301846109ce565b9594505050505056fea2646970667358221220b3afe6d2bcb8fd25bdb05b143b2c8d0d4f443504c856fc371db1d3ddf250725664736f6c634300081c0033", + "nonce": "0x39", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xf86ab5", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x23bfe882d57a8feeb40d999f62daa9cafaa2d3a5c30101c3afe71c196238c2c0", + "transactionIndex": "0x9c", + "blockHash": "0x8420fccb19cbd1292ac1da1411c867dfa2f2dc2e8b33974fbf87e019fad15496", + "blockNumber": "0x7e95af", + "gasUsed": "0x9ac45", + "effectiveGasPrice": "0xf4251", + "from": "0xae5a4884d813f334e1b80b26402ec78fadaee7eb", + "to": null, + "contractAddress": "0xda51ebfbb740d2183e91faf762666b169a1a9a62" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1746870410, + "chain": 11155111, + "commit": "cce2192" +} \ No newline at end of file diff --git a/broadcast/DeployMultisend.s.sol/11155111/run-latest.json b/broadcast/DeployMultisend.s.sol/11155111/run-latest.json new file mode 100644 index 0000000..f077b96 --- /dev/null +++ b/broadcast/DeployMultisend.s.sol/11155111/run-latest.json @@ -0,0 +1,46 @@ +{ + "transactions": [ + { + "hash": "0x23bfe882d57a8feeb40d999f62daa9cafaa2d3a5c30101c3afe71c196238c2c0", + "transactionType": "CREATE", + "contractName": "EOAMultisend", + "contractAddress": "0xda51ebfbb740d2183e91faf762666b169a1a9a62", + "function": null, + "arguments": null, + "transaction": { + "from": "0xae5a4884d813f334e1b80b26402ec78fadaee7eb", + "gas": "0xc9326", + "value": "0x0", + "input": "0x6080604052348015600e575f5ffd5b50610a8a8061001c5f395ff3fe608060405260043610610042575f3560e01c806309c5eabe146100455780631f6a1eb91461006d5780638d80ff0a14610095578063affed0e0146100b157610043565b5b005b348015610050575f5ffd5b5061006b6004803603810190610066919061072c565b6100db565b005b348015610078575f5ffd5b50610093600480360381019061008e91906107d0565b61014c565b005b6100af60048036038101906100aa919061072c565b610260565b005b3480156100bc575f5ffd5b506100c56102df565b6040516100d29190610861565b60405180910390f35b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610140576040517fded4370e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61014981610260565b50565b5f465f5f81548092919061015f906108a7565b919050558560405160200161017693929190610960565b6040516020818303038152906040528051906020012090505f610198826102e4565b90505f6101e88286868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f82011690508083019250505050505050610317565b90503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461024f576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61025886610260565b505050505050565b805160205b818110156102da578083015160f81c6001820184015160601c60158301850151603584018601516055850187015f855f81146102a857600181146102b7576102bb565b5f5f8585888a5af191506102bb565b5f5ffd5b505f81036102c7575f5ffd5b8260550187019650505050505050610265565b505050565b5f5481565b5f7f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f5281601c52603c5f209050919050565b5f5f5f5f6103258686610341565b9250925092506103358282610396565b82935050505092915050565b5f5f5f6041845103610381575f5f5f602087015192506040870151915060608701515f1a9050610373888285856104f8565b95509550955050505061038f565b5f600285515f1b9250925092505b9250925092565b5f60038111156103a9576103a8610998565b5b8260038111156103bc576103bb610998565b5b03156104f457600160038111156103d6576103d5610998565b5b8260038111156103e9576103e8610998565b5b03610420576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600381111561043457610433610998565b5b82600381111561044757610446610998565b5b0361048b57805f1c6040517ffce698f70000000000000000000000000000000000000000000000000000000081526004016104829190610861565b60405180910390fd5b60038081111561049e5761049d610998565b5b8260038111156104b1576104b0610998565b5b036104f357806040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004016104ea91906109dd565b60405180910390fd5b5b5050565b5f5f5f7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0845f1c1115610534575f6003859250925092506105d5565b5f6001888888886040515f81526020016040526040516105579493929190610a11565b6020604051602081039080840390855afa158015610577573d5f5f3e3d5ffd5b5050506020604051035190505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036105c8575f60015f5f1b935093509350506105d5565b805f5f5f1b935093509350505b9450945094915050565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61063e826105f8565b810181811067ffffffffffffffff8211171561065d5761065c610608565b5b80604052505050565b5f61066f6105df565b905061067b8282610635565b919050565b5f67ffffffffffffffff82111561069a57610699610608565b5b6106a3826105f8565b9050602081019050919050565b828183375f83830152505050565b5f6106d06106cb84610680565b610666565b9050828152602081018484840111156106ec576106eb6105f4565b5b6106f78482856106b0565b509392505050565b5f82601f830112610713576107126105f0565b5b81356107238482602086016106be565b91505092915050565b5f60208284031215610741576107406105e8565b5b5f82013567ffffffffffffffff81111561075e5761075d6105ec565b5b61076a848285016106ff565b91505092915050565b5f5ffd5b5f5ffd5b5f5f83601f8401126107905761078f6105f0565b5b8235905067ffffffffffffffff8111156107ad576107ac610773565b5b6020830191508360018202830111156107c9576107c8610777565b5b9250929050565b5f5f5f604084860312156107e7576107e66105e8565b5b5f84013567ffffffffffffffff811115610804576108036105ec565b5b610810868287016106ff565b935050602084013567ffffffffffffffff811115610831576108306105ec565b5b61083d8682870161077b565b92509250509250925092565b5f819050919050565b61085b81610849565b82525050565b5f6020820190506108745f830184610852565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6108b182610849565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108e3576108e261087a565b5b600182019050919050565b5f819050919050565b61090861090382610849565b6108ee565b82525050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f61093a8261090e565b6109448185610918565b9350610954818560208601610922565b80840191505092915050565b5f61096b82866108f7565b60208201915061097b82856108f7565b60208201915061098b8284610930565b9150819050949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b5f819050919050565b6109d7816109c5565b82525050565b5f6020820190506109f05f8301846109ce565b92915050565b5f60ff82169050919050565b610a0b816109f6565b82525050565b5f608082019050610a245f8301876109ce565b610a316020830186610a02565b610a3e60408301856109ce565b610a4b60608301846109ce565b9594505050505056fea2646970667358221220b3afe6d2bcb8fd25bdb05b143b2c8d0d4f443504c856fc371db1d3ddf250725664736f6c634300081c0033", + "nonce": "0x39", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xf86ab5", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x23bfe882d57a8feeb40d999f62daa9cafaa2d3a5c30101c3afe71c196238c2c0", + "transactionIndex": "0x9c", + "blockHash": "0x8420fccb19cbd1292ac1da1411c867dfa2f2dc2e8b33974fbf87e019fad15496", + "blockNumber": "0x7e95af", + "gasUsed": "0x9ac45", + "effectiveGasPrice": "0xf4251", + "from": "0xae5a4884d813f334e1b80b26402ec78fadaee7eb", + "to": null, + "contractAddress": "0xda51ebfbb740d2183e91faf762666b169a1a9a62" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1746870410, + "chain": 11155111, + "commit": "cce2192" +} \ No newline at end of file diff --git a/script/DeployMultisend.s.sol b/script/DeployMultisend.s.sol index 9f78e33..46366f5 100644 --- a/script/DeployMultisend.s.sol +++ b/script/DeployMultisend.s.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.28; import "forge-std/Script.sol"; import "forge-std/Vm.sol"; import "src/EOAMultisend.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol"; -contract DeployBatchCaller is Script { +contract DeployMultisend is Script { EOAMultisend public multisend; function run() external { diff --git a/ts/index.ts b/ts/index.ts deleted file mode 100644 index 076adab..0000000 --- a/ts/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import dotenv from "dotenv"; -import { - createWalletClient, - http, - encodeFunctionData, - zeroAddress, -} from "viem"; -import { sepolia } from "viem/chains"; -import { privateKeyToAccount } from "viem/accounts"; -import { batchCallerABI, contractAddress } from "./contract"; - -dotenv.config(); -let privateKey = process.env.PK; -if (!privateKey) { - console.warn("Missing PK, using dummy account"); - privateKey = - "0xe4dc8cbe94cbc139084c9c7adc5c2a829d3246f76282679e0c067147a47eb3f8"; -} -const account = privateKeyToAccount(privateKey as `0x${string}`); - -export const walletClient = createWalletClient({ - account, - chain: sepolia, - transport: http(), -}); - -const run = async (): Promise => { - const authorization = await walletClient.signAuthorization({ - executor: "self", - contractAddress, - }); - - const hash = await walletClient.sendTransaction({ - authorizationList: [authorization], - data: encodeFunctionData({ - abi: batchCallerABI, - functionName: "execute", - args: [ - [ - { - to: zeroAddress, - value: 1n, - data: "0x", - }, - { - to: "0x1111111111111111111111111111111111111111", - value: 2n, - data: "0x", - }, - ], - ], - }), - to: walletClient.account.address, - }); - console.log("Batch Call with Hash", hash); -}; - -run(); diff --git a/ts/package.json b/ts/package.json index 5af840b..790b7dd 100644 --- a/ts/package.json +++ b/ts/package.json @@ -4,7 +4,7 @@ "type": "module", "private": true, "scripts": { - "fmt": "prettier --write *.ts" + "fmt": "prettier --write **/*.ts" }, "devDependencies": { "@types/bun": "latest", diff --git a/ts/contract.ts b/ts/src/contract.ts similarity index 94% rename from ts/contract.ts rename to ts/src/contract.ts index ad3c2a6..e512a8e 100644 --- a/ts/contract.ts +++ b/ts/src/contract.ts @@ -66,4 +66,5 @@ export const batchCallerABI = [ }, ] as const; -export const contractAddress = "0x862d8Fd8dc4979b1AA72E808E73773dD0AAC3211"; +export const BATCH_CALLER_ADDRESS = + "0x862d8Fd8dc4979b1AA72E808E73773dD0AAC3211"; diff --git a/ts/src/index.ts b/ts/src/index.ts new file mode 100644 index 0000000..b1bee81 --- /dev/null +++ b/ts/src/index.ts @@ -0,0 +1,108 @@ +import dotenv from "dotenv"; +import { + createWalletClient, + http, + encodeFunctionData, + zeroAddress, + toHex, + getAddress, + type Chain, + parseEther, +} from "viem"; +import { sepolia } from "viem/chains"; +import { privateKeyToAccount } from "viem/accounts"; +import { batchCallerABI, BATCH_CALLER_ADDRESS } from "./contract"; +import { + EOA_MULTISEND_ADDRESS, + type MetaTransaction, + encodeMulti, +} from "./multisend"; + +function printHash(chain: Chain, hash: `0x${string}`) { + const explorer = chain.blockExplorers?.default; + console.log(" Tx Receipt:", explorer?.url + "/tx/" + hash); +} + +async function batchCall(pk: `0x${string}`, calls: MetaTransaction[]) { + console.log("Batch Call"); + const account = privateKeyToAccount(pk); + + const walletClient = createWalletClient({ + account, + chain: sepolia, + transport: http(), + }); + const authorization = await walletClient.signAuthorization({ + executor: "self", + contractAddress: BATCH_CALLER_ADDRESS, + }); + + const hash = await walletClient.sendTransaction({ + authorizationList: [authorization], + data: encodeFunctionData({ + abi: batchCallerABI, + functionName: "execute", + args: [ + calls.map((c) => ({ + to: getAddress(c.to), + value: BigInt(c.value), + data: c.data as `0x${string}`, + })), + ], + }), + to: walletClient.account.address, + }); + printHash(walletClient.chain, hash); +} + +async function eoaMultisend(pk: `0x${string}`, calls: MetaTransaction[]) { + console.log("EOA Multisend"); + const account = privateKeyToAccount(pk); + + const walletClient = createWalletClient({ + account, + chain: sepolia, + transport: http(), + }); + const authorization = await walletClient.signAuthorization({ + executor: "self", + contractAddress: EOA_MULTISEND_ADDRESS, + }); + + const hash = await walletClient.sendTransaction({ + authorizationList: [authorization], + data: encodeMulti(calls), + to: walletClient.account.address, + }); + printHash(walletClient.chain, hash); +} + +const run = async (): Promise => { + dotenv.config(); + let privateKey = process.env.PK as `0x${string}`; + if (!privateKey) { + console.warn("Missing PK, using dummy account"); + privateKey = + "0xe4dc8cbe94cbc139084c9c7adc5c2a829d3246f76282679e0c067147a47eb3f8"; + } + + const calls = [ + { + to: zeroAddress, + value: toHex(parseEther("0.00001")), + data: "0x", + }, + { + to: "0x1111111111111111111111111111111111111111", + value: toHex(parseEther("0.00002")), + data: "0x", + }, + ]; + // This is the human readable version of the multisend call + // await batchCall(privateKey, calls); + + // This is compact - gas efficient representation. + await eoaMultisend(privateKey, calls); +}; + +run(); diff --git a/ts/src/multisend.ts b/ts/src/multisend.ts new file mode 100644 index 0000000..258931c --- /dev/null +++ b/ts/src/multisend.ts @@ -0,0 +1,120 @@ +import { + decodeFunctionData, + encodeFunctionData, + encodePacked, + getAddress, + parseAbi, + size, + toHex, +} from "viem"; +import type { Hex, Address } from "viem"; + +/** + * Enum representing the type of operation in a meta-transaction. + */ +export enum OperationType { + /** Standard call operation (0). */ + Call = 0, + /** Delegate call operation (1). */ + DelegateCall = 1, +} + +/** + * Represents a meta-transaction, which includes the destination address, value, data, and type of operation. + */ +export interface MetaTransaction { + /** The destination address for the meta-transaction. */ + readonly to: string; + /** The value to be sent with the transaction (as a string to handle large numbers). */ + readonly value: string; // TODO: Change to hex string! No Confusion. + /** The encoded data for the contract call or function execution. */ + readonly data: string; +} + +export const EOA_MULTI_SEND_ABI = ["function execute(bytes memory calls)"]; +export const MULTI_SEND_ABI = ["function multiSend(bytes memory transactions)"]; + +export const EOA_MULTISEND_ADDRESS = + "0xDa51eBfBb740D2183e91FAf762666B169A1A9a62"; + +/// Encodes the transaction as packed bytes of: +/// - `operation` as a `uint8` with `0` for a `call` or `1` for a `delegatecall` (=> 1 byte), +/// - `to` as an `address` (=> 20 bytes), +/// - `value` as a `uint256` (=> 32 bytes), +/// - length of `data` as a `uint256` (=> 32 bytes), +/// - `data` as `bytes`. +export const encodeMetaTx = (tx: MetaTransaction): Hex => + encodePacked( + ["uint8", "address", "uint256", "uint256", "bytes"], + [ + OperationType.Call, + tx.to as Address, + BigInt(tx.value), + BigInt(size(tx.data as Hex)), + tx.data as Hex, + ], + ); + +const remove0x = (hexString: Hex): string => hexString.slice(2); + +// Encodes a batch of module transactions into a single multiSend module transaction. +export function encodeMulti(calls: readonly MetaTransaction[]): Hex { + const encodedCalls = "0x" + calls.map(encodeMetaTx).map(remove0x).join(""); + return encodeFunctionData({ + abi: parseAbi(EOA_MULTI_SEND_ABI), + functionName: "execute", + args: [encodedCalls as Hex], + }); +} + +function unpack( + packed: string, + startIndex: number, +): { + operation: number; + to: string; + value: string; + data: string; + endIndex: number; +} { + // read operation from first 8 bits (= 2 hex digits) + const operation = parseInt(packed.substring(startIndex, startIndex + 2), 16); + // the next 40 characters are the to address + const to = getAddress( + `0x${packed.substring(startIndex + 2, startIndex + 42)}`, + ); + // then comes the uint256 value (= 64 hex digits) + const value = toHex( + BigInt(`0x${packed.substring(startIndex + 42, startIndex + 106)}`), + ); + // and the uint256 data length (= 64 hex digits) + const hexDataLength = parseInt( + packed.substring(startIndex + 106, startIndex + 170), + 16, + ); + const endIndex = startIndex + 170 + hexDataLength * 2; // * 2 because each hex item is represented with 2 digits + const data = `0x${packed.substring(startIndex + 170, endIndex)}`; + return { + operation, + to, + value, + data, + endIndex, + }; +} + +export function decodeMulti(data: Hex): MetaTransaction[] { + const tx = decodeFunctionData({ + abi: parseAbi(MULTI_SEND_ABI), + data, + }); + const [transactionsEncoded] = tx.args as [string]; + const result = []; + let startIndex = 2; // skip over 0x + while (startIndex < transactionsEncoded.length) { + const { endIndex, ...tx } = unpack(transactionsEncoded, startIndex); + result.push(tx); + startIndex = endIndex; + } + return result; +}