diff --git a/.env.example b/.env.example index 7ec0c47f..2c074f1c 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,6 @@ BUNDLER_URL= BICONOMY_SDK_DEBUG=false PAYMASTER_URL= PIMLICO_API_KEY= -TENDERLY_API_KEY= \ No newline at end of file +TENDERLY_API_KEY= +TENDERLY_ACCOUNT_SLUG= +TENDERLY_PROJECT_SLUG= \ No newline at end of file diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 8ec4ad5d..416a20a5 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -373,6 +373,31 @@ export const isTesting = () => { } } +type TenderlyDetails = { + accountSlug: string + projectSlug: string + apiKey: string +} +export const getTenderlyDetails = (): TenderlyDetails | null => { + try { + const accountSlug = process?.env?.TENDERLY_ACCOUNT_SLUG + const projectSlug = process?.env?.TENDERLY_PROJECT_SLUG + const apiKey = process?.env?.TENDERLY_API_KEY + + if (!accountSlug || !projectSlug || !apiKey) { + return null + } + + return { + accountSlug, + projectSlug, + apiKey + } + } catch (e) { + return null + } +} + /** * Safely multiplies a bigint by a number, rounding appropriately. * diff --git a/src/sdk/account/utils/getAAError.ts b/src/sdk/account/utils/getAAError.ts index 6025b6ba..db38afe5 100644 --- a/src/sdk/account/utils/getAAError.ts +++ b/src/sdk/account/utils/getAAError.ts @@ -21,16 +21,23 @@ const matchError = (message: string): null | KnownError => message.toLowerCase().indexOf(knownError.regex.toLowerCase()) > -1 ) ?? null -const buildErrorStrings = (error: KnownError, status: string): string[] => - [ - `${status}: ${error.description}\n`, - error.causes?.length - ? ["Potential cause(s): \n", ...error.causes, ""].join("\n") - : "", - error.solutions?.length - ? ["Potential solution(s): \n", ...error.solutions].join("\n") - : "" - ].filter(Boolean) +const buildErrorStrings = (error: KnownError, status: string): string[] => { + const strings: string[] = [] + + strings.push(`${status}: ${error.description}`) + + if (error.causes?.length) { + strings.push("Potential cause(s):") + strings.push(...error.causes) + } + + if (error.solutions?.length) { + strings.push("Potential solution(s):") + strings.push(...error.solutions) + } + + return strings +} type AccountAbstractionErrorParams = { docsSlug?: string @@ -53,8 +60,7 @@ export const getAAError = async (message: string, httpStatus?: number) => { knownErrors.push(...errors) } - const details: string = JSON.stringify(message) - const matchedError = matchError(details) + const matchedError = matchError(message) const status = matchedError?.regex ?? (httpStatus ?? UNKOWN_ERROR_CODE).toString() @@ -64,9 +70,5 @@ export const getAAError = async (message: string, httpStatus?: number) => { const title = matchedError ? matchedError.name : "Unknown Error" const docsSlug = matchedError?.docsUrl ?? DOCS_URL - return new AccountAbstractionError(title, { - docsSlug, - metaMessages, - details - }) + return { title, docsSlug, metaMessages, message } } diff --git a/src/sdk/clients/decorators/smartAccount/sendDebugUserOperation.ts b/src/sdk/clients/decorators/smartAccount/sendDebugUserOperation.ts index 873655ce..a3f0d9bf 100644 --- a/src/sdk/clients/decorators/smartAccount/sendDebugUserOperation.ts +++ b/src/sdk/clients/decorators/smartAccount/sendDebugUserOperation.ts @@ -1,15 +1,3 @@ -import type { - Address, - Assign, - BaseError, - Chain, - Client, - Hex, - MaybeRequired, - Narrow, - OneOf, - Transport -} from "viem" import { type DeriveEntryPointVersion, type DeriveSmartAccount, @@ -25,13 +13,33 @@ import { type UserOperationRequest, formatUserOperationRequest, getUserOperationError, - prepareUserOperation + prepareUserOperation, + toPackedUserOperation } from "viem/account-abstraction" -import { parseAccount } from "viem/accounts" + +import { + http, + type Assign, + type BaseError, + type Chain, + type Client, + type Hex, + type MaybeRequired, + type Narrow, + type OneOf, + type Transport, + createPublicClient, + parseEther, + zeroAddress +} from "viem" +import { type Address, parseAccount } from "viem/accounts" import { type RequestErrorType, getAction } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { generateRandomHex } from "../../../modules/utils/Uid" - +import { getTenderlyDetails } from "../../../account/utils/Utils" +import { deepHexlify } from "../../../account/utils/deepHexlify" +import { getAAError } from "../../../account/utils/getAAError" +import { getChain } from "../../../account/utils/getChain" +import { ENTRY_POINT_ADDRESS, EntrypointAbi } from "../../../constants" export type SendDebugUserOperationParameters< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = SmartAccount | undefined, @@ -118,6 +126,8 @@ export async function sendDebugUserOperation< client: Client, parameters: SendDebugUserOperationParameters ) { + const tenderlyDetails = getTenderlyDetails() + const { account: account_ = client.account, entryPointAddress } = parameters if (!account_ && !parameters.sender) throw new AccountNotFoundError() @@ -140,46 +150,84 @@ export async function sendDebugUserOperation< signature } as UserOperation) - console.log("Sending User Operation:", rpcParameters) - - // Create Tenderly debug URL - const tenderlyUrl = new URL( - "https://dashboard.tenderly.co/JoeBico/biconomy/simulator/new" + console.log("Sending UserOperation to bundler:", rpcParameters) + const packed = toPackedUserOperation(request as UserOperation) + console.log( + "Packed user operation: ", + JSON.stringify(deepHexlify(packed), null, 2) ) - const formattedRpcParams = { - callData: rpcParameters.callData, - callGasLimit: rpcParameters.callGasLimit, - maxFeePerGas: rpcParameters.maxFeePerGas, - maxPriorityFeePerGas: rpcParameters.maxPriorityFeePerGas, - nonce: rpcParameters.nonce, - paymasterPostOpGasLimit: rpcParameters.paymasterPostOpGasLimit, - paymasterVerificationGasLimit: rpcParameters.paymasterVerificationGasLimit, - preVerificationGas: rpcParameters.preVerificationGas, - sender: rpcParameters.sender, - signature: rpcParameters.signature, - verificationGasLimit: rpcParameters.verificationGasLimit + + const chainId = client.account?.client?.chain?.id?.toString() + + if (tenderlyDetails) { + const tenderlyUrl = new URL( + `https://dashboard.tenderly.co/${tenderlyDetails.accountSlug}/${tenderlyDetails.projectSlug}/simulator/new` + ) + + const formattedRpcParams = { + callData: rpcParameters.callData, + callGasLimit: rpcParameters.callGasLimit, + maxFeePerGas: rpcParameters.maxFeePerGas, + maxPriorityFeePerGas: rpcParameters.maxPriorityFeePerGas, + nonce: rpcParameters.nonce, + paymasterPostOpGasLimit: rpcParameters.paymasterPostOpGasLimit, + paymasterVerificationGasLimit: + rpcParameters.paymasterVerificationGasLimit, + preVerificationGas: rpcParameters.preVerificationGas, + sender: rpcParameters.sender, + signature: rpcParameters.signature, + verificationGasLimit: rpcParameters.verificationGasLimit + } + + const params = new URLSearchParams({ + contractAddress: "0x0000000071727de22e5e9d8baf0edac6f37da032", + value: "0", + network: chainId ?? "84532", + // simulationId: generateRandomHex(), + contractFunction: "0x765e827f", + rawFunctionInput: rpcParameters.callData, + functionInputs: JSON.stringify([formattedRpcParams]), + headerBlockNumber: "19463586", + headerTimestamp: "1734695460", + stateOverrides: JSON.stringify([ + { + contractAddress: rpcParameters.sender, + balance: "100000000000000000000" + } + ]) + }) + tenderlyUrl.search = params.toString() + console.log("Tenderly Simulation URL:", tenderlyUrl.toString()) + + // Also do a simulation using the publicClient + } else { + console.log( + "Tenderly details not found in environment variables. Please set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG." + ) } - const params = new URLSearchParams({ - contractAddress: "0x0000000071727de22e5e9d8baf0edac6f37da032", - value: "0", - network: "84532", - // simulationId: generateRandomHex(), - contractFunction: "0x765e827f", - rawFunctionInput: rpcParameters.callData, - functionInputs: JSON.stringify([formattedRpcParams]), - headerBlockNumber: "19463586", - headerTimestamp: "1734695460", - stateOverrides: JSON.stringify([ - { - contractAddress: rpcParameters.sender, - balance: "100000000000000000000" - } - ]) - }) - tenderlyUrl.search = params.toString() + try { + const simulation = await createPublicClient({ + chain: client.account?.client?.chain ?? getChain(Number(chainId)), + transport: http() + }).simulateContract({ + account: rpcParameters.sender, + address: ENTRY_POINT_ADDRESS, + abi: EntrypointAbi, + functionName: "handleOps", + args: [[packed], rpcParameters.sender], + stateOverride: [ + { + address: rpcParameters.sender, + balance: parseEther("1000") + } + ] + }) - console.log("Tenderly Simulation URL:", tenderlyUrl.toString()) + console.log("Simulation:", { simulation }) + } catch (error) { + console.error("Simulation failed") + } try { const hash = await client.request( @@ -197,6 +245,12 @@ export async function sendDebugUserOperation< return hash } catch (error) { // console.error("User Operation Failed:", error) + + if (error?.details) { + const aaError = await getAAError(error.details) + console.log({ aaError }) + } + const calls = (parameters as any).calls throw getUserOperationError(error as BaseError, { ...(request as UserOperation), diff --git a/src/sdk/constants/index.ts b/src/sdk/constants/index.ts index 1004c916..9ac76bcb 100644 --- a/src/sdk/constants/index.ts +++ b/src/sdk/constants/index.ts @@ -12,7 +12,7 @@ import { ParamCondition } from "../modules/smartSessionsValidator/Types" export * from "./abi" export const SIMPLE_SESSION_VALIDATOR_ADDRESS: Hex = - "0x41f143f4B5f19AfCd2602F6ADE18E75e9b5E37d3" + "0xE7A6F1a02151E50b600BC3d06FeEd70C6c4B19Bd" export const ENTRY_POINT_ADDRESS: Hex = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" export const ENTRYPOINT_SIMULATIONS_ADDRESS: Hex = diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts index f2a2306b..dca16831 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts @@ -126,27 +126,27 @@ describe("modules.smartSessions.enable.mode.dx", async () => { test("should pack user operation", async () => { const packed = toPackedUserOperation({ + verificationGasLimit: 10000000n, + paymasterVerificationGasLimit: 10000000n, + callGasLimit: 10000000n, + paymasterPostOpGasLimit: 10000000n, + preVerificationGas: 10000000n, signature: - "0x02010000e014010040e0141d020004802004e03300e0173f00e0e0155c0103c0e0151f0100012003e011001f014a34ae11a739a10c39be924306d512bff8c480de193a42f1209c99d1b2950c02e20f42e0033c1341f143f4b5f19afcd2602f6ade18e75e9b5e37d3e0031fe00a001fc06d47491264728e9233d06d2b0efbc5689de47b89e9fe3d419365676ef3d77d001de00a33e00200e015be201f00202004e012000101a0e0121c400014147a478affccf9ba4722b4fd42fb68e460d16fbc1e4018e03e00e2163f0100602003e05300e117400420273ea3e3e01f801314e4829e655f0b3a1793838ddd47273d5341d416e0163be017dfe0189fe0035f13529ad04f4d83aab25144a90267d4a1443b84f5a6e0031fe00a00e1177fe01700005520201f2d6db27c52e3c11c1cf24072004ac75cba04086e15e6bd4485230c7655d5d1e01f1bef115c8098335703bda6e3f8f565bd2c00485124334422c5798da167e63a5d1179826812046dd500cf2a1a7439b0c83d6d1c2054e01e001f414c83cd664b8fcb40e8e06152028f452d11e9ed58e80de3825645d3f53b16c31f4b7e84ab430dc42200d802a42e85b98eb1d5e319e32ceb4ee3b349e285025bdc01661be01168040000000000", + "0x02010000e014010040e0141d020004802004e03300e0173f00e0e0155c0103c0e0151f0100012003e011001f014a34ce499bbaebbe7d4959cfff7288cb93ef4092e01394aaa8d8be9a83965002ce564ee0033c13e7a6f1a02151e50b600bc3d06feed70c6c4b19bde0031fe00a001fc0cd2cb6e55eee9b4a40c46ffbacd016ba4a24478af6824c4f4a1c54fd408dde00a5e00a33e00200e015be201f00202004e012000101a0e0121c4000141485db11bee65cc7b724f84a59f6f28e12c908a34c4018e03e00e2163f0100602003e05300e117400420273ea3e3e01f801314e4829e655f0b3a1793838ddd47273d5341d416e0163be017dfe0189fe0035f13529ad04f4d83aab25144a90267d4a1443b84f5a6e0031fe00a00e1177fe01700005520201f2d6db27c52e3c11c1cf24072004ac75cba46b3ec7a493343de755d89cbadf12f1f7313bd4d71e406b0de146f86c859af98f73b62677b7ae474375b13ca50d09dc81104e7327697b7e823c1be631f6b851ed2f21b2054e01e001f415ed3370e1bb7f72b82d7080f02785f6e9765999f5bb76e043164e93af57fa31f4d2d513aaa6b04c14d3b64f98844b77815f9f2a72d3dad3a7f6c017fd90263cc01341ce01168040000000000", + sender: "0xdC66A533f27aC804B40c991F6016ceAAb8d7Defc", + maxFeePerGas: 1500582n, + maxPriorityFeePerGas: 1500000n, callData: "0xe9ae5c5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000003814e4829e655f0b3a1793838ddd47273d5341d4160000000000000000000000000000000000000000000000000000000000000000273ea3e30000000000000000", - sender: "0xdC66A533f27aC804B40c991F6016ceAAb8d7Defc", - maxFeePerGas: 3500855n, - maxPriorityFeePerGas: 3500000n, factory: undefined, factoryData: undefined, nonce: - 84423803952951738471617358635217431751803879867990993447121768113268552892416n, - callGasLimit: 1562217n, - verificationGasLimit: 1567305n, - paymasterPostOpGasLimit: 1567305n, - paymasterVerificationGasLimit: 1567305n, - preVerificationGas: 10000000000n + 24082891610152898234334765648976326605918059143978045864478758690797549256704n }) // console.log(JSON.stringify(deepHexlify(packed), null, 2)) expect(packed.nonce).toBe( - 84423803952951738471617358635217431751803879867990993447121768113268552892416n + 24082891610152898234334765648976326605918059143978045864478758690797549256704n ) console.log(JSON.stringify(deepHexlify(packed), null, 2))