From 30acdf798849553d7f385338395530c399a827e8 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 2 Oct 2024 14:53:06 +0200 Subject: [PATCH 1/3] decode TxData singular --- package.json | 1 + src/lib/multisend.ts | 8 ++++ src/near-safe.ts | 46 +++++++++++++++++++- src/types.ts | 15 ++++--- tests/unit/nearsafe.spec.ts | 42 +++++++++++++++++++ yarn.lock | 83 +++++++++++++++++++++++++++++++++++-- 6 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 tests/unit/nearsafe.spec.ts diff --git a/package.json b/package.json index 46b689f..6c0896c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "@safe-global/safe-gateway-typescript-sdk": "^3.22.2", + "ethers-multisend": "^3.1.0", "near-api-js": "^5.0.0", "near-ca": "^0.5.6", "semver": "^7.6.3", diff --git a/src/lib/multisend.ts b/src/lib/multisend.ts index cfdffc9..e23382e 100644 --- a/src/lib/multisend.ts +++ b/src/lib/multisend.ts @@ -58,3 +58,11 @@ export function encodeMulti( }), }; } + +export function isMultisendTx(args: readonly unknown[]): boolean { + const to = (args[0] as string).toLowerCase(); + return ( + to === MULTISEND_141.toLowerCase() || + to === MULTISEND_CALLONLY_141.toLowerCase() + ); +} diff --git a/src/near-safe.ts b/src/near-safe.ts index 0852c44..ed92cf2 100644 --- a/src/near-safe.ts +++ b/src/near-safe.ts @@ -1,3 +1,4 @@ +import { decodeMulti } from "ethers-multisend"; import { NearConfig } from "near-api-js/lib/near"; import { FinalExecutionOutcome } from "near-api-js/lib/providers"; import { @@ -9,14 +10,22 @@ import { toPayload, PersonalSignParams, } from "near-ca"; -import { Address, Hash, Hex, serializeSignature } from "viem"; +import { + Address, + decodeFunctionData, + formatEther, + Hash, + Hex, + serializeSignature, +} from "viem"; import { Erc4337Bundler } from "./lib/bundler"; -import { encodeMulti } from "./lib/multisend"; +import { encodeMulti, isMultisendTx } from "./lib/multisend"; import { SafeContractSuite } from "./lib/safe"; import { decodeSafeMessage } from "./lib/safe-message"; import { EncodedTxData, + EvmTransactionData, MetaTransaction, UserOperation, UserOperationReceipt, @@ -243,6 +252,39 @@ export class NearSafe { ); } + decodeTxData(data: EvmTransactionData): { + chainId: number; + costEstimate: string; + transactions: MetaTransaction[]; + } { + // TODO: data.data may not always parse to UserOperation. We will have to handle the other cases. + const userOp: UserOperation = JSON.parse(data.data); + const { callGasLimit, maxFeePerGas, maxPriorityFeePerGas } = userOp; + const maxGasPrice = BigInt(maxFeePerGas) + BigInt(maxPriorityFeePerGas); + const { args } = decodeFunctionData({ + abi: this.safePack.m4337.abi, + data: userOp.callData, + }); + + // Determine if sungular or double! + const transactions = isMultisendTx(args) + ? decodeMulti(args[2] as string) + : [ + { + to: args[0], + value: args[1], + data: args[2], + operation: args[3], + } as MetaTransaction, + ]; + return { + chainId: data.chainId, + // This is an upper bound on the gas fees (could be lower) + costEstimate: formatEther(BigInt(callGasLimit) * maxGasPrice), + transactions, + }; + } + /** * Handles routing of signature requests based on the provided method, chain ID, and parameters. * diff --git a/src/types.ts b/src/types.ts index 2203ddd..242277c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ import { FunctionCallTransaction, SignArgs } from "near-ca"; -import { Address, Hash, Hex, ParseAbi, TransactionSerializable } from "viem"; +import { Address, Hex, ParseAbi } from "viem"; export type SafeDeployments = { singleton: Deployment; @@ -115,12 +115,15 @@ export interface MetaTransaction { readonly data: string; readonly operation?: OperationType; } + +export interface EvmTransactionData { + chainId: number; + data: string; + hash: string; +} + export interface EncodedTxData { - evmData: { - chainId: number; - data: string | TransactionSerializable; - hash: Hash; - }; + evmData: EvmTransactionData; nearPayload: FunctionCallTransaction<{ request: SignArgs; }>; diff --git a/tests/unit/nearsafe.spec.ts b/tests/unit/nearsafe.spec.ts new file mode 100644 index 0000000..7a10b9e --- /dev/null +++ b/tests/unit/nearsafe.spec.ts @@ -0,0 +1,42 @@ +import { NearSafe } from "../../src"; +describe("NearSafe", () => { + it("decodeTxData (singular)", async () => { + const adapter = await NearSafe.create({ + accountId: "neareth-dev.testnet", + mpcContractId: "v1.signer-prod.testnet", + pimlicoKey: "dummyKey", + }); + const txData = { + chainId: 11155111, + data: JSON.stringify({ + sender: "0x4184cabfD63Da66828dE8486FE20DC015D800BbB", + nonce: "0xc", + callData: + "0x7bb374280000000000000000000000008d99f8b2710e6a3b94d9bf465a98e5273069acbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002B00B000000000000000000000000000000000000000000000000000000000000", + maxFeePerGas: "0x3575104828", + maxPriorityFeePerGas: "0x47868c00", + paymaster: "0x0000000000000039cd5e8aE05257CE51C473ddd1", + paymasterData: + "0x00000066fd3196000000000000ec1da1a540e872a6b82ad0e5622ff24e06f5a928477aa2f3c9655c1a92c2ed7f153b8bcb8e5ad52f734a58d498b60d58bd307f1d2ccbd5115f09773d804cbc0c1b", + preVerificationGas: "0xd257", + verificationGasLimit: "0x138b1", + callGasLimit: "0x14a6a", + paymasterVerificationGasLimit: "0x6c8e", + paymasterPostOpGasLimit: "0x1", + }), + hash: "0xfcfe9c2a4a5faade2bb5e9e483eece0fee77760db1698a6eb8060d1d33c56377", + }; + expect(adapter.decodeTxData(txData)).toStrictEqual({ + chainId: 11155111, + costEstimate: "0.019522217711724688", + transactions: [ + { + data: "0xb00b", + operation: 0, + to: "0x8d99F8b2710e6A3B94d9bf465A98E5273069aCBd", + value: 0n, + }, + ], + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index b4148d0..63d1a96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -479,7 +479,22 @@ dependencies: levn "^0.4.1" -"@ethersproject/abstract-provider@^5.7.0": +"@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@^5.5.1", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== @@ -503,7 +518,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@^5.7.0": +"@ethersproject/address@^5.0.0", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== @@ -521,7 +536,7 @@ dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/bignumber@^5.7.0": +"@ethersproject/bignumber@^5.0.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== @@ -530,7 +545,7 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@^5.7.0": +"@ethersproject/bytes@^5.0.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== @@ -544,6 +559,22 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" +"@ethersproject/contracts@^5.5.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" @@ -594,6 +625,15 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + "@ethersproject/signing-key@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" @@ -606,6 +646,18 @@ elliptic "6.5.4" hash.js "1.1.7" +"@ethersproject/solidity@^5.0.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" @@ -630,6 +682,15 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" +"@ethersproject/units@^5.0.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" @@ -2826,6 +2887,20 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +ethers-multisend@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ethers-multisend/-/ethers-multisend-3.1.0.tgz#76d6743904250c8f172635301732fc7e41b48a7b" + integrity sha512-PVfe4v9vs96cAages4yMoEAagOw0Hdh3fHe8IyMnLPNgV8KcGOY1Kv4NYnwB3t7EilnZbQ4jw3Nx0r+1Dc09kw== + dependencies: + "@ethersproject/abi" "^5.0.0" + "@ethersproject/abstract-provider" "^5.5.1" + "@ethersproject/address" "^5.0.0" + "@ethersproject/bignumber" "^5.0.0" + "@ethersproject/bytes" "^5.0.0" + "@ethersproject/contracts" "^5.5.0" + "@ethersproject/solidity" "^5.0.0" + "@ethersproject/units" "^5.0.0" + ethers@^6.13.1: version "6.13.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.2.tgz#4b67d4b49e69b59893931a032560999e5e4419fe" From 1638c4e9ad999988cf27cc0cfd428eca5b2bafc8 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 2 Oct 2024 15:16:51 +0200 Subject: [PATCH 2/3] add multisend test --- tests/unit/nearsafe.spec.ts | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/unit/nearsafe.spec.ts b/tests/unit/nearsafe.spec.ts index 7a10b9e..fa82b14 100644 --- a/tests/unit/nearsafe.spec.ts +++ b/tests/unit/nearsafe.spec.ts @@ -39,4 +39,55 @@ describe("NearSafe", () => { ], }); }); + + it("decodeTxData (multi)", async () => { + const adapter = await NearSafe.create({ + accountId: "neareth-dev.testnet", + mpcContractId: "v1.signer-prod.testnet", + pimlicoKey: "dummyKey", + }); + const txData = { + chainId: 11155111, + data: JSON.stringify({ + sender: "0x575a9D13B206EaF9d621c8626252ac32F72c1133", + nonce: "0x0", + factory: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + factoryData: + "0x1688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000569361e38aca310a578a5b3a271496849749490800000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + callData: + "0x7bb374280000000000000000000000009641d764fc13c8b624c04430c7356c1c7c8102e200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001a48d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014c00beef4dad0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003b00b1e0099999999999999999999999999999999999999990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000611112222444400575a9d13b206eaf9d621c8626252ac32f72c1133000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000440d582f130000000000000000000000007f01d9b227593e033bf8d6fc86e634d27aa855680000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + maxFeePerGas: "0x3fba4b0a48", + maxPriorityFeePerGas: "0x47868c00", + verificationGasLimit: "0x7a120", + callGasLimit: "0x186a0", + preVerificationGas: "0x186a0", + }), + hash: "0xb931864e6c59e44ad2753ef07dcb2dc98436119be90e69599b19b516175ac7f1", + }; + + expect(adapter.decodeTxData(txData)).toStrictEqual({ + chainId: 11155111, + costEstimate: "0.0274908419656", + transactions: [ + { + operation: 0, + to: "0xbeEf4Dad00000000000000000000000000000000", + value: "0x01", + data: "0xb00b1e", + }, + { + operation: 0, + to: "0x9999999999999999999999999999999999999999", + value: "0x00", + data: "0x111122224444", + }, + { + operation: 0, + to: "0x575a9D13B206EaF9d621c8626252ac32F72c1133", + value: "0x00", + data: "0x0d582f130000000000000000000000007f01d9b227593e033bf8d6fc86e634d27aa855680000000000000000000000000000000000000000000000000000000000000001", + }, + ], + }); + }); }); From 300837668bf1589e831cde9766820760262adf29 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 2 Oct 2024 15:19:50 +0200 Subject: [PATCH 3/3] rename test --- tests/unit/lib/safe.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/lib/safe.spec.ts b/tests/unit/lib/safe.spec.ts index 69d48fb..4dd1e1d 100644 --- a/tests/unit/lib/safe.spec.ts +++ b/tests/unit/lib/safe.spec.ts @@ -54,7 +54,7 @@ describe("Safe Pack", () => { ); }); - it("getSetup", async () => { + it("addressForSetup", async () => { const setup = viemPack.getSetup([zeroAddress]); const [eps0, vps0, eps1, vps1] = await Promise.all([ ethersPack.addressForSetup(setup),