diff --git a/demo/Invoice.ts b/demo/Invoice.ts new file mode 100644 index 0000000..e62687c --- /dev/null +++ b/demo/Invoice.ts @@ -0,0 +1,128 @@ +import { z } from "zod"; +import { + encodeAbiParameters, + encodePacked, + getAddress, + Hex, + keccak256, +} from "viem"; +import fs from "fs/promises"; +import path from "path"; +import { getRepayTokens } from "./utils"; + +const RepayTokenInfoSchema = z.object({ + vault: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/) + .transform((val) => getAddress(val)), + amount: z.string().transform((val) => BigInt(val)), + chainId: z.string().transform((val) => BigInt(val)), +}); + +const InvoiceWithRepayTokensSchema = z.object({ + account: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/) + .transform((val) => getAddress(val)), + paymaster: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/) + .transform((val) => getAddress(val)), + nonce: z.string().transform((val) => BigInt(val)), + sponsorChainId: z.string().transform((val) => BigInt(val)), + repayTokenInfos: z.array(RepayTokenInfoSchema), +}); + +const InvoiceIdSchema = z + .string() + .regex(/^0x[a-fA-F0-9]{64}$/, "Must be a 32-byte hex string"); + +const InvoicesSchema = z.record(InvoiceIdSchema, InvoiceWithRepayTokensSchema); + +export type InvoiceId = z.infer; +export type InvoiceWithRepayTokens = z.infer< + typeof InvoiceWithRepayTokensSchema +>; +export type Invoices = z.infer; + +export interface InvoiceIO { + readInvoice(invoiceId: InvoiceId): Promise; + writeInvoice(invoice: InvoiceWithRepayTokens): Promise; +} + +/** + * InvoiceManager reads and writes invoices to a JSON file. + * FOR DEMO PURPOSES ONLY - implement InvoiceIO to use a database in production. + */ + +class InvoiceManager implements InvoiceIO { + private invoicesPath: string; + + constructor(invoicesPath: string) { + this.invoicesPath = invoicesPath; + } + + async readInvoice(invoiceId: InvoiceId): Promise { + const invoices = await this.readInvoices(); + const invoice = invoices[invoiceId]; + if (!invoice) { + throw new Error(`Invoice ID ${invoiceId} not found`); + } + return invoice; + } + + async writeInvoice(invoice: InvoiceWithRepayTokens): Promise { + try { + const invoices = await this.readInvoices(); + const invoiceId = this.getInvoiceId(invoice); + invoices[invoiceId] = invoice; + const serializedInvoices = JSON.stringify( + invoices, + (key, value) => (typeof value === "bigint" ? value.toString() : value), + 2, + ); + await fs.writeFile(this.invoicesPath, serializedInvoices); + return invoiceId; + } catch (error: any) { + console.error("Error handling invoices:", error.message); + throw new Error("Failed to write the invoice."); + } + } + + getInvoiceId(invoice: InvoiceWithRepayTokens): InvoiceId { + const repayTokensEncoded = getRepayTokens(invoice.account); + const packed = encodePacked( + ["address", "address", "uint256", "uint256", "bytes"], + [ + invoice.account, + invoice.paymaster, + invoice.nonce, + invoice.sponsorChainId, + repayTokensEncoded, + ], + ); + return keccak256(packed) as InvoiceId; + } + + private async readInvoices(): Promise { + try { + const fileContent = await fs.readFile(this.invoicesPath, "utf8"); + const invoicesJson = fileContent ? JSON.parse(fileContent) : {}; + return InvoicesSchema.parse(invoicesJson); + } catch (err: any) { + if (err.code === "ENOENT") { + throw new Error(`File not found at path: ${this.invoicesPath}`); + } else if (err.name === "SyntaxError") { + throw new Error(`Invalid JSON format in file: ${this.invoicesPath}`); + } else if (err instanceof z.ZodError) { + throw new Error(`Validation error: ${err.message}`); + } else { + throw new Error(`An unexpected error occurred: ${err.message}`); + } + } + } +} + +export const invoiceManager = new InvoiceManager( + path.join(__dirname, "invoices.json"), +); diff --git a/demo/SimpleSmartAccount.ts b/demo/SimpleSmartAccount.ts index 5ec78ff..663cc1d 100644 --- a/demo/SimpleSmartAccount.ts +++ b/demo/SimpleSmartAccount.ts @@ -17,8 +17,8 @@ import { encodeDeployData, encodeFunctionData, getAddress, -} from "viem" -import { toAccount } from "viem/accounts" +} from "viem"; +import { toAccount } from "viem/accounts"; import { type SmartAccount, type SmartAccountImplementation, @@ -27,522 +27,513 @@ import { entryPoint07Abi, entryPoint07Address, getUserOperationHash, - toSmartAccount -} from "viem/account-abstraction" -import { getChainId, readContract, signMessage, call, signTypedData } from "viem/actions" -import { getAction } from "viem/utils" + toSmartAccount, +} from "viem/account-abstraction"; +import { + getChainId, + readContract, + signMessage, + call, + signTypedData, +} from "viem/actions"; +import { getAction } from "viem/utils"; export const getAccountInitCode = async ( owner: Address, - salt: bigint + salt: bigint, ): Promise => { - if (!owner) throw new Error("Owner account not found") + if (!owner) throw new Error("Owner account not found"); return encodeFunctionData({ - abi: [ + abi: [ + { + inputs: [ { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address" - }, - { - internalType: "uint256", - name: "salt", - type: "uint256" - } - ], - name: "createAccount", - outputs: [ - { - internalType: "contract SimpleAccount", - name: "ret", - type: "address" - } - ], - stateMutability: "nonpayable", - type: "function" - } - ], - functionName: "createAccount", - args: [owner, salt] - }) -} + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + ], + name: "createAccount", + outputs: [ + { + internalType: "contract SimpleAccount", + name: "ret", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "createAccount", + args: [owner, salt], + }); +}; export type ToSimpleSmartAccountParameters< - entryPointVersion extends "0.6" | "0.7" + entryPointVersion extends "0.6" | "0.7", > = { - client: Client + client: Client; owner: OneOf< - | EthereumProvider - | WalletClient - | LocalAccount - > - factoryAddress?: Address + | EthereumProvider + | WalletClient + | LocalAccount + >; + factoryAddress?: Address; entryPoint?: { - address: Address - version: entryPointVersion - } - salt?: bigint - address?: Address - nonceKey?: bigint -} + address: Address; + version: entryPointVersion; + }; + salt?: bigint; + address?: Address; + nonceKey?: bigint; +}; const getFactoryAddress = ( entryPointVersion: "0.6" | "0.7", - factoryAddress?: Address + factoryAddress?: Address, ): Address => { - if (factoryAddress) return factoryAddress + if (factoryAddress) return factoryAddress; if (entryPointVersion === "0.6") { - return "0x9406Cc6185a346906296840746125a0E44976454" + return "0x9406Cc6185a346906296840746125a0E44976454"; } - return "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985" -} + return "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985"; +}; export type SimpleSmartAccountImplementation< - entryPointVersion extends "0.6" | "0.7" = "0.7" + entryPointVersion extends "0.6" | "0.7" = "0.7", > = Assign< SmartAccountImplementation< - entryPointVersion extends "0.6" - ? typeof entryPoint06Abi - : typeof entryPoint07Abi, - entryPointVersion - // { - // // entryPoint === ENTRYPOINT_ADDRESS_V06 ? "0.2.2" : "0.3.0-beta" - // abi: entryPointVersion extends "0.6" ? typeof BiconomyAbi - // factory: { abi: typeof FactoryAbi; address: Address } - // } + entryPointVersion extends "0.6" + ? typeof entryPoint06Abi + : typeof entryPoint07Abi, + entryPointVersion + // { + // // entryPoint === ENTRYPOINT_ADDRESS_V06 ? "0.2.2" : "0.3.0-beta" + // abi: entryPointVersion extends "0.6" ? typeof BiconomyAbi + // factory: { abi: typeof FactoryAbi; address: Address } + // } >, { sign: NonNullable } -> +>; export type ToSimpleSmartAccountReturnType< - entryPointVersion extends "0.6" | "0.7" = "0.7" -> = SmartAccount> + entryPointVersion extends "0.6" | "0.7" = "0.7", +> = SmartAccount>; /** -* @description Creates an Simple Account from a private key. -* -* @returns A Private Key Simple Account. -*/ + * @description Creates an Simple Account from a private key. + * + * @returns A Private Key Simple Account. + */ export async function toSimpleSmartAccount< - entryPointVersion extends "0.6" | "0.7" + entryPointVersion extends "0.6" | "0.7", >( - parameters: ToSimpleSmartAccountParameters + parameters: ToSimpleSmartAccountParameters, ): Promise> { const { - client, - owner, - factoryAddress: _factoryAddress, - salt, - address, - nonceKey - } = parameters + client, + owner, + factoryAddress: _factoryAddress, + salt, + address, + nonceKey, + } = parameters; - const localOwner = await toOwner({ owner }) + const localOwner = await toOwner({ owner }); const entryPoint = { - address: parameters.entryPoint?.address ?? entryPoint07Address, - abi: - (parameters.entryPoint?.version ?? "0.7") === "0.6" - ? entryPoint06Abi - : entryPoint07Abi, - version: parameters.entryPoint?.version ?? "0.7" - } as const + address: parameters.entryPoint?.address ?? entryPoint07Address, + abi: + (parameters.entryPoint?.version ?? "0.7") === "0.6" + ? entryPoint06Abi + : entryPoint07Abi, + version: parameters.entryPoint?.version ?? "0.7", + } as const; - const factoryAddress = getFactoryAddress( - entryPoint.version, - _factoryAddress - ) + const factoryAddress = getFactoryAddress(entryPoint.version, _factoryAddress); - let accountAddress: Address | undefined = address + let accountAddress: Address | undefined = address; - let chainId: number + let chainId: number; const getMemoizedChainId = async () => { - if (chainId) return chainId - chainId = client.chain - ? client.chain.id - : await getAction(client, getChainId, "getChainId")({}) - return chainId - } + if (chainId) return chainId; + chainId = client.chain + ? client.chain.id + : await getAction(client, getChainId, "getChainId")({}); + return chainId; + }; const getFactoryArgs = async () => { - return { - factory: factoryAddress, - factoryData: await getAccountInitCode(localOwner.address, salt ?? BigInt(0)) - } - } + return { + factory: factoryAddress, + factoryData: await getAccountInitCode( + localOwner.address, + salt ?? BigInt(0), + ), + }; + }; return toSmartAccount({ - client, - entryPoint, - getFactoryArgs, - async getAddress() { - if (accountAddress) return accountAddress - const { factory, factoryData } = await getFactoryArgs() - // Get the sender address based on the init code - accountAddress = await getSenderAddress(client, { - factory, - factoryData, - entryPointAddress: entryPoint.address - }) - - return accountAddress - }, - async encodeCalls(calls) { - if (calls.length > 1) { - if (entryPoint.version === "0.6") { - return encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address[]", - name: "dest", - type: "address[]" - }, - { - internalType: "bytes[]", - name: "func", - type: "bytes[]" - } - ], - name: "executeBatch", - outputs: [], - stateMutability: "nonpayable", - type: "function" - } - ], - functionName: "executeBatch", - args: [ - calls.map((a) => a.to), - calls.map((a) => a.data ?? "0x") - ] - }) - } - return encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address[]", - name: "dest", - type: "address[]" - }, - { - internalType: "uint256[]", - name: "value", - type: "uint256[]" - }, - { - internalType: "bytes[]", - name: "func", - type: "bytes[]" - } - ], - name: "executeBatch", - outputs: [], - stateMutability: "nonpayable", - type: "function" - } - ], - functionName: "executeBatch", - args: [ - calls.map((a) => a.to), - calls.map((a) => a.value ?? 0n), - calls.map((a) => a.data ?? "0x") - ] - }) - } - - const call = calls.length === 0 ? undefined : calls[0] - - if (!call) { - throw new Error("No calls to encode") - } - + client, + entryPoint, + getFactoryArgs, + async getAddress() { + if (accountAddress) return accountAddress; + const { factory, factoryData } = await getFactoryArgs(); + // Get the sender address based on the init code + accountAddress = await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address, + }); + + return accountAddress; + }, + async encodeCalls(calls) { + if (calls.length > 1) { + if (entryPoint.version === "0.6") { return encodeFunctionData({ - abi: [ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "dest", + type: "address[]", + }, { - inputs: [ - { - internalType: "address", - name: "dest", - type: "address" - }, - { - internalType: "uint256", - name: "value", - type: "uint256" - }, - { - internalType: "bytes", - name: "func", - type: "bytes" - } - ], - name: "execute", - outputs: [], - stateMutability: "nonpayable", - type: "function" - } + internalType: "bytes[]", + name: "func", + type: "bytes[]", + }, + ], + name: "executeBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "executeBatch", + args: [calls.map((a) => a.to), calls.map((a) => a.data ?? "0x")], + }); + } + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "dest", + type: "address[]", + }, + { + internalType: "uint256[]", + name: "value", + type: "uint256[]", + }, + { + internalType: "bytes[]", + name: "func", + type: "bytes[]", + }, ], - functionName: "execute", - args: [call.to, call.value ?? 0n, call.data ?? "0x"] - }) - }, - async getNonce(args) { - return getAccountNonce(client, { - address: await this.getAddress(), - entryPointAddress: entryPoint.address, - key: nonceKey ?? args?.key - }) - }, - async getStubSignature() { - return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" - }, - async sign({ hash }) { - return this.signMessage({ message: hash }) - }, - signMessage: async (_) => { - throw new Error("Simple account isn't 1271 compliant") - }, - signTypedData: async (_) => { - throw new Error("Simple account isn't 1271 compliant") - }, - async signUserOperation(parameters) { - const { chainId = await getMemoizedChainId(), ...userOperation } = - parameters - return signMessage(client, { - account: localOwner, - message: { - raw: getUserOperationHash({ - userOperation: { - ...userOperation, - sender: - userOperation.sender ?? - (await this.getAddress()), - signature: "0x" - } as UserOperation, - entryPointAddress: entryPoint.address, - entryPointVersion: entryPoint.version, - chainId: chainId - }) - } - }) + name: "executeBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "executeBatch", + args: [ + calls.map((a) => a.to), + calls.map((a) => a.value ?? 0n), + calls.map((a) => a.data ?? "0x"), + ], + }); } - }) as Promise> -} + const call = calls.length === 0 ? undefined : calls[0]; -export type GetAccountNonceParams = { - address: Address - entryPointAddress: Address - key?: bigint + if (!call) { + throw new Error("No calls to encode"); + } + + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "dest", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "func", + type: "bytes", + }, + ], + name: "execute", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "execute", + args: [call.to, call.value ?? 0n, call.data ?? "0x"], + }); + }, + async getNonce(args) { + return getAccountNonce(client, { + address: await this.getAddress(), + entryPointAddress: entryPoint.address, + key: nonceKey ?? args?.key, + }); + }, + async getStubSignature() { + return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; + }, + async sign({ hash }) { + return this.signMessage({ message: hash }); + }, + signMessage: async (_) => { + throw new Error("Simple account isn't 1271 compliant"); + }, + signTypedData: async (_) => { + throw new Error("Simple account isn't 1271 compliant"); + }, + async signUserOperation(parameters) { + const { chainId = await getMemoizedChainId(), ...userOperation } = + parameters; + return signMessage(client, { + account: localOwner, + message: { + raw: getUserOperationHash({ + userOperation: { + ...userOperation, + sender: userOperation.sender ?? (await this.getAddress()), + signature: "0x", + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, + chainId: chainId, + }), + }, + }); + }, + }) as Promise>; } +export type GetAccountNonceParams = { + address: Address; + entryPointAddress: Address; + key?: bigint; +}; export const getAccountNonce = async ( client: Client, - args: GetAccountNonceParams + args: GetAccountNonceParams, ): Promise => { - const { address, entryPointAddress, key = BigInt(0) } = args + const { address, entryPointAddress, key = BigInt(0) } = args; return await getAction( - client, - readContract, - "readContract" + client, + readContract, + "readContract", )({ - address: entryPointAddress, - abi: [ + address: entryPointAddress, + abi: [ + { + inputs: [ { - inputs: [ - { - name: "sender", - type: "address" - }, - { - name: "key", - type: "uint192" - } - ], - name: "getNonce", - outputs: [ - { - name: "nonce", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - } - ], - functionName: "getNonce", - args: [address, key] - }) -} - + name: "sender", + type: "address", + }, + { + name: "key", + type: "uint192", + }, + ], + name: "getNonce", + outputs: [ + { + name: "nonce", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + ], + functionName: "getNonce", + args: [address, key], + }); +}; const GetSenderAddressHelperByteCode = - "0x6080604052604051610302380380610302833981016040819052610022916101de565b600080836001600160a01b0316639b249f6960e01b8460405160240161004891906102b2565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161008691906102e5565b6000604051808303816000865af19150503d80600081146100c3576040519150601f19603f3d011682016040523d82523d6000602084013e6100c8565b606091505b5091509150600082610148576004825111156100ef5760248201519050806000526014600cf35b60405162461bcd60e51b8152602060048201526024808201527f67657453656e64657241646472657373206661696c656420776974686f7574206044820152636461746160e01b60648201526084015b60405180910390fd5b60405162461bcd60e51b815260206004820152602b60248201527f67657453656e6465724164647265737320646964206e6f74207265766572742060448201526a185cc8195e1c1958dd195960aa1b606482015260840161013f565b634e487b7160e01b600052604160045260246000fd5b60005b838110156101d55781810151838201526020016101bd565b50506000910152565b600080604083850312156101f157600080fd5b82516001600160a01b038116811461020857600080fd5b60208401519092506001600160401b0381111561022457600080fd5b8301601f8101851361023557600080fd5b80516001600160401b0381111561024e5761024e6101a4565b604051601f8201601f19908116603f011681016001600160401b038111828210171561027c5761027c6101a4565b60405281815282820160200187101561029457600080fd5b6102a58260208301602086016101ba565b8093505050509250929050565b60208152600082518060208401526102d18160408501602087016101ba565b601f01601f19169190910160400192915050565b600082516102f78184602087016101ba565b919091019291505056fe" + "0x6080604052604051610302380380610302833981016040819052610022916101de565b600080836001600160a01b0316639b249f6960e01b8460405160240161004891906102b2565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161008691906102e5565b6000604051808303816000865af19150503d80600081146100c3576040519150601f19603f3d011682016040523d82523d6000602084013e6100c8565b606091505b5091509150600082610148576004825111156100ef5760248201519050806000526014600cf35b60405162461bcd60e51b8152602060048201526024808201527f67657453656e64657241646472657373206661696c656420776974686f7574206044820152636461746160e01b60648201526084015b60405180910390fd5b60405162461bcd60e51b815260206004820152602b60248201527f67657453656e6465724164647265737320646964206e6f74207265766572742060448201526a185cc8195e1c1958dd195960aa1b606482015260840161013f565b634e487b7160e01b600052604160045260246000fd5b60005b838110156101d55781810151838201526020016101bd565b50506000910152565b600080604083850312156101f157600080fd5b82516001600160a01b038116811461020857600080fd5b60208401519092506001600160401b0381111561022457600080fd5b8301601f8101851361023557600080fd5b80516001600160401b0381111561024e5761024e6101a4565b604051601f8201601f19908116603f011681016001600160401b038111828210171561027c5761027c6101a4565b60405281815282820160200187101561029457600080fd5b6102a58260208301602086016101ba565b8093505050509250929050565b60208152600082518060208401526102d18160408501602087016101ba565b601f01601f19169190910160400192915050565b600082516102f78184602087016101ba565b919091019291505056fe"; const GetSenderAddressHelperAbi = [ - { - inputs: [ - { - internalType: "address", - name: "_entryPoint", - type: "address" - }, - { - internalType: "bytes", - name: "initCode", - type: "bytes" - } - ], - stateMutability: "payable", - type: "constructor" - } -] + { + inputs: [ + { + internalType: "address", + name: "_entryPoint", + type: "address", + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + ], + stateMutability: "payable", + type: "constructor", + }, +]; export type GetSenderAddressParams = OneOf< - | { - initCode: Hex - entryPointAddress: Address - factory?: never - factoryData?: never - } - | { - entryPointAddress: Address - factory: Address - factoryData: Hex - initCode?: never - } -> + | { + initCode: Hex; + entryPointAddress: Address; + factory?: never; + factoryData?: never; + } + | { + entryPointAddress: Address; + factory: Address; + factoryData: Hex; + initCode?: never; + } +>; export class InvalidEntryPointError extends BaseError { - override name = "InvalidEntryPointError" - - constructor({ + override name = "InvalidEntryPointError"; + + constructor({ + cause, + entryPointAddress, + }: { cause?: BaseError; entryPointAddress?: Address } = {}) { + super( + `The entry point address (\`entryPoint\`${ + entryPointAddress ? ` = ${entryPointAddress}` : "" + }) is not a valid entry point. getSenderAddress did not revert with a SenderAddressResult error.`, + { cause, - entryPointAddress - }: { cause?: BaseError; entryPointAddress?: Address } = {}) { - super( - `The entry point address (\`entryPoint\`${ - entryPointAddress ? ` = ${entryPointAddress}` : "" - }) is not a valid entry point. getSenderAddress did not revert with a SenderAddressResult error.`, - { - cause - } - ) - } + }, + ); + } } export const getSenderAddress = async ( - client: Client, - args: Prettify + client: Client, + args: Prettify, ): Promise
=> { - const { initCode, entryPointAddress, factory, factoryData } = args + const { initCode, entryPointAddress, factory, factoryData } = args; - if (!initCode && !factory && !factoryData) { - throw new Error( - "Either `initCode` or `factory` and `factoryData` must be provided" - ) - } + if (!initCode && !factory && !factoryData) { + throw new Error( + "Either `initCode` or `factory` and `factoryData` must be provided", + ); + } - const formattedInitCode = - initCode || concat([factory as Hex, factoryData as Hex]) - - const { data } = await getAction( - client, - call, - "call" - )({ - data: encodeDeployData({ - abi: GetSenderAddressHelperAbi, - bytecode: GetSenderAddressHelperByteCode, - args: [entryPointAddress, formattedInitCode] - }) - }) - - if (!data) { - throw new Error("Failed to get sender address") - } + const formattedInitCode = + initCode || concat([factory as Hex, factoryData as Hex]); - return getAddress(data) + const { data } = await getAction( + client, + call, + "call", + )({ + data: encodeDeployData({ + abi: GetSenderAddressHelperAbi, + bytecode: GetSenderAddressHelperByteCode, + args: [entryPointAddress, formattedInitCode], + }), + }); + + if (!data) { + throw new Error("Failed to get sender address"); } + return getAddress(data); +}; -export type EthereumProvider = { request(...args: any): Promise } +export type EthereumProvider = { request(...args: any): Promise }; export async function toOwner({ - owner, - address + owner, + address, }: { - owner: OneOf< - | provider - | WalletClient - | LocalAccount - > - address?: Address + owner: OneOf< + | provider + | WalletClient + | LocalAccount + >; + address?: Address; }): Promise { - if ("type" in owner && owner.type === "local") { - return owner as LocalAccount - } + if ("type" in owner && owner.type === "local") { + return owner as LocalAccount; + } - let walletClient: - | WalletClient - | undefined = undefined - - if ("request" in owner) { - if (!address) { - try { - ;[address] = await (owner as EthereumProvider).request({ - method: "eth_requestAccounts" - }) - } catch { - ;[address] = await (owner as EthereumProvider).request({ - method: "eth_accounts" - }) - } - } - if (!address) { - // For TS to be happy - throw new Error("address is required") - } - walletClient = createWalletClient({ - account: address, - transport: custom(owner as EthereumProvider) - }) + let walletClient: + | WalletClient + | undefined = undefined; + + if ("request" in owner) { + if (!address) { + try { + [address] = await (owner as EthereumProvider).request({ + method: "eth_requestAccounts", + }); + } catch { + [address] = await (owner as EthereumProvider).request({ + method: "eth_accounts", + }); + } } - - if (!walletClient) { - walletClient = owner as WalletClient< - Transport, - Chain | undefined, - Account - > + if (!address) { + // For TS to be happy + throw new Error("address is required"); } + walletClient = createWalletClient({ + account: address, + transport: custom(owner as EthereumProvider), + }); + } - return toAccount({ - address: walletClient.account.address, - async signMessage({ message }) { - return walletClient.signMessage({ message }) - }, - async signTypedData(typedData) { - return getAction( - walletClient, - signTypedData, - "signTypedData" - )(typedData as any) - }, - async signTransaction(_) { - throw new Error( - "Smart account signer doesn't need to sign transactions" - ) - } - }) -} \ No newline at end of file + if (!walletClient) { + walletClient = owner as WalletClient; + } + + return toAccount({ + address: walletClient.account.address, + async signMessage({ message }) { + return walletClient.signMessage({ message }); + }, + async signTypedData(typedData) { + return getAction( + walletClient, + signTypedData, + "signTypedData", + )(typedData as any); + }, + async signTransaction(_) { + throw new Error("Smart account signer doesn't need to sign transactions"); + }, + }); +} diff --git a/demo/clients.ts b/demo/clients.ts deleted file mode 100644 index 0519ecb..0000000 --- a/demo/clients.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/demo/constants.ts b/demo/constants.ts index e2c864b..db2cad0 100644 --- a/demo/constants.ts +++ b/demo/constants.ts @@ -3,65 +3,59 @@ import { baseSepolia, optimismSepolia } from "viem/chains"; // Add proper type definition export type supportedChain = "optimism" | "base"; -export const V7SimpleAccountFactoryAddress = "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985"; +export const V7SimpleAccountFactoryAddress = + "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985"; export const paymasterVerifier = privateKeyToAccount( - process.env.PAYMASTER_VERIFIER_PRIVATE_KEY as `0x${string}`, - ); + process.env.PAYMASTER_VERIFIER_PRIVATE_KEY as `0x${string}`, +); export const ownerAccount = privateKeyToAccount( - process.env.OWNER_PRIVATE_KEY as `0x${string}`, - ); - + process.env.OWNER_PRIVATE_KEY as `0x${string}`, +); export type token = Address; export type Vault = { address: Address; token: Address; -} +}; export type OpenfortContracts = { - paymaster: Address, - invoiceManager: Address, - vaultManager: Address - vaults: Record -} + paymaster: Address; + invoiceManager: Address; + vaultManager: Address; + vaults: Record; +}; export const openfortContracts: Record = { base: { - paymaster: "0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2", - invoiceManager: "0xE94b6B5346BF1E46daDDe0002148ec9d3b2778B4", - vaultManager: "0x38c3c2d2BDdDBC2916d9c85638932f8Fd2F4a7fe", + paymaster: "0x3cB057Fd3BE519cB50788b8b282732edBF533DC6", + invoiceManager: "0x666eB01fBba3F3D5f7e5d8e72c6Ea57B6AF09798", + vaultManager: "0xEA7aa047c78c5583a2896e18E127A5C2E59C0887", vaults: { - "0xfF3311cd15aB091B00421B23BcB60df02EFD8db7": "0x2e49f4faf533060c33Da040B28cC7297C7EE2770", - "0xa9a0179e045cF39C5F4d914583dc3648DfBDeeF1": "0xA22D8D45E83E405C801a18eF427f7c86BB10C241" - } + "0xfF3311cd15aB091B00421B23BcB60df02EFD8db7": + "0x21c14066F5D62Cbec3c42e2c718Ce82E72fCBF87", + "0xa9a0179e045cF39C5F4d914583dc3648DfBDeeF1": + "0x742d0fc742B89267411c5AC24a5fdA3CA264eeC2", + }, }, optimism: { - paymaster: "0x7926E12044F7f29150F5250B1A335a145298308d", - invoiceManager: "0xa3152B80759dfb0cB74009F4bB31b29d01e0e624", - vaultManager: "0x9E6A6E55D9DbE20DF20A90C426724442C8D95481", + paymaster: "0x48c2DE32E983cD077486c218a2f6A0119E1446cF", + invoiceManager: "0x2C4511a143e9C583B5Ae5c4206A4C9D3882F35Bf", + vaultManager: "0x1EEb54d847BC170a4F1e12312f9b5D74EeCF1018", vaults: { - "0x2522F4Fc9aF2E1954a3D13f7a5B2683A00a4543A": "0x8e2048c85Eae2a4443408C284221B33e61906463", - "0xd926e338e047aF920F59390fF98A3114CCDcab4a": "0xB35E1f4A65341e6D916902AB0238AC17c59b7430" - } - } -} - - -export const paymasters = { - base: "0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2", - optimism: "0x7926E12044F7f29150F5250B1A335a145298308d", -}; - -export const vaultManagers = { - base: "0x38c3c2d2BDdDBC2916d9c85638932f8Fd2F4a7fe", - optimism: "0x9E6A6E55D9DbE20DF20A90C426724442C8D95481", + "0x2522F4Fc9aF2E1954a3D13f7a5B2683A00a4543A": + "0xeFb7144787FFFCEF306bC99cEBF42AB08d5609c8", + "0xd926e338e047aF920F59390fF98A3114CCDcab4a": + "0x34BC35Ff16C1ab0e5123D5De58eC8d1353B09968", + }, + }, }; -export const invoiceManagers = { - base: "0xE94b6B5346BF1E46daDDe0002148ec9d3b2778B4", - optimism: "0xa3152B80759dfb0cB74009F4bB31b29d01e0e624", +// TODO: refactor this to only refer to openfortContracts +export const vaultA = { + base: "0x21c14066F5D62Cbec3c42e2c718Ce82E72fCBF87", + optimism: "0xeFb7144787FFFCEF306bC99cEBF42AB08d5609c8", }; export const tokenA = { @@ -79,18 +73,11 @@ export const demoNFTs = { optimism: "0x9999999999999999999999999999999999999999", }; -export const vaultA = { - base: "0x2e49f4faf533060c33Da040B28cC7297C7EE2770", - optimism: "0x8e2048c85Eae2a4443408C284221B33e61906463", -}; - - export const chainIDs = { base: baseSepolia.id, optimism: optimismSepolia.id, }; - export function isValidChain(chain: string): chain is supportedChain { return chain === "optimism" || chain === "base"; -} \ No newline at end of file +} diff --git a/demo/index.ts b/demo/index.ts index e288201..21d155c 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -1,8 +1,23 @@ import { Command } from "commander"; import { polymerProverClient } from "./polymerProverClient"; import { bundlerClients, publicClients, walletClients } from "./viemClients"; -import { isValidChain, demoNFTs, tokenA, paymasters, ownerAccount, chainIDs, V7SimpleAccountFactoryAddress, openfortContracts } from "./constants"; -import { Address, getAddress, Hex, parseAbi } from "viem"; +import { + isValidChain, + demoNFTs, + tokenA, + ownerAccount, + chainIDs, + V7SimpleAccountFactoryAddress, + openfortContracts, + vaultA, +} from "./constants"; +import { + Address, + encodeAbiParameters, + encodeFunctionData, + Hex, + parseAbi, +} from "viem"; import { toSimpleSmartAccount } from "./SimpleSmartAccount"; import { entryPoint07Address, @@ -10,6 +25,8 @@ import { } from "viem/account-abstraction"; import util from "util"; +import { invoiceManager } from "./Invoice"; +import { getBlockNumber, getBlockTimestamp } from "./utils"; const figlet = require("figlet"); const program = new Command(); @@ -61,8 +78,8 @@ program ) .requiredOption("-t, --token ", "token address") .requiredOption("-a, --amount ", "amount to lock") - .requiredOption("-s, --account-salt ", "account salt") - .action(async ({ token, amount, chain, accountSalt }) => { + .requiredOption("-r, --recipient ", "recipient address") + .action(async ({ token, amount, chain, recipient }) => { if (!isValidChain(chain)) { throw new Error(`Unsupported chain: ${chain}`); } @@ -73,56 +90,37 @@ program const vaultManager = openfortContracts[chain].vaultManager; const vault = openfortContracts[chain].vaults[token]; + const walletClient = walletClients[chain]; const publicClient = publicClients[chain]; - const account = await toSimpleSmartAccount({ - client: publicClient, - owner: ownerAccount, - salt: accountSalt, - entryPoint: { - address: entryPoint07Address, - version: "0.7", - }, - }); - const accountAddress = await account.getAddress(); - console.log(`Account Address: ${accountAddress}`); + // Get the current nonce for the account + // const nonce = await publicClient.getTransactionCount({ + // address: walletClient.account?.address as Address, + // }); + // const approveHash = await walletClient.writeContract({ + // address: token, + // abi: parseAbi(["function approve(address, uint256)"]), + // functionName: "approve", + // args: [vaultManager, amount], + // chain: walletClient.chain, + // account: walletClient.account || null, + // nonce, + // }); + //console.log(`Approve transaction sent: ${approveHash}`); - const bundlerClient = bundlerClients[chain]; - const unsignedUserOp = await bundlerClient.prepareUserOperation({ - account: account, - calls: [ - { - to: vaultManager, - abi: parseAbi(["function deposit(address, address, uint256, bool)"]), - functionName: "deposit", - args: [getAddress(token), getAddress(vault), amount, false], - } - ] - }) - - const userOpHash = await getUserOperationHash({ - chainId: chainIDs[chain], - entryPointAddress: entryPoint07Address, - entryPointVersion: "0.7", - userOperation: { - ...(unsignedUserOp as any), - sender: await account.getAddress(), - }, - }); - - const signature = await ownerAccount.signMessage({ - message: { raw: userOpHash }, - }); - - const hash = await bundlerClient.sendUserOperation({ - ...unsignedUserOp, - signature, - account: account, + const hash = await walletClient.writeContract({ + address: vaultManager, + abi: parseAbi([ + "function depositFor(address, address, address, uint256, bool)", + ]), + functionName: "depositFor", + args: [recipient, token, vault, amount, false], + chain: walletClient.chain, + account: walletClient.account || null, }); - console.log(`UserOp sent: ${hash}`); + console.log(`Deposit transaction sent: ${hash}`); }); - program .command("get-chain-abstraction-balance") .description("get chain abstraction balance") @@ -135,10 +133,7 @@ program if (!isValidChain(chain)) { throw new Error(`Unsupported chain: ${chain}`); } - - const walletClient = walletClients[chain]; - const publicClient = publicClients[chain]; - + // TODO: read balance in all vaults console.log("WIP"); }); @@ -171,6 +166,7 @@ program }); const accountAddress = await account.getAddress(); console.log(`Account Address: ${accountAddress}`); + const paymaster = openfortContracts[chain].paymaster; const unsignedUserOp = await bundlerClient.prepareUserOperation({ account: account, calls: [ @@ -178,27 +174,28 @@ program to: tokenA[chain] as Address, abi: parseAbi(["function transferFrom(address, address, uint256)"]), functionName: "transferFrom", - args: [paymasters[chain] as Address, accountAddress, nftPrice], + args: [paymaster, accountAddress, nftPrice], }, { to: tokenA[chain] as Address, abi: parseAbi(["function approve(address, uint256)"]), functionName: "approve", - args: [demoNFTs[chain] as Address, nftPrice] + args: [demoNFTs[chain] as Address, nftPrice], }, { to: demoNFTs[chain] as Address, abi: parseAbi(["function mint(string)"]), functionName: "mint", args: [ipfsHash], - } + }, ], verificationGasLimit: 1000000n, postVerificationGasLimit: 1000000n, preVerificationGas: 1000000n, callGasLimit: 1000000n, /** Concatenation of {@link UserOperation`verificationGasLimit`} (16 bytes) and {@link UserOperation`callGasLimit`} (16 bytes) */ - accountGasLimits: `0x${1000000n.toString(16)}${1000000n.toString(16)}` as Hex, + accountGasLimits: + `0x${1000000n.toString(16)}${1000000n.toString(16)}` as Hex, }); const userOpHash = await getUserOperationHash({ @@ -220,6 +217,23 @@ program account: account, }); console.log(`UserOp sent: ${hash}`); + // TODO: support multiple source chains + const srcChain = chain === "base" ? "optimism" : "base"; + const invoiceId = await invoiceManager.writeInvoice({ + account: accountAddress, + nonce: BigInt(unsignedUserOp.nonce), + paymaster: openfortContracts[chain].paymaster, + sponsorChainId: BigInt(chainIDs[chain]), + + repayTokenInfos: [ + { + vault: vaultA[srcChain] as Address, + amount: nftPrice, + chainId: BigInt(chainIDs[srcChain]), + }, + ], + }); + console.log(`Invoice ID: ${invoiceId}`); }); program @@ -230,7 +244,12 @@ program .requiredOption("-b, --src-block ", "source block number") .requiredOption("-t, --tx-index ", "transaction index") .action(async ({ srcChain, dstChain, srcBlock, txIndex }) => { - const jobId = await polymerProverClient.receiptRequestProof(srcChain, dstChain, srcBlock, txIndex); + const jobId = await polymerProverClient.receiptRequestProof( + srcChain, + dstChain, + srcBlock, + txIndex, + ); console.log(`jobId: ${jobId}`); }); @@ -240,7 +259,14 @@ program .requiredOption("-j, --job-id ", "job id") .action(async ({ jobId }) => { const proofResponse = await polymerProverClient.queryReceiptProof(jobId); - console.log(util.inspect(proofResponse, { showHidden: true, depth: null, colors: true })); + console.log( + util.inspect(proofResponse, { + showHidden: true, + depth: null, + colors: true, + maxStringLength: null, + }), + ); }); program @@ -275,9 +301,205 @@ program program .command("refund-paymaster") .description("call invoice manager to refund paymaster") + .addOption( + new Command() + .createOption("-c, --chain ", "choose chain") + .choices(["base", "optimism"]), + ) + .requiredOption("-p, --proof ", "proof") .requiredOption("-i, --invoice-id ", "invoice id") - .action(async ({ invoiceId }) => { - console.log("WIP"); + .action(async ({ chain, proof, invoiceId }) => { + if (!isValidChain(chain)) { + throw new Error(`Unsupported chain: ${chain}`); + } + const invoiceWithRepayTokens = await invoiceManager.readInvoice(invoiceId); + const walletClient = walletClients[chain]; + + // we can hard code logIndex since it is the index of the log within the tx + // TODO: validate receipt + parselog to implement refund batching + const logIndex = BigInt(8); + + const proofWithLogIndex = encodeAbiParameters( + [{ type: "uint256" }, { type: "bytes" }], + [logIndex, proof], + ); + + const hash = await walletClient.writeContract({ + address: openfortContracts[chain].invoiceManager, + abi: parseAbi([ + "struct RepayTokenInfo { address vault; uint256 amount; uint256 chainId; }", + "struct InvoiceWithRepayTokens { address account; uint256 nonce; address paymaster; uint256 sponsorChainId; RepayTokenInfo[] repayTokenInfos; }", + "function repay(bytes32 invoiceId, InvoiceWithRepayTokens invoice, bytes proof)", + ]), + functionName: "repay", + args: [invoiceId, invoiceWithRepayTokens, proofWithLogIndex], + chain: walletClient.chain, + account: walletClient.account || null, + }); + console.log(`Transaction sent: ${hash}`); + }); + +program + .command("get-invoices") + .description( + "read blockchain to get invoices-id from paymaster contract events", + ) + .addOption( + new Command() + .createOption("-c, --chain ", "choose chain") + .choices(["base", "optimism"]), + ) + .action(async ({ chain }) => { + if (!isValidChain(chain)) { + throw new Error(`Unsupported chain: ${chain}`); + } + const paymaster = openfortContracts[chain].paymaster; + const publicClient = publicClients[chain]; + + const currentBlock = await getBlockNumber(chain); + + const logs = await publicClient.getLogs({ + address: paymaster, + event: parseAbi(["event InvoiceCreated(bytes32 indexed invoiceId)"])[0], + fromBlock: currentBlock - 10000n, + toBlock: currentBlock, + }); + + console.log( + util.inspect(logs, { showHidden: true, depth: null, colors: true }), + ); + }); + +program + .command("register-paymaster") + .description("register paymaster") + .addOption( + new Command() + .createOption("-c, --chain ", "choose chain") + .choices(["base", "optimism"]), + ) + .requiredOption("-s, --account-salt ", "account salt") + .action(async ({ chain, accountSalt }) => { + if (!isValidChain(chain)) { + throw new Error(`Unsupported chain: ${chain}`); + } + const publicClient = publicClients[chain]; + const bundlerClient = bundlerClients[chain]; + + const paymaster = openfortContracts[chain].paymaster; + const account = await toSimpleSmartAccount({ + owner: ownerAccount, + client: publicClient, + salt: accountSalt, + factoryAddress: V7SimpleAccountFactoryAddress, + entryPoint: { + address: entryPoint07Address, + version: "0.7", + }, + }); + const accountAddress = await account.getAddress(); + console.log(`Account Address: ${accountAddress}`); + + const unsignedUserOp = await bundlerClient.prepareUserOperation({ + account: account, + calls: [ + { + to: openfortContracts[chain].invoiceManager, + abi: parseAbi([ + "function registerPaymaster(address, address, uint256)", + ]), + functionName: "registerPaymaster", + args: [ + paymaster, + paymaster, + (await getBlockTimestamp(chain)) + 1000000n, + ], + }, + ], + }); + + const userOpHash = await getUserOperationHash({ + chainId: chainIDs[chain], + entryPointAddress: entryPoint07Address, + entryPointVersion: "0.7", + userOperation: { + ...(unsignedUserOp as any), + sender: accountAddress, + }, + }); + + const signature = await ownerAccount.signMessage({ + message: { raw: userOpHash }, + }); + + const hash = await bundlerClient.sendUserOperation({ + ...unsignedUserOp, + signature, + account: account, + }); + console.log(`UserOp sent: ${hash}`); + }); + +program + .command("revoke-paymaster") + .description("revoke paymaster") + .addOption( + new Command() + .createOption("-c, --chain ", "choose chain") + .choices(["base", "optimism"]), + ) + .requiredOption("-s, --account-salt ", "account salt") + .action(async ({ chain, accountSalt }) => { + if (!isValidChain(chain)) { + throw new Error(`Unsupported chain: ${chain}`); + } + const publicClient = publicClients[chain]; + const bundlerClient = bundlerClients[chain]; + + const account = await toSimpleSmartAccount({ + owner: ownerAccount, + client: publicClient, + salt: accountSalt, + factoryAddress: V7SimpleAccountFactoryAddress, + entryPoint: { + address: entryPoint07Address, + version: "0.7", + }, + }); + + const accountAddress = await account.getAddress(); + console.log(`Account Address: ${accountAddress}`); + const unsignedUserOp = await bundlerClient.prepareUserOperation({ + account: account, + calls: [ + { + to: openfortContracts[chain].invoiceManager, + abi: parseAbi(["function revokePaymaster()"]), + functionName: "revokePaymaster", + args: [], + }, + ], + }); + + const userOpHash = await getUserOperationHash({ + chainId: chainIDs[chain], + entryPointAddress: entryPoint07Address, + entryPointVersion: "0.7", + userOperation: { + ...unsignedUserOp, + sender: accountAddress, + }, + }); + const signature = await ownerAccount.signMessage({ + message: { raw: userOpHash }, + }); + + const hash = await bundlerClient.sendUserOperation({ + ...unsignedUserOp, + signature, + account: account as any, + }); + console.log(`UserOp sent: ${hash}`); }); program.parse(); diff --git a/demo/paymaster.ts b/demo/paymaster.ts index c76f37b..33d4508 100644 --- a/demo/paymaster.ts +++ b/demo/paymaster.ts @@ -1,82 +1,127 @@ -import { PaymasterActions, GetPaymasterDataParameters, GetPaymasterDataReturnType, GetPaymasterStubDataParameters, GetPaymasterStubDataReturnType, UserOperation, PackedUserOperation } from "viem/account-abstraction"; -import { paymasters, paymasterVerifier, supportedChain } from "./constants"; -import { Hex, Address, concat, numberToHex, getAddress, toHex, pad } from "viem"; -import { computeHash, getBlockTimestamp, getRepayToken, getSponsorTokens } from "./utils"; - +import { + PaymasterActions, + GetPaymasterDataParameters, + GetPaymasterDataReturnType, + GetPaymasterStubDataParameters, + GetPaymasterStubDataReturnType, + UserOperation, + PackedUserOperation, +} from "viem/account-abstraction"; +import { + openfortContracts, + paymasterVerifier, + supportedChain, +} from "./constants"; +import { + Hex, + Address, + concat, + numberToHex, + getAddress, + toHex, + pad, +} from "viem"; +import { + computeHash, + getBlockTimestamp, + getRepayTokens, + getSponsorTokens, +} from "./utils"; export function getPaymasterActions(chain: supportedChain): PaymasterActions { - const pmAddress = paymasters[chain] as Address; - return { - getPaymasterData: async (parameters: GetPaymasterDataParameters): Promise => { - const validAfter = await getBlockTimestamp(chain); - const validUntil = validAfter + 1_000_000n; - - const postVerificationGas = parameters.paymasterPostOpGasLimit || BigInt(1e5); - const verificationGasLimit = parameters.verificationGasLimit || BigInt(1e5); - const callGasLimit = parameters.callGasLimit || BigInt(1e5); + const pmAddress = openfortContracts[chain].paymaster; + return { + getPaymasterData: async ( + parameters: GetPaymasterDataParameters, + ): Promise => { + const validAfter = await getBlockTimestamp(chain); + const validUntil = validAfter + 1_000_000n; - const userOp: PackedUserOperation = { - accountGasLimits: getAccountGasLimits(verificationGasLimit, callGasLimit), - gasFees: getGasLimits(parameters.maxPriorityFeePerGas!, parameters.maxFeePerGas!), - preVerificationGas: parameters.preVerificationGas || BigInt(0), - callData: parameters.callData, - nonce: parameters.nonce, - sender: parameters.sender, - signature: "0x", - initCode: getInitCode(parameters.factory, parameters.factoryData), - paymasterAndData: "0x", - }; + const postVerificationGas = + parameters.paymasterPostOpGasLimit || BigInt(1e5); + const verificationGasLimit = + parameters.verificationGasLimit || BigInt(1e5); + const callGasLimit = parameters.callGasLimit || BigInt(1e5); - const hash = await computeHash(userOp, chain, validUntil, validAfter, verificationGasLimit, postVerificationGas); - const signature = await paymasterVerifier.signMessage({ message: { raw: hash } }); + const userOp: PackedUserOperation = { + accountGasLimits: getAccountGasLimits( + verificationGasLimit, + callGasLimit, + ), + gasFees: getGasLimits( + parameters.maxPriorityFeePerGas!, + parameters.maxFeePerGas!, + ), + preVerificationGas: parameters.preVerificationGas || BigInt(0), + callData: parameters.callData, + nonce: parameters.nonce, + sender: parameters.sender, + signature: "0x", + initCode: getInitCode(parameters.factory, parameters.factoryData), + paymasterAndData: "0x", + }; - return { - paymaster: getAddress(pmAddress), - paymasterData: concat([ - numberToHex(validUntil, { size: 6 }), - numberToHex(validAfter, { size: 6 }), - getRepayToken(userOp.sender), - getSponsorTokens(userOp.sender, chain), - signature - ]) as Hex, - paymasterVerificationGasLimit: verificationGasLimit, - paymasterPostOpGasLimit: postVerificationGas, - }; - }, - getPaymasterStubData: async ( - parameters: GetPaymasterStubDataParameters, - ): Promise => { + const hash = await computeHash( + userOp, + chain, + validUntil, + validAfter, + verificationGasLimit, + postVerificationGas, + ); + const signature = await paymasterVerifier.signMessage({ + message: { raw: hash }, + }); - return { - paymasterAndData: (await getPaymasterActions(chain).getPaymasterData(parameters)).paymasterAndData as Hex, - }; - }, - }; + return { + paymaster: getAddress(pmAddress), + paymasterData: concat([ + numberToHex(validUntil, { size: 6 }), + numberToHex(validAfter, { size: 6 }), + getRepayTokens(userOp.sender), + getSponsorTokens(userOp.sender, chain), + signature, + ]) as Hex, + paymasterVerificationGasLimit: verificationGasLimit, + paymasterPostOpGasLimit: postVerificationGas, + }; + }, + getPaymasterStubData: async ( + parameters: GetPaymasterStubDataParameters, + ): Promise => { + return { + paymasterAndData: ( + await getPaymasterActions(chain).getPaymasterData(parameters) + ).paymasterAndData as Hex, + }; + }, + }; } -function getInitCode(factory: Address | undefined, factoryData: Hex | undefined) { - return factory - ? concat([ - factory, - factoryData || ("0x" as Hex) - ]) - : "0x" +function getInitCode( + factory: Address | undefined, + factoryData: Hex | undefined, +) { + return factory ? concat([factory, factoryData || ("0x" as Hex)]) : "0x"; } -function getAccountGasLimits(verificationGasLimit: bigint, callGasLimit: bigint) { - return concat([ - pad(toHex(verificationGasLimit), { - size: 16 - }), - pad(toHex(callGasLimit), { size: 16 }) - ]) +function getAccountGasLimits( + verificationGasLimit: bigint, + callGasLimit: bigint, +) { + return concat([ + pad(toHex(verificationGasLimit), { + size: 16, + }), + pad(toHex(callGasLimit), { size: 16 }), + ]); } function getGasLimits(maxPriorityFeePerGas: bigint, maxFeePerGas: bigint) { - return concat([ - pad(toHex(maxPriorityFeePerGas), { - size: 16 - }), - pad(toHex(maxFeePerGas), { size: 16 }) - ]) + return concat([ + pad(toHex(maxPriorityFeePerGas), { + size: 16, + }), + pad(toHex(maxFeePerGas), { size: 16 }), + ]); } diff --git a/demo/polymerProverClient.ts b/demo/polymerProverClient.ts index 2e42dc3..5c2ad9c 100644 --- a/demo/polymerProverClient.ts +++ b/demo/polymerProverClient.ts @@ -1,112 +1,111 @@ import { z } from "zod"; -import { Hex } from "viem"; -const jobIdSchema = z.number() - .int() - .min(0) - .max(Number.MAX_SAFE_INTEGER); +const jobIdSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER); const ReceiptRequestProofSchema = z.object({ - jsonrpc: z.literal("2.0"), - id: z.number(), - result: jobIdSchema + jsonrpc: z.literal("2.0"), + id: z.number(), + result: jobIdSchema, }); const ReceiptQueryProofSchema = z.object({ - jsonrpc: z.literal("2.0"), - id: z.number(), - result: z.object({ - jobID: jobIdSchema, - status: z.enum(["complete", "error", "pending"]), - blockNumber: z.number(), - receiptIndex: z.number(), - srcChainId: z.number(), - dstChainId: z.number(), - createdAt: z.number(), - updatedAt: z.number(), - proof: z.string().optional(), - }), + jsonrpc: z.literal("2.0"), + id: z.number(), + result: z.object({ + jobID: jobIdSchema, + status: z.enum(["complete", "error", "pending"]), + blockNumber: z.number().optional(), + receiptIndex: z.number(), + srcChainId: z.number(), + dstChainId: z.number(), + createdAt: z.number(), + updatedAt: z.number(), + proof: z + .string() + .transform((val) => { + if (!val) return undefined; + const buffer = Buffer.from(val, "base64"); + return `0x${buffer.toString("hex")}`; + }) + .optional(), + }), }); type ReceiptProofResponse = { - jobID: number; - status: "complete" | "error" | "pending"; - blockNumber: number; - receiptIndex: number; - srcChainId: number; - dstChainId: number; - createdAt: number; - updatedAt: number; - proof?: string; + jobID: number; + status: "complete" | "error" | "pending"; + blockNumber?: number; + receiptIndex: number; + srcChainId: number; + dstChainId: number; + createdAt: number; + updatedAt: number; + proof?: string; }; class PolymerProverClient { - constructor(private readonly endpoint: string, private readonly apiKey: string) { } + constructor( + private readonly endpoint: string, + private readonly apiKey: string, + ) {} - private async fetchWithValidation( - schema: z.ZodType, - body: object - ): Promise { - const response = await fetch(this.endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${this.apiKey}`, - }, - body: JSON.stringify(body), - }); + private async fetchWithValidation( + schema: z.ZodType, + body: object, + ): Promise { + const response = await fetch(this.endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + body: JSON.stringify(body), + }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const rawData = await response.json() as { - jsonrpc: string; - id: number; - result: unknown; - }; - return schema.parse(rawData); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } - async receiptRequestProof( - srcChainId: bigint, - dstChainId: bigint, - srcBlockNumber: bigint, - txIndex: bigint - ): Promise { + const rawData = (await response.json()) as { + jsonrpc: string; + id: number; + result: unknown; + }; + return schema.parse(rawData); + } - const response = await this.fetchWithValidation( - ReceiptRequestProofSchema, - { - jsonrpc: "2.0", - id: 1, - method: "receipt_requestProof", - params: [ - Number(srcChainId), - Number(dstChainId), - Number(srcBlockNumber), - Number(txIndex) - ] - } - ); - return response.result; - } + async receiptRequestProof( + srcChainId: bigint, + dstChainId: bigint, + srcBlockNumber: bigint, + txIndex: bigint, + ): Promise { + const response = await this.fetchWithValidation(ReceiptRequestProofSchema, { + jsonrpc: "2.0", + id: 1, + method: "receipt_requestProof", + params: [ + Number(srcChainId), + Number(dstChainId), + Number(srcBlockNumber), + Number(txIndex), + ], + }); + return response.result; + } - async queryReceiptProof(jobId: bigint): Promise { - const response = await this.fetchWithValidation( - ReceiptQueryProofSchema, - { - jsonrpc: "2.0", - id: 1, - method: "receipt_queryProof", - params: [Number(jobId)] - } - ); - return response.result; - } + async queryReceiptProof(jobId: bigint): Promise { + const response = await this.fetchWithValidation(ReceiptQueryProofSchema, { + jsonrpc: "2.0", + id: 1, + method: "receipt_queryProof", + params: [Number(jobId)], + }); + return response.result; + } } export const polymerProverClient = new PolymerProverClient( - process.env.POLYMER_PROVER_API_ENDPOINT || "secret public endpoint", - process.env.POLYMER_PROVER_API_KEY || "no token no proof brother" + process.env.POLYMER_PROVER_API_ENDPOINT || "secret public endpoint", + process.env.POLYMER_PROVER_API_KEY || "no token no proof brother", ); - diff --git a/demo/test/computeHash.test.ts b/demo/test/computeHash.test.ts index 273e3ab..d2e8a4e 100644 --- a/demo/test/computeHash.test.ts +++ b/demo/test/computeHash.test.ts @@ -3,60 +3,46 @@ import { Address, Hex } from "viem"; import { computeHash } from "../utils"; describe("computeHash", () => { - const PAYMASTER_VALIDATION_GAS_OFFSET = 20; - const PAYMASTER_DATA_OFFSET = 52; + const PAYMASTER_VALIDATION_GAS_OFFSET = 20; + const PAYMASTER_DATA_OFFSET = 52; test("should compute correct hash for user operation 1", async () => { // Setup test data const mockUserOp = { sender: "0xFb619E988fD324734be51b0475A67b6921D0301f" as Address, nonce: 31996375400717808072039644266496n, - initCode: "0x91E60e0613810449d098b0b5Ec8b51A0FE8c89855fbfb9cf0000000000000000000000009590ed0c18190a310f4e93caccc4cc17270bed400000000000000000000000000000000000000000000000000000000000000000" as Hex, - callData: "0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000006423b872dd000000000000000000000000f6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000fb619e988fd324734be51b0475a67b6921d0301f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d85d3d270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965346636336c7935676a6471346c676d74336e33346f67637a716d757277663275326e756b693366747073613679636a79677865000000000000000000000000000000000000000000000000000000000000000000" as Hex, - accountGasLimits: "0x000000000000000000000000000f4240000000000000000000000000000f4240" as Hex, + initCode: + "0x91E60e0613810449d098b0b5Ec8b51A0FE8c89855fbfb9cf0000000000000000000000009590ed0c18190a310f4e93caccc4cc17270bed400000000000000000000000000000000000000000000000000000000000000000" as Hex, + callData: + "0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000006423b872dd000000000000000000000000f6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000fb619e988fd324734be51b0475a67b6921d0301f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d85d3d270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965346636336c7935676a6471346c676d74336e33346f67637a716d757277663275326e756b693366747073613679636a79677865000000000000000000000000000000000000000000000000000000000000000000" as Hex, + accountGasLimits: + "0x000000000000000000000000000f4240000000000000000000000000000f4240" as Hex, preVerificationGas: 1000000n, - gasFees: "0x0000000000000000000000003b9aca00000000000000000000000000b2d05e00" as Hex, - paymasterAndData: "0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2000000000000000000000000000f4240000000000000000000000000000186a000000136f6d400000127b494018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01fF3311cd15aB091B00421B23BcB60df02EFD8db7Fb619E988fD324734be51b0475A67b6921D0301f00000000000000000000000000000000000000000000000000000000000001f4469cfb36ee62bf829b1a21065b34fad76f69fc458ef15aef644db820e87880e91e9fc86ebe6112b1ad52f5cf050f63cabf35c42328886d54dba63d7912cbb7461b" as Hex, - signature: "0x47140b77612d9ea6bb0d7175ae8c91ea693e4e231fb48ae499c45d0a9309a42735de448da6ce53a4f195607a9afb7a4a21d5ae78c20cff808702d4e892a082ee1b" as Hex, + gasFees: + "0x0000000000000000000000003b9aca00000000000000000000000000b2d05e00" as Hex, + paymasterAndData: + "0x3cB057Fd3BE519cB50788b8b282732edBF533DC6000000000000000000000000000f4240000000000000000000000000000186a000000136f6d400000127b494018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01fF3311cd15aB091B00421B23BcB60df02EFD8db7Fb619E988fD324734be51b0475A67b6921D0301f00000000000000000000000000000000000000000000000000000000000001f4469cfb36ee62bf829b1a21065b34fad76f69fc458ef15aef644db820e87880e91e9fc86ebe6112b1ad52f5cf050f63cabf35c42328886d54dba63d7912cbb7461b" as Hex, + signature: + "0x47140b77612d9ea6bb0d7175ae8c91ea693e4e231fb48ae499c45d0a9309a42735de448da6ce53a4f195607a9afb7a4a21d5ae78c20cff808702d4e892a082ee1b" as Hex, }; const chain = "base"; const validUntil = 20379348n; const validAfter = 19379348n; - const gasInfo = `${mockUserOp.paymasterAndData.slice(2 + 2 * PAYMASTER_VALIDATION_GAS_OFFSET, 2 + 2 * PAYMASTER_DATA_OFFSET)}` + const gasInfo = `${mockUserOp.paymasterAndData.slice(2 + 2 * PAYMASTER_VALIDATION_GAS_OFFSET, 2 + 2 * PAYMASTER_DATA_OFFSET)}`; - const verificationGasLimit = BigInt(`0x${gasInfo.slice(0, 32)}`) - const postVerificationGasLimit = BigInt(`0x${gasInfo.slice(32, 64)}`) - const hash = computeHash(mockUserOp, chain, validUntil, validAfter, verificationGasLimit, postVerificationGasLimit); - // Verify the hash format + const verificationGasLimit = BigInt(`0x${gasInfo.slice(0, 32)}`); + const postVerificationGasLimit = BigInt(`0x${gasInfo.slice(32, 64)}`); + const hash = computeHash( + mockUserOp, + chain, + validUntil, + validAfter, + verificationGasLimit, + postVerificationGasLimit, + ); expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/); - // Verify specific components that went into the hash - expect(hash).toBe("0x88800f0c29d6ba24fa635822ff4098e15b23048a57eec551b4a3af752aab97cd"); - }); - - test("should compute correct hash for user operation 2", async () => { - const mockUserOp = { - sender: "0xFb619E988fD324734be51b0475A67b6921D0301f" as Address, - nonce: 31996793154649218918368382287872n, - initCode: "0x91e60e0613810449d098b0b5ec8b51a0fe8c89855fbfb9cf0000000000000000000000009590ed0c18190a310f4e93caccc4cc17270bed400000000000000000000000000000000000000000000000000000000000000000" as Hex, - callData: "0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000006423b872dd000000000000000000000000f6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000fb619e988fd324734be51b0475a67b6921d0301f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d85d3d270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965346636336c7935676a6471346c676d74336e33346f67637a716d757277663275326e756b693366747073613679636a79677865000000000000000000000000000000000000000000000000000000000000000000" as Hex, - accountGasLimits: "0x000000000000000000000000000f4240000000000000000000000000000f4240" as Hex, - preVerificationGas: 1000000n, - gasFees: "0x0000000000000000000000003b9aca00000000000000000000000000b2d05e00" as Hex, - paymasterAndData: "0xf6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000000f4240000000000000000000000000000186a000000137231000000127e0d0018e2048c85eae2a4443408c284221b33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01ff3311cd15ab091b00421b23bcb60df02efd8db7fb619e988fd324734be51b0475a67b6921d0301f00000000000000000000000000000000000000000000000000000000000001f494df8f020405e0960c7f8f65eb45ef528830974a9d750bb610a29e45d821833b5ce82e106bdd6a52da2ced548b7f8a8700dd58291936da0f80d884890064de451c" as Hex, - signature: "f680f9b3a12949978e55911a09d99850096bbe5ab49569fe9d62c8865f3bf54d5a5ff5a112cfa766bb8c380308b37152ab98e40acb5316a7da11b0876a0f82741c" as Hex, - }; - - const chain = "base"; - const validUntil = 20390672n; - const validAfter = 19390672n; - const gasInfo = `${mockUserOp.paymasterAndData.slice(2 + 2 * PAYMASTER_VALIDATION_GAS_OFFSET, 2 + 2 * PAYMASTER_DATA_OFFSET)}` - - const verificationGasLimit = BigInt(`0x${gasInfo.slice(0, 32)}`) - const postVerificationGasLimit = BigInt(`0x${gasInfo.slice(32, 64)}`) - const hash = computeHash(mockUserOp, chain, validUntil, validAfter, verificationGasLimit, postVerificationGasLimit); - // Verify the hash format - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/); - // Verify specific components that went into the hash - expect(hash).toBe("0x62ccf508e3a63a4902fbde772d218855e6d2a614ec6d9dab8b13b9bf7d477829"); + expect(hash).toBe( + "0xa96409479e23813ab559babaa29a18b31797f99fb6aefe66782fb8304dcb52ab", + ); }); }); diff --git a/demo/test/getInvoiceId.test.ts b/demo/test/getInvoiceId.test.ts new file mode 100644 index 0000000..2b86454 --- /dev/null +++ b/demo/test/getInvoiceId.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "vitest"; +import { getAddress } from "viem"; +import { invoiceManager } from "../Invoice"; + +describe("getInvoiceId", () => { + /* + * cast call 0xec721B31c1F003E3D45671D3e6cB83F73AA8f0D6 "getInvoiceId(address,address,uint256,uint256,bytes)" "0x499E26E7A97cB8F89bE5668770Fb022fdDbCa40d" "0x76342B873f9583f9a1D2cF7b12F0b3E0536E1a71" 32006121834480964251358041473024 84532 "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008e2048c85eae2a4443408c284221b33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc" --rpc-url https://sepolia.base.org + * ===> 0xc7fea7a3bdb81a75efca8bda6e2082245bc07b38bf7827aa8b8e0e7036987909 + */ + + test("should match onchain invoice ID computation", async () => { + const invoice = { + account: getAddress("0x499E26E7A97cB8F89bE5668770Fb022fdDbCa40d"), + paymaster: getAddress("0x3B03425198341CD6469Ba3e05e215a458CF021E6"), + nonce: 32006186547098020862214455427072n, + sponsorChainId: 84532n, + repayTokenInfos: [ + { + vault: getAddress("0x8e2048c85Eae2a4443408C284221B33e61906463"), + amount: 500n, + chainId: 11155420n, + }, + ], + }; + + const expectedInvoiceId = + "0x8d1d56f301140f5e317ae961e5eb8065d04f7ef60725e4305b88f757245d2212"; + const computedInvoiceId = await invoiceManager.getInvoiceId(invoice); + expect(computedInvoiceId).toBe(expectedInvoiceId); + }); +}); diff --git a/demo/utils.ts b/demo/utils.ts index d613cae..c284711 100644 --- a/demo/utils.ts +++ b/demo/utils.ts @@ -1,83 +1,102 @@ - -import { Address, concat, encodeAbiParameters, getAddress, Hex, keccak256, numberToHex, pad, toHex } from "viem"; +import { + Address, + concat, + encodeAbiParameters, + getAddress, + Hex, + keccak256, + numberToHex, + pad, + toHex, +} from "viem"; import { publicClients } from "./viemClients"; -import { chainIDs, paymasters, supportedChain, tokenA, vaultA } from "./constants"; +import { + chainIDs, + openfortContracts, + supportedChain, + tokenA, + vaultA, +} from "./constants"; import { PackedUserOperation } from "viem/account-abstraction"; export async function getBlockTimestamp(chain: supportedChain) { - const block = await publicClients[chain].getBlock(); - return block.timestamp; + const block = await publicClients[chain].getBlock(); + return block.timestamp; +} + +export async function getBlockNumber(chain: supportedChain) { + const block = await publicClients[chain].getBlockNumber(); + return block; } export function computeHash( - userOp: PackedUserOperation, - chain: supportedChain, - validUntil: bigint, - validAfter: bigint, - paymasterVerificationGasLimit: bigint, - paymasterPostOpGasLimit: bigint, + userOp: PackedUserOperation, + chain: supportedChain, + validUntil: bigint, + validAfter: bigint, + paymasterVerificationGasLimit: bigint, + paymasterPostOpGasLimit: bigint, ) { - - const repayTokenData = getRepayToken(userOp.sender); - const sponsorTokenData = getSponsorTokens(userOp.sender, chain); - const encodedTokenData = encodeAbiParameters( - [{ type: "bytes" }, { type: "bytes" }], - [repayTokenData, sponsorTokenData] - ); - const gasInfo = concat([ - pad(toHex(paymasterVerificationGasLimit || 0n), { size: 16 }), - pad(toHex(paymasterPostOpGasLimit || 0n), { size: 16}), - ]) - const encodedData = encodeAbiParameters( - [ - { type: "address", name: "sender" }, - { type: "uint256", name: "nonce" }, - { type: "bytes32", name: "initCodeHash" }, - { type: "bytes32", name: "callDataHash" }, - // { type: "bytes32", name: "accountGasLimits" }, - { type: "bytes32", name: "tokensHash" }, - { type: "bytes32", name: "gasInfo" }, - { type: "uint256", name: "preVerificationGas" }, - { type: "bytes32", name: "gasFees" }, - { type: "uint256", name: "chainId" }, - { type: "address", name: "paymaster" }, - { type: "uint48", name: "validUntil" }, - { type: "uint48", name: "validAfter" } - ], - [ - getAddress(userOp.sender), - userOp.nonce, - pad(keccak256(userOp.initCode), { size: 32 }), - keccak256(userOp.callData), - // pad(userOp.accountGasLimits, { size: 32 }), - keccak256(encodedTokenData), - pad(gasInfo as Hex, { size: 32 }), - userOp.preVerificationGas, - pad(userOp.gasFees, { size: 32 }), - BigInt(chainIDs[chain]), - getAddress(paymasters[chain]), - Number(validUntil), - Number(validAfter) - ] - ) - return keccak256(encodedData); + const repayTokenData = getRepayTokens(userOp.sender); + const sponsorTokenData = getSponsorTokens(userOp.sender, chain); + const encodedTokenData = encodeAbiParameters( + [{ type: "bytes" }, { type: "bytes" }], + [repayTokenData, sponsorTokenData], + ); + const gasInfo = concat([ + pad(toHex(paymasterVerificationGasLimit || 0n), { size: 16 }), + pad(toHex(paymasterPostOpGasLimit || 0n), { size: 16 }), + ]); + const encodedData = encodeAbiParameters( + [ + { type: "address", name: "sender" }, + { type: "uint256", name: "nonce" }, + { type: "bytes32", name: "initCodeHash" }, + { type: "bytes32", name: "callDataHash" }, + // { type: "bytes32", name: "accountGasLimits" }, + { type: "bytes32", name: "tokensHash" }, + { type: "bytes32", name: "gasInfo" }, + { type: "uint256", name: "preVerificationGas" }, + { type: "bytes32", name: "gasFees" }, + { type: "uint256", name: "chainId" }, + { type: "address", name: "paymaster" }, + { type: "uint48", name: "validUntil" }, + { type: "uint48", name: "validAfter" }, + ], + [ + getAddress(userOp.sender), + userOp.nonce, + pad(keccak256(userOp.initCode), { size: 32 }), + keccak256(userOp.callData), + // pad(userOp.accountGasLimits, { size: 32 }), + keccak256(encodedTokenData), + pad(gasInfo as Hex, { size: 32 }), + userOp.preVerificationGas, + pad(userOp.gasFees, { size: 32 }), + BigInt(chainIDs[chain]), + getAddress(openfortContracts[chain].paymaster), + Number(validUntil), + Number(validAfter), + ], + ); + return keccak256(encodedData); } -export function getRepayToken(sender: Address) { - // TODO: check sender locked-funds - return concat([ - "0x01", // length of the array (only one repay token) - vaultA["optimism"] as Address, - pad(numberToHex(500), { size: 32 }), - pad(numberToHex(chainIDs["optimism"]), { size: 32 }) - ]) +export function getRepayTokens(sender: Address) { + // TODO: check sender locked-funds + return concat([ + "0x01", // length of the array (only one repay token) + vaultA["optimism"] as Address, + pad(numberToHex(500), { size: 32 }), + pad(numberToHex(chainIDs["optimism"]), { size: 32 }), + ]); } export function getSponsorTokens(spender: Address, chain: supportedChain) { - return concat([ - "0x01", // length of the array (only one sponsor token) - tokenA[chain] as Address, - spender, - pad(numberToHex(500), { size: 32 }) // 500 (the NFT Price) - ]) -} \ No newline at end of file + return concat([ + "0x01", // length of the array (only one sponsor token) + tokenA[chain] as Address, + spender, + pad(numberToHex(500), { size: 32 }), // 500 (the NFT Price) + ]); +} diff --git a/demo/viemClients.ts b/demo/viemClients.ts index 29d9d28..fc342a1 100644 --- a/demo/viemClients.ts +++ b/demo/viemClients.ts @@ -32,19 +32,20 @@ export const optimismWalletClient = createWalletClient({ transport: http(), }); - export const baseSepoliaBundlerClient = createBundlerClient({ client: baseSepoliaPublicClient, paymaster: getPaymasterActions("base"), transport: http( - `https://api.pimlico.io/v2/base-sepolia/rpc?apikey=${process.env.PIMLICO_API_KEY}` + `https://api.pimlico.io/v2/base-sepolia/rpc?apikey=${process.env.PIMLICO_API_KEY}`, ), }); export const optimismBundlerClient = createBundlerClient({ client: optimismPublicClient, paymaster: getPaymasterActions("optimism"), - transport: http(`http://localhost:8080/bundler/${optimismSepolia.id}`), + transport: http( + `https://api.pimlico.io/v2/optimism-sepolia/rpc?apikey=${process.env.PIMLICO_API_KEY}`, + ), }); export const walletClients: Record = { @@ -60,4 +61,4 @@ export const publicClients: Record = { export const bundlerClients: Record = { optimism: optimismBundlerClient as BundlerClient, base: baseSepoliaBundlerClient as BundlerClient, -}; \ No newline at end of file +}; diff --git a/lib/account-abstraction b/lib/account-abstraction index 7a656d2..c6f34e4 160000 --- a/lib/account-abstraction +++ b/lib/account-abstraction @@ -1 +1 @@ -Subproject commit 7a656d212a8918d0d027703a8855e380faab5130 +Subproject commit c6f34e43bbfd1a224c066d7562e64982663fc860 diff --git a/lib/forge-std b/lib/forge-std index 1eea5ba..b93cf4b 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit b93cf4bc34ff214c099dc970b153f85ade8c9f66 diff --git a/lib/solady b/lib/solady index 1dd8967..717c0a8 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit 1dd8967b93b379ca6cf384640e0715e55ef08e3d +Subproject commit 717c0a860a83dc9a1520d32384577aec51cecd84 diff --git a/lib/vibc-core-smart-contracts b/lib/vibc-core-smart-contracts index 33cae1d..b292b1d 160000 --- a/lib/vibc-core-smart-contracts +++ b/lib/vibc-core-smart-contracts @@ -1 +1 @@ -Subproject commit 33cae1d3cb82a664772ab8e771717a83901cec71 +Subproject commit b292b1dc6c4a7f716e7fa80f68df623ffc5a562b diff --git a/remappings.txt b/remappings.txt index 1ea4c47..ffbcb9d 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,4 @@ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @vibc-core-smart-contracts/=lib/vibc-core-smart-contracts/ -@solady/=lib/solady/src/ +@solady/=lib/solady/src/ \ No newline at end of file diff --git a/script/deployChainAbstractionSetup.s.sol b/script/deployChainAbstractionSetup.s.sol index 004a422..96c5d01 100644 --- a/script/deployChainAbstractionSetup.s.sol +++ b/script/deployChainAbstractionSetup.s.sol @@ -37,8 +37,11 @@ contract DeployChainAbstractionSetup is Script, CheckOrDeployEntryPoint { vm.startBroadcast(deployerPrivKey); + InvoiceManager invoiceManagerImpl = new InvoiceManager(); + console.log("InvoiceManagerImpl Address", address(invoiceManagerImpl)); + InvoiceManager invoiceManager = - InvoiceManager(payable(new UpgradeableOpenfortProxy{salt: versionSalt}(address(new InvoiceManager()), ""))); + InvoiceManager(payable(new UpgradeableOpenfortProxy{salt: versionSalt}(address(invoiceManagerImpl), ""))); console.log("InvoiceManager Address", address(invoiceManager)); VaultManager vaultManager = VaultManager( payable( diff --git a/src/core/InvoiceManager.sol b/src/core/InvoiceManager.sol index bd56eb4..37cfb63 100644 --- a/src/core/InvoiceManager.sol +++ b/src/core/InvoiceManager.sol @@ -60,8 +60,6 @@ contract InvoiceManager is UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardU delete cabPaymasters[msg.sender]; } - // bytes calldata receiptIndex, bytes calldata receiptRLPEncodedBytes - /// @inheritdoc IInvoiceManager function createInvoice(uint256 nonce, address paymaster, bytes32 invoiceId) external override { // check if the invoice already exists @@ -124,9 +122,9 @@ contract InvoiceManager is UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardU address paymaster, uint256 nonce, uint256 sponsorChainId, - RepayTokenInfo[] calldata repayTokenInfos + bytes calldata repayTokenInfos ) public view returns (bytes32) { - return keccak256(abi.encodePacked(account, paymaster, nonce, sponsorChainId, abi.encode(repayTokenInfos))); + return keccak256(abi.encodePacked(account, paymaster, nonce, sponsorChainId, repayTokenInfos)); } function _getRepayToken(InvoiceWithRepayTokens memory invoice) diff --git a/src/interfaces/IInvoiceManager.sol b/src/interfaces/IInvoiceManager.sol index d641857..6db834b 100644 --- a/src/interfaces/IInvoiceManager.sol +++ b/src/interfaces/IInvoiceManager.sol @@ -134,7 +134,7 @@ interface IInvoiceManager { * @param paymaster The address of the paymaster. * @param nonce The nonce of the invoice. * @param sponsorChainId The chain ID of the sponsor. - * @param repayTokenInfos The tokens to repay. + * @param repayTokenData The encoded RepayTokenInfo[] to repay. * @return invoiceId The ID of the invoice. */ function getInvoiceId( @@ -142,6 +142,6 @@ interface IInvoiceManager { address paymaster, uint256 nonce, uint256 sponsorChainId, - RepayTokenInfo[] calldata repayTokenInfos + bytes calldata repayTokenData ) external view returns (bytes32); } diff --git a/src/interfaces/IPaymasterVerifier.sol b/src/interfaces/IPaymasterVerifier.sol index f338782..73332df 100644 --- a/src/interfaces/IPaymasterVerifier.sol +++ b/src/interfaces/IPaymasterVerifier.sol @@ -9,7 +9,10 @@ import {IInvoiceManager} from "./IInvoiceManager.sol"; */ interface IPaymasterVerifier { /// @notice Emitted when an invoice is created. - event InvoiceCreated(bytes32 invoiceId); + event InvoiceCreated(bytes32 indexed invoiceId); + + /// @notice Emitted when an invoice is verified. + event InvoiceVerified(bytes32 indexed invoiceId); /// @notice The struct of the sponsor token. struct SponsorToken { diff --git a/src/mocks/MockCrossL2Prover.sol b/src/mocks/MockCrossL2Prover.sol new file mode 100644 index 0000000..4d45dc8 --- /dev/null +++ b/src/mocks/MockCrossL2Prover.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/ICrossL2Prover.sol"; +import {LightClientType} from "@vibc-core-smart-contracts/contracts/interfaces/ILightClient.sol"; + +contract MockCrossL2Prover is ICrossL2Prover { + function validateEvent(uint256 logIndex, bytes calldata proof) + external + pure + returns (string memory chainId, address emittingContract, bytes[] memory topics, bytes memory unindexedData) + { + (chainId, emittingContract, topics, unindexedData) = abi.decode( + hex"00000000000000000000000000000000000000000000000000000000000000800000000000000000000000003cb057fd3be519cb50788b8b282732edbf533dc600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000538343533320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020ab6994bba319b437692f1dbbe4d689382f25c4cfa9a291959e0af1ca5cd9e13f0000000000000000000000000000000000000000000000000000000000000020886f7a98cfb9c6f2b6a6b4be00a89b75c0a846bf1c9b265b17ba4f9452acbc640000000000000000000000000000000000000000000000000000000000000000", + (string, address, bytes[], bytes) + ); + } + + function validateReceipt(bytes calldata proof) + external + view + returns (string memory srcChainId, bytes calldata receiptRLP) + { + revert("not implemented"); + } + + function getState(uint256 height) external view returns (uint256) { + revert("not implemented"); + } + + function LIGHT_CLIENT_TYPE() external view returns (LightClientType) { + revert("not implemented"); + } + + function updateClient(bytes calldata proof, uint256 height, uint256 appHash) external { + revert("not implemented"); + } +} diff --git a/src/paymasters/CABPaymaster.sol b/src/paymasters/CABPaymaster.sol index d417db7..812987d 100644 --- a/src/paymasters/CABPaymaster.sol +++ b/src/paymasters/CABPaymaster.sol @@ -12,8 +12,7 @@ import {IInvoiceManager} from "../interfaces/IInvoiceManager.sol"; import {IVault} from "../interfaces/IVault.sol"; import {IPaymasterVerifier} from "../interfaces/IPaymasterVerifier.sol"; import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/ICrossL2Prover.sol"; - -import {console} from "forge-std/console.sol"; +import {LibBytes} from "@solady/utils/LibBytes.sol"; /** * @title CABPaymaster @@ -49,28 +48,26 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { IInvoiceManager.InvoiceWithRepayTokens calldata _invoice, bytes calldata _proof ) external virtual override returns (bool) { - // Check if the invoiceId corresponds to the invoice bytes32 invoiceId = invoiceManager.getInvoiceId( - _invoice.account, _invoice.paymaster, _invoice.nonce, _invoice.sponsorChainId, _invoice.repayTokenInfos + _invoice.account, + _invoice.paymaster, + _invoice.nonce, + _invoice.sponsorChainId, + _encodeRepayToken(_invoice.repayTokenInfos) ); if (invoiceId != _invoiceId) return false; - // _proof is an opaque bytes object to potentialy support different proof systems - // This paymasterVerifier supports Polymer proof system - ( - bytes memory receiptIndex, - bytes memory receiptRLPEncodedBytes, - uint256 logIndex, - bytes memory logBytes, - bytes memory proof - ) = abi.decode(_proof, (bytes, bytes, uint256, bytes, bytes)); - - if (!crossL2Prover.validateEvent(receiptIndex, receiptRLPEncodedBytes, logIndex, logBytes, proof)) { + (uint256 logIndex, bytes memory proof) = abi.decode(_proof, (uint256, bytes)); + (,, bytes[] memory topics,) = crossL2Prover.validateEvent(logIndex, proof); + bytes[] memory expectedTopics = new bytes[](2); + expectedTopics[0] = abi.encode(InvoiceCreated.selector); + expectedTopics[1] = abi.encode(invoiceId); + + if (!LibBytes.eq(abi.encode(topics), abi.encode(expectedTopics))) { return false; } - - // TODO: check if event matches InvoiceCreated(invoiceId) + emit InvoiceVerified(invoiceId); return true; } @@ -78,18 +75,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { IERC20(token).safeTransfer(owner(), amount); } - function getInvoiceHash(IInvoiceManager.InvoiceWithRepayTokens calldata invoice) public pure returns (bytes32) { - return keccak256( - abi.encode( - invoice.account, - invoice.nonce, - invoice.paymaster, - invoice.sponsorChainId, - keccak256(abi.encode(invoice.repayTokenInfos)) // vault, amount, chain - ) - ); - } - function getHash(PackedUserOperation calldata userOp, uint48 validUntil, uint48 validAfter) public view @@ -119,6 +104,18 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { ); } + function getInvoiceHash(IInvoiceManager.InvoiceWithRepayTokens calldata invoice) public pure returns (bytes32) { + return keccak256( + abi.encode( + invoice.account, + invoice.nonce, + invoice.paymaster, + invoice.sponsorChainId, + keccak256(abi.encode(invoice.repayTokenInfos)) // vault, amount, chain + ) + ); + } + function _validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund) internal override @@ -132,7 +129,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { parsePaymasterSignature(signature); (uint256 sponsorTokenLength, SponsorToken[] memory sponsorTokens) = parseSponsorTokenData(sponsorTokenData); - (, IInvoiceManager.RepayTokenInfo[] memory repayTokens) = parseRepayTokenData(repayTokenData); // revoke the approval at the end of userOp for (uint256 i = 0; i < sponsorTokenLength; i++) { @@ -141,7 +137,7 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { } bytes32 invoiceId = - invoiceManager.getInvoiceId(userOp.getSender(), address(this), userOp.nonce, block.chainid, repayTokens); + invoiceManager.getInvoiceId(userOp.getSender(), address(this), userOp.nonce, block.chainid, repayTokenData); bytes32 hash = MessageHashUtils.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter)); @@ -244,29 +240,19 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster { } } - function parseRepayTokenData(bytes calldata repayTokenData) - public + function _encodeRepayToken(IInvoiceManager.RepayTokenInfo[] memory repayTokens) + internal pure - returns (uint8 repayTokenLength, IInvoiceManager.RepayTokenInfo[] memory repayTokens) + returns (bytes memory encodedRepayToken) { - repayTokenLength = uint8(bytes1(repayTokenData[0])); - - // 1 byte: length - // length * 84 bytes: (20 bytes: vault address + 32 bytes chainID + 32 bytes amount) - require(repayTokenData.length == 1 + repayTokenLength * 84, "CABPaymaster: invalid repayTokenData length"); - - repayTokens = new IInvoiceManager.RepayTokenInfo[](repayTokenLength); - for (uint256 i = 0; i < uint256(repayTokenLength);) { - uint256 offset = 1 + i * 84; - address vault = address(bytes20(repayTokenData[offset:offset + 20])); - uint256 chainId = uint256(bytes32(repayTokenData[offset + 20:offset + 52])); - uint256 amount = uint256(bytes32(repayTokenData[offset + 52:offset + 84])); - - repayTokens[i] = IInvoiceManager.RepayTokenInfo(IVault(vault), amount, chainId); - - unchecked { - i++; - } + for (uint8 i = 0; i < repayTokens.length; i++) { + encodedRepayToken = bytes.concat( + encodedRepayToken, + bytes20(address(repayTokens[i].vault)), + bytes32(repayTokens[i].amount), + bytes32(repayTokens[i].chainId) + ); } + return abi.encodePacked(uint8(repayTokens.length), encodedRepayToken); } } diff --git a/test/CABPaymater.t.sol b/test/CABPaymater.t.sol index 1866e31..735cf73 100644 --- a/test/CABPaymater.t.sol +++ b/test/CABPaymater.t.sol @@ -9,6 +9,7 @@ import {CABPaymaster} from "../src/paymasters/CABPaymaster.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {InvoiceManager} from "../src/core/InvoiceManager.sol"; import {VaultManager} from "../src/vaults/VaultManager.sol"; +import {IVault} from "../src/interfaces/IVault.sol"; import {IVaultManager} from "../src/interfaces/IVaultManager.sol"; import {BaseVault} from "../src/vaults/BaseVault.sol"; import {IInvoiceManager} from "../src/interfaces/IInvoiceManager.sol"; @@ -19,6 +20,7 @@ import {IPaymasterVerifier} from "../src/interfaces/IPaymasterVerifier.sol"; import {UserOpSettlement} from "../src/settlement/UserOpSettlement.sol"; import {IPaymaster} from "account-abstraction/interfaces/IPaymaster.sol"; import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/ICrossL2Prover.sol"; +import {MockCrossL2Prover} from "../src/mocks/MockCrossL2Prover.sol"; contract CABPaymasterTest is Test { uint256 immutable BASE_SEPOLIA_CHAIN_ID = 84532; @@ -46,7 +48,7 @@ contract CABPaymasterTest is Test { owner = address(1); rekt = address(0x9590Ed0C18190a310f4e93CAccc4CC17270bED40); - crossL2Prover = ICrossL2Prover(address(0xBA3647D0749Cb37CD92Cc98e6185A77a8DCBFC62)); + crossL2Prover = ICrossL2Prover(address(new MockCrossL2Prover())); verifyingSignerPrivateKey = uint256(keccak256(abi.encodePacked("VERIFIYING_SIGNER"))); verifyingSignerAddress = vm.addr(verifyingSignerPrivateKey); @@ -100,6 +102,22 @@ contract CABPaymasterTest is Test { return abi.encodePacked(uint8(len), encodedSponsorToken); } + function encodeRepayToken(IInvoiceManager.RepayTokenInfo[] memory repayTokens) + internal + pure + returns (bytes memory encodedRepayToken) + { + for (uint8 i = 0; i < repayTokens.length; i++) { + encodedRepayToken = bytes.concat( + encodedRepayToken, + bytes20(address(repayTokens[i].vault)), + bytes32(repayTokens[i].amount), + bytes32(repayTokens[i].chainId) + ); + } + return abi.encodePacked(uint8(repayTokens.length), encodedRepayToken); + } + function getEncodedRepayTokens(uint8 len) internal returns (bytes memory encodedRepayToken) { IInvoiceManager.RepayTokenInfo[] memory repayTokens = new IInvoiceManager.RepayTokenInfo[](len); for (uint8 i = 0; i < len; i++) { @@ -115,6 +133,20 @@ contract CABPaymasterTest is Test { return abi.encodePacked(uint8(len), encodedRepayToken); } + function testEncodeRepayToken() public { + IInvoiceManager.RepayTokenInfo[] memory repayTokens = new IInvoiceManager.RepayTokenInfo[](1); + repayTokens[0] = IInvoiceManager.RepayTokenInfo({ + vault: IVault(address(0x8e2048c85Eae2a4443408C284221B33e61906463)), + amount: 500, + chainId: OPTIMISM_CHAIN_ID + }); + bytes memory encodedRepayToken = encodeRepayToken(repayTokens); + assertEq( + encodedRepayToken, + hex"018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc" + ); + } + function testValidateUserOp() public { vm.chainId(BASE_SEPOLIA_CHAIN_ID); bytes memory sponsorTokensBytes = getEncodedSponsorTokens(1); @@ -172,54 +204,62 @@ contract CABPaymasterTest is Test { userOp.paymasterAndData = bytes.concat(userOp.paymasterAndData, signature); - // DEMO paymasterAndData with sponsorToken address replaced by MockToken address to pass the approve in the paymaster - // userOp.paymasterAndData = - // hex"F6e64504ed56ec2725CDd0b3C1b23626D66008A2000000000000000000000000000f4240000000000000000000000000000186a000000136ea9100000127a851018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01a0Cb889707d426A7A386870A03bc70d1b0697598Fb619E988fD324734be51b0475A67b6921D0301f00000000000000000000000000000000000000000000000000000000000001f4b6e46e8f25f10e368181d6957e4c4021d7d563dfde06cf9e12d8f1c31a88986c3755ef5dcfed29cda855f525c473d1310890389bd5232da23946ed27594796111c"; - vm.startPrank(address(entryPoint)); (bytes memory context, uint256 validationData) = paymaster.validatePaymasterUserOp(userOp, userOpHash, type(uint256).max); - (, IInvoiceManager.RepayTokenInfo[] memory repayTokens) = paymaster.parseRepayTokenData(repayTokensBytes); // validate postOp // This is the event that we must track on dest chain and prove on source chain with Polymer proof system - vm.expectEmit(); - emit IPaymasterVerifier.InvoiceCreated( - invoiceManager.getInvoiceId(rekt, address(paymaster), userOp.nonce, BASE_SEPOLIA_CHAIN_ID, repayTokens) - ); + + // Calculate the expected invoiceId + bytes32 expectedInvoiceId = + invoiceManager.getInvoiceId(rekt, address(paymaster), userOp.nonce, BASE_SEPOLIA_CHAIN_ID, repayTokensBytes); + vm.expectEmit(true, true, true, true); + emit IPaymasterVerifier.InvoiceCreated(expectedInvoiceId); paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1222, 42); } -} -// function testGetHash() public { -// uint48 validUntil = 20379348; -// uint48 validAfter = 19379348; - -// PackedUserOperation memory userOp = PackedUserOperation({ -// sender: 0xFb619E988fD324734be51b0475A67b6921D0301f, -// nonce: 31996375400717808072039644266496, -// initCode: hex"91E60e0613810449d098b0b5Ec8b51A0FE8c89855fbfb9cf0000000000000000000000009590ed0c18190a310f4e93caccc4cc17270bed400000000000000000000000000000000000000000000000000000000000000000", -// callData: hex"47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000006423b872dd000000000000000000000000f6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000fb619e988fd324734be51b0475a67b6921d0301f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d85d3d270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965346636336c7935676a6471346c676d74336e33346f67637a716d757277663275326e756b693366747073613679636a79677865000000000000000000000000000000000000000000000000000000000000000000", -// accountGasLimits: 0x000000000000000000000000000f4240000000000000000000000000000f4240, -// preVerificationGas: 1000000, -// gasFees: 0x0000000000000000000000003b9aca00000000000000000000000000b2d05e00, -// paymasterAndData: hex"F6e64504ed56ec2725CDd0b3C1b23626D66008A2000000000000000000000000000f4240000000000000000000000000000186a000000136f6d400000127b494018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01fF3311cd15aB091B00421B23BcB60df02EFD8db7Fb619E988fD324734be51b0475A67b6921D0301f00000000000000000000000000000000000000000000000000000000000001f4469cfb36ee62bf829b1a21065b34fad76f69fc458ef15aef644db820e87880e91e9fc86ebe6112b1ad52f5cf050f63cabf35c42328886d54dba63d7912cbb7461b", -// signature: hex"47140b77612d9ea6bb0d7175ae8c91ea693e4e231fb48ae499c45d0a9309a42735de448da6ce53a4f195607a9afb7a4a21d5ae78c20cff808702d4e892a082ee1b" -// }); - -// vm.chainId(BASE_SEPOLIA_CHAIN_ID); -// bytes32 hash = paymaster.getHash(userOp, validUntil, validAfter); -// console.log("getHash returns:"); -// console.logBytes32(hash); - -// /* -// cast call 0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2 "getHash((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes),uint48,uint48)" "(0xFb619E988fD324734be51b0475A67b6921D0301f,31996375400717808072039644266496,0x91E60e0613810449d098b0b5Ec8b51A0FE8c89855fbfb9cf0000000000000000000000009590ed0c18190a310f4e93caccc4cc17270bed400000000000000000000000000000000000000000000000000000000000000000,0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000ff3311cd15ab091b00421b23bcb60df02efd8db7000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000 -// 00000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000006423b872dd000000000000000000000000f6e64504ed56ec2725cdd0b3c1b23626d66008a2000000000000000000000000fb619e988fd324734be51b0475a67b6921d0301f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678f0000000000000000000000000000000000000000000000000 -// 0000000000001f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d85d3d270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965346636336c7935676a6471346c676d74336e33346f67637a716d757277663275326e756b693366747073613679636a79677865000000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000f4240000000000000000000000000000f4240,1000000,0x0000000000000000000000003b9aca00000000000000000000000000b2d05e00,0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2000000000000000000000000000f4240000000000000000000000000000186a000000136f6d400000127b494018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01fF3311cd15aB091B00421B23BcB60df02EFD8db7Fb619E988fD324734be51b0475A67b6921D0301f00000000000000000000000000000000000000000000000000000000000001f4469cfb36ee62bf829b1a21065b34fad76f69fc458ef15aef644db820e87880e91e9fc86ebe6112b1ad52f5cf050f63cabf35c42328886d54dba63d7912cbb7461b,0x47140b77612d9ea6bb0d7175ae8c91ea693e4e231fb48ae499c45d0a9309a42735de448da6ce53a4f195607a9afb7a4a21d5ae78c20cff808702d4e892a082ee1b)" 20379348 19379348 --rpc-url https://sepolia.base.org - -// ==> 0x88800f0c29d6ba24fa635822ff4098e15b23048a57eec551b4a3af752aab97cd - -// */ -// // NEED TO MOCK paymaster address to pass the proof: 0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2!!! -// assert(hash == 0x88800f0c29d6ba24fa635822ff4098e15b23048a57eec551b4a3af752aab97cd); -// } + function testGetInvoiceId() public { + address account = 0x5E3Ae8798eAdE56c3B4fe8F085DAd16D4912Ba83; + address paymaster = 0xF6e64504ed56ec2725CDd0b3C1b23626D66008A2; + uint256 nonce = 32005827482497451446878209048576; + uint256 sponsorChainId = 84532; + + IInvoiceManager.RepayTokenInfo[] memory repayTokens = new IInvoiceManager.RepayTokenInfo[](1); + repayTokens[0] = IInvoiceManager.RepayTokenInfo({ + vault: IVault(0x8e2048c85Eae2a4443408C284221B33e61906463), + amount: 500, + chainId: 11155420 + }); + + bytes32 expectedInvoiceId = 0xccabf5a2f5630bf7e426047c30d25fd0afe4bff9651bc648b4174153a38e38d8; + bytes32 computedInvoiceId = + invoiceManager.getInvoiceId(account, paymaster, nonce, sponsorChainId, abi.encode(repayTokens)); + assertEq(computedInvoiceId, expectedInvoiceId, "Invoice ID computation mismatch"); + } + + function testVerifyInvoice() public { + bytes32 invoiceId = 0x886f7a98cfb9c6f2b6a6b4be00a89b75c0a846bf1c9b265b17ba4f9452acbc64; + bytes memory proof = + hex"00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000001640d989b47d36256c8eede18985987a9ef59ec16dd048d2bae790d9be4b37fa6b8e00000000000000000000000000000000000000000000000000000000012c438e000000000000000000000000000000000000000000000000000000000000208000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000009acb6000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000f6000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000e400000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000ee0000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000007e000000000000000000000000000000000000000000000000000000000000008c000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000b200000000000000000000000000000000000000000000000000000000000000be00000000000000000000000000000000000000000000000000000000000000cc0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000270204e8b24d208e12943afa90bac3aab0fb591950efc79664d99aad187011d4d8b8ae03ba2d5c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000270406e8b24d20e9caf6a4dd6892bb71fb43531df793cf16f2519260fac0ecaf9be16796f4d9c620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000027060ae8b24d2030daef3fbf15a9e6723034d8b771197eebec09f957f5c17785bca4690409385420000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000027081ae8b24d2056b07448868213f555a6b174af2284f8793869ceeff974904436b9b5eff3cb6220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000270a28e8b24d20fd78b81a9670e6310f6ed51d832916fd85adf38760af73f420ae96169d990d0f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000270c44e8b24d20c55d51dfe139f5af2d5ef686c07fbb5bb946787714f51d36d65e52829638e4fe20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000280ec401e8b24d207d7f819dbcab08495eb867e1f47fbb7c6ec07f4005289942a74c0d694880c7aa200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002810c403e8b24d20a190ba09650015f507b0f939d5c4abc1a99f9f778d7ab60f01503307a5e61a4e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000712ca04e8b24d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212006e25764f54a66f10e09ae1bb6a93cd9d36795915d41aebf649c55545782ab1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002814ca0ae8b24d20caa54d2923fbeea7aa0805e9e5b904fb3f00bc229cd246b5b62afd05e3a3a223200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000028168c10e8b24d20f83ecd51829306e433359c65fcde867037d07a9a12e8664d1661765988ee854320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000718fe2ee8b24d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212002b818877f29e4f46c7a64a7ac02d1562400673ee5ef09271dc3e30056e41bbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000281a9465e8b24d2057529676445aa1d3f54e424b57412982d773d9fe6e97217a5333dcd93f190cec2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000081c8cad01e8b24d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021208de3c5b46bbb8c0d364b7a6d64ac402991aad74d35725349008e18d020b4f1a90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000820a28f03e8b24d20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002120bc5dea1a3863bf853c6f78bd91e92d416b53d63dcc3a5e9b001cec4e7c5bc1f8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039636861696e2f38343533322f73746f72656452656365697074732f70726f6f665f6170692f72656365697074526f6f742f3139363738303934000000000000000000000000000000000000000000000000000000000000000000000000000020d989b47d36256c8eede18985987a9ef59ec16dd048d2bae790d9be4b37fa6b8e00000000000000000000000000000000000000000000000000000000000000050002e8b24d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005400000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020292cc35c96848c21c65a6bc632a4691c951cb3725e4ccccb595fd79d833f01ad000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000021019295bc5d601c43b416e419cc8c8fd7fa2635487133e612104cb6833bd5709359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002101a19a397ca7a6312793316f4743b8c19c56bcf3cd50aba80b13865a27e40b5034000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002101163e8925fd6ff7a35a815eb433251fdffef29e09b1249e5d4f0176cb2c1b6fd5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000206f0658ab82ea8c13e259f0b12b70fd4cde8d7de60fb95c2927a5cbf4e66d51380000000000000000000000000000000000000000000000000000000000000007706f6c79696263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020009df1612c8df4422f8f2004ac7a1c14bdc38e016ec9e7118b9b03bc8476d34e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000073f871a091634c2456a7d94c10a3bb39b533d47daf5e2ce340641ffc38152b3e68276bb6a0e662e95f085c6c019c89a44791508ecb08d09ae7576d67a57f922c1819bf8d7f808080808080a0ce58d857bac7a7a48c0027004318da0dab51282a433dca9fe9d3bbaffa0d0f4080808080808080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f4f901f180a027a41e3e2cf012635b9f52ac964fc3ac418f44707e59713e955506442b5f2aeba00a0a5d077a3f210fd466e728ea4243ac5a9ae76e924230ddcc9b9c5b50438678a06677662dd531b242fa9b330568425282856fceef794a5f2ca65218e799f42d30a05bcd2db7e5ccd0c6635f341f9e22a233f29965cd306ebc93a021e60c616b76ffa0eda9cb9b8bdafda2cacd2b861d7f87408ffa1251e234d84ee61debe2530a711da0acadb233a9e9499ad3b91b93ca2b293f80acd75b82fc6050066a36933fbcb6ffa03a24417d10e7f03c25e385db84ae098201d000a30a3b3465f06fefaa409715bba03f83fda6b6aec77d7e74befd01f957af67b0a9db24df1c68a51202db78e4df6da0cd03d6e03c35351182c6af8e12ebb821ebe063009f10d0dada0644dfb89f7742a049db42aa0452803945c870dc049333769c53ddbd50f582d15e73eb2fe080c769a0bdcdec4f9960eebe76ebe045f7ce9ba71f513f7087d7ba1eb682a147aab6bd3aa0f62805d1294bf940aa7fa28e87472fb4b5633549c902b872f9d6db8340f245a3a02ddb4ed23bcd990fe5c0edd6ed991904d69b6c748187c1941167312961691e5aa0e41e83dd146f7bec0910da01226d299d87680185cd697f1822358d0c2e194d7ca0352491e5502bad59e90f0a11165b19d61ab2f5df18f86258100af9ffb80aea578000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d5f906d220b906ce02f906ca01830cb02db9010000000000000010000000000000000000000000040000010000000000000000000008001000000000008000050000000000000000000000000000020000200000108000000000000000000008000000000041000000200040000000000000000001000000022800000000000800000800000000000000040000010010000000000000000000000000000000000000000000040000000000100400000000008000020000000000000000400000000000002000000000000000000002000004448000000002000000400001000080000000000000000000000000000000000020000010000004000000200000000002000000000080000000000000000000890000f905bff89b94ff3311cd15ab091b00421b23bcb60df02efd8db7f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000003cb057fd3be519cb50788b8b282732edbf533dc6a0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea000000000000000000000000000000000000000000000000000000000000001f4f838940000000071727de22e5e9d8baf0edac6f37da032e1a0bb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f97280f89b94ff3311cd15ab091b00421b23bcb60df02efd8db7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003cb057fd3be519cb50788b8b282732edbf533dc6a0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea000000000000000000000000000000000000000000000000000000000000001f4f89b94ff3311cd15ab091b00421b23bcb60df02efd8db7f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea0000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678fa000000000000000000000000000000000000000000000000000000000000001f4f89b94ff3311cd15ab091b00421b23bcb60df02efd8db7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea0000000000000000000000000d129bda7ce0888d7fd66ff46e7577c96984d678fa000000000000000000000000000000000000000000000000000000000000001f4f89c94d129bda7ce0888d7fd66ff46e7577c96984d678ff884a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea0000000000000000000000000000000000000000000000000000000000000002680f85894d129bda7ce0888d7fd66ff46e7577c96984d678fe1a0f8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce7a00000000000000000000000000000000000000000000000000000000000000026f89b94ff3311cd15ab091b00421b23bcb60df02efd8db7f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000003cb057fd3be519cb50788b8b282732edbf533dc6a0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea00000000000000000000000000000000000000000000000000000000000000000f85a943cb057fd3be519cb50788b8b282732edbf533dc6f842a0ab6994bba319b437692f1dbbe4d689382f25c4cfa9a291959e0af1ca5cd9e13fa0886f7a98cfb9c6f2b6a6b4be00a89b75c0a846bf1c9b265b17ba4f9452acbc6480f9011d940000000071727de22e5e9d8baf0edac6f37da032f884a049628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419fa072be8d36280e80a2c91b382858e0f39e9f47f8fc604cdc7f030da7de8637500aa0000000000000000000000000d6fe65f290d4ba8446b9efd3badad5a4bdfa98dea00000000000000000000000003cb057fd3be519cb50788b8b282732edbf533dc6b8800000000000000000000000000000000000000193fd7a5ad1000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000004f028b5b6e5a8000000000000000000000000000000000000000000000000000000000015358500000000000000000000000000000000000000000000000000000000000000000000000000000000000005383435333200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000"; + IInvoiceManager.RepayTokenInfo[] memory repayTokens = new IInvoiceManager.RepayTokenInfo[](1); + repayTokens[0] = IInvoiceManager.RepayTokenInfo({ + vault: IVault(0xeFb7144787FFFCEF306bC99cEBF42AB08d5609c8), + amount: 500, + chainId: 11155420 + }); + IInvoiceManager.InvoiceWithRepayTokens memory invoice = IInvoiceManager.InvoiceWithRepayTokens({ + account: 0xd6fe65f290d4BA8446b9EfD3BadAD5A4Bdfa98De, + nonce: 32007397118551674307018261266432, + paymaster: 0x3cB057Fd3BE519cB50788b8b282732edBF533DC6, + sponsorChainId: 84532, + repayTokenInfos: repayTokens + }); + + console.logBytes(abi.encode(invoice)); + + vm.expectEmit(true, true, true, true); + emit IPaymasterVerifier.InvoiceVerified(invoiceId); + assert(paymaster.verifyInvoice(invoiceId, invoice, proof)); + } +}