diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 2e7e59f0..49650f76 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -433,3 +433,44 @@ export const getAllowance = async ( return approval as bigint } + +export function parseRequestArguments(input: string[]) { + const fieldsToOmit = [ + "callGasLimit", + "preVerificationGas", + "maxFeePerGas", + "maxPriorityFeePerGas", + "paymasterAndData", + "verificationGasLimit" + ] + + // Skip the first element which is just "Request Arguments:" + const argsString = input.slice(1).join("") + + // Split by newlines and filter out empty lines + const lines = argsString.split("\n").filter((line) => line.trim()) + + // Create an object from the key-value pairs + const result = lines.reduce( + (acc, line) => { + // Remove extra spaces and split by ':' + const [key, value] = line.split(":").map((s) => s.trim()) + + // Clean up the key (remove trailing spaces and colons) + const cleanKey = key.trim() + + // Clean up the value (remove 'gwei' and other units) + const cleanValue: string | number = value.replace("gwei", "").trim() + + if (fieldsToOmit.includes(cleanKey)) { + return acc + } + + acc[cleanKey] = cleanValue + return acc + }, + {} as Record + ) + + return result +} diff --git a/src/sdk/account/utils/contractSimulation.ts b/src/sdk/account/utils/contractSimulation.ts new file mode 100644 index 00000000..d8779fd7 --- /dev/null +++ b/src/sdk/account/utils/contractSimulation.ts @@ -0,0 +1,29 @@ +import { http, type Address, createPublicClient, parseEther } from "viem" +import { ENTRY_POINT_ADDRESS, EntrypointAbi } from "../../constants" +import { getChain } from "./getChain" +import { getSimulationUserOp } from "./tenderlySimulation" +import type { AnyUserOperation } from "./tenderlySimulation" + +export async function contractSimulation( + partialUserOp: AnyUserOperation, + chainId: number +) { + const packed = getSimulationUserOp(partialUserOp) + + return createPublicClient({ + chain: getChain(chainId), + transport: http() + }).simulateContract({ + account: partialUserOp.sender as Address, + address: ENTRY_POINT_ADDRESS, + abi: EntrypointAbi, + functionName: "handleOps", + args: [[packed], packed.sender], + stateOverride: [ + { + address: partialUserOp.sender as Address, + balance: parseEther("1000") + } + ] + }) +} diff --git a/src/sdk/account/utils/tenderlySimulation.ts b/src/sdk/account/utils/tenderlySimulation.ts new file mode 100644 index 00000000..3f479092 --- /dev/null +++ b/src/sdk/account/utils/tenderlySimulation.ts @@ -0,0 +1,66 @@ +import type { RpcUserOperation } from "viem" +import { + type UserOperation, + toPackedUserOperation +} from "viem/account-abstraction" +import { getTenderlyDetails } from "." +import { ENTRY_POINT_ADDRESS } from "../../constants" +import { deepHexlify } from "./deepHexlify" + +export type AnyUserOperation = Partial | RpcUserOperation> + +export const getSimulationUserOp = (partialUserOp: AnyUserOperation) => { + const simulationGasLimits = { + callGasLimit: 100_000_000_000n, + verificationGasLimit: 100_000_000_000n, + preVerificationGas: 1n, + maxFeePerGas: 100_000_000_000n, + maxPriorityFeePerGas: 1n, + paymasterVerificationGasLimit: 100_000_000_000n, + paymasterPostOpGasLimit: 100_000n + } + + const mergedUserOp = deepHexlify({ + ...simulationGasLimits, + ...partialUserOp + }) + + return toPackedUserOperation(mergedUserOp) +} + +export function tenderlySimulation( + partialUserOp: AnyUserOperation, + chainId = 84532 +) { + const tenderlyDetails = getTenderlyDetails() + + if (!tenderlyDetails) { + console.log( + "Tenderly details not found in environment variables. Please set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG." + ) + return null + } + + const tenderlyUrl = new URL( + `https://dashboard.tenderly.co/${tenderlyDetails.accountSlug}/${tenderlyDetails.projectSlug}/simulator/new` + ) + + const packedUserOp = getSimulationUserOp(partialUserOp) + + const params = new URLSearchParams({ + contractAddress: ENTRY_POINT_ADDRESS, + value: "0", + network: chainId.toString(), + contractFunction: "0x765e827f", // handleOps + functionInputs: JSON.stringify([packedUserOp]), + stateOverrides: JSON.stringify([ + { + contractAddress: packedUserOp.sender, + balance: "100000000000000000000" + } + ]) + }) + + tenderlyUrl.search = params.toString() + return tenderlyUrl.toString() +} diff --git a/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts b/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts index b68aaba8..eaffb92a 100644 --- a/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts +++ b/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts @@ -17,28 +17,24 @@ import { toPackedUserOperation } from "viem/account-abstraction" -import { - http, - type Assign, - type BaseError, - type Chain, - type Client, - type Hex, - type MaybeRequired, - type Narrow, - type OneOf, - type Transport, - createPublicClient, - parseEther +import type { + Assign, + BaseError, + Chain, + Client, + Hex, + MaybeRequired, + Narrow, + OneOf, + Transport } from "viem" import { type Address, parseAccount } from "viem/accounts" import { type RequestErrorType, getAction } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { getTenderlyDetails } from "../../../account/utils/Utils" +import { parseRequestArguments } 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" +import { tenderlySimulation } from "../../../account/utils/tenderlySimulation" export type DebugUserOperationParameters< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = SmartAccount | undefined, @@ -125,128 +121,79 @@ export async function debugUserOperation< client: Client, parameters: DebugUserOperationParameters ) { - const tenderlyDetails = getTenderlyDetails() - - const { account: account_ = client.account, entryPointAddress } = parameters - - if (!account_ && !parameters.sender) throw new AccountNotFoundError() - const account = account_ ? parseAccount(account_) : undefined + const chainId = Number(client.account?.client?.chain?.id?.toString() ?? 84532) - const request = account - ? await getAction( - client, - prepareUserOperation, - "prepareUserOperation" - )(parameters as unknown as PrepareUserOperationParameters) - : parameters - - // biome-ignore lint/style/noNonNullAssertion: - const signature = (parameters.signature || - (await account?.signUserOperation(request as UserOperation)))! - - const userOpWithSignature = { - ...request, - signature - } as UserOperation + try { + const { account: account_ = client.account, entryPointAddress } = parameters - const packed = toPackedUserOperation(userOpWithSignature) - console.log( - "Packed userOp:\n", - JSON.stringify([deepHexlify(packed)], null, 2) - ) - const rpcParameters = formatUserOperationRequest(userOpWithSignature) - console.log("Bundler userOp:", rpcParameters) + if (!account_ && !parameters.sender) throw new AccountNotFoundError() + const account = account_ ? parseAccount(account_) : undefined - const chainId = client.account?.client?.chain?.id?.toString() + const request = account + ? await getAction( + client, + prepareUserOperation, + "prepareUserOperation" + )(parameters as unknown as PrepareUserOperationParameters) + : parameters - if (tenderlyDetails) { - const tenderlyUrl = new URL( - `https://dashboard.tenderly.co/${tenderlyDetails.accountSlug}/${tenderlyDetails.projectSlug}/simulator/new` - ) + // biome-ignore lint/style/noNonNullAssertion: + const signature = (parameters.signature || + (await account?.signUserOperation(request as UserOperation)))! - const formattedRpcParams = { - sender: rpcParameters.sender, - nonce: rpcParameters.nonce, - initCode: rpcParameters.initCode, - callData: rpcParameters.callData, - accountGasLimits: rpcParameters.callGasLimit, - preVerificationGas: rpcParameters.preVerificationGas, - gasFees: rpcParameters.maxFeePerGas, - maxPriorityFeePerGas: rpcParameters.maxPriorityFeePerGas, - paymasterAndData: rpcParameters.paymasterAndData, - signature: rpcParameters.signature - } + const userOpWithSignature = { + ...request, + signature + } as UserOperation - const params = new URLSearchParams({ - contractAddress: ENTRY_POINT_ADDRESS, - value: "0", - network: chainId ?? "84532", - contractFunction: "0x765e827f", - rawFunctionInput: packed.callData, - functionInputs: JSON.stringify([formattedRpcParams]), - stateOverrides: JSON.stringify([ - { - contractAddress: rpcParameters.sender, - balance: "100000000000000000000" - } - ]) - }) - tenderlyUrl.search = params.toString() - } else { + const packed = toPackedUserOperation(userOpWithSignature) console.log( - "Tenderly details not found in environment variables. Please set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG." + "Packed userOp:\n", + JSON.stringify([deepHexlify(packed)], null, 2) ) - } + const rpcParameters = formatUserOperationRequest(userOpWithSignature) + console.log("Bundler userOp:", rpcParameters) - 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") - } - ] - }) + const tenderlyUrl = tenderlySimulation(rpcParameters, chainId) + console.log({ tenderlyUrl }) - console.log("Simulation:", { simulation }) - } catch (error) { - console.error("Simulation failed") - } - - try { - const hash = await client.request( - { - method: "eth_sendUserOperation", - params: [ - rpcParameters, - // biome-ignore lint/style/noNonNullAssertion: - (entryPointAddress ?? account?.entryPoint.address)! - ] - }, - { retryCount: 0 } - ) - console.log("User Operation Hash:", hash) - return hash + try { + const hash = await client.request( + { + method: "eth_sendUserOperation", + params: [ + rpcParameters, + // biome-ignore lint/style/noNonNullAssertion: + (entryPointAddress ?? account?.entryPoint.address)! + ] + }, + { retryCount: 0 } + ) + console.log("User Operation Hash:", hash) + return hash + // biome-ignore lint/suspicious/noExplicitAny: + } catch (error: any) { + 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), + ...(calls ? { calls } : {}), + signature + }) + } // biome-ignore lint/suspicious/noExplicitAny: } catch (error: any) { - if (error?.details) { - const aaError = await getAAError(error?.details) - console.log({ aaError }) + if (error.metaMessages) { + try { + const messageJson = parseRequestArguments(error.metaMessages) + const tenderlyUrl = tenderlySimulation(messageJson) + console.log({ tenderlyUrl }) + } catch (error) {} } - - const calls = (parameters as any).calls - throw getUserOperationError(error as BaseError, { - ...(request as UserOperation), - ...(calls ? { calls } : {}), - signature - }) + throw error } } diff --git a/src/sdk/modules/smartSessionsValidator/Types.ts b/src/sdk/modules/smartSessionsValidator/Types.ts index cc63d037..39be3a7a 100644 --- a/src/sdk/modules/smartSessionsValidator/Types.ts +++ b/src/sdk/modules/smartSessionsValidator/Types.ts @@ -205,7 +205,7 @@ export type Rule = { */ export type RawParamRule = { condition: ParamCondition - offset: bigint + offset: number isLimited: boolean ref: Hex usage: LimitUsage diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index 0a626e58..d0e02670 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -174,11 +174,6 @@ describe("modules.smartSessions.dx", async () => { const byteCode = await testClient.getCode({ address: testAddresses.Counter }) - console.log( - "testAddresses.Counter", - testAddresses.Counter, - byteCode?.length - ) const userOpHash = await useSmartSessionNexusClient.usePermission({ calls: [ {