From ae0b2c7b80315897fb4b6fc4a76a4cec1f2c9e92 Mon Sep 17 00:00:00 2001 From: Vasile Gabriel Marian <56271768+VGabriel45@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:49:27 +0200 Subject: [PATCH 1/6] feat: added env variable (#153) Co-authored-by: VGabriel45 --- .github/workflows/unit-tests.yml | 1 + src/test/testUtils.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 297fb7a1..062565ca 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -28,5 +28,6 @@ jobs: PIMLICO_API_KEY: ${{ secrets.PIMLICO_API_KEY }} PAYMASTER_URL: ${{ secrets.PAYMASTER_URL }} BUNDLER_URL: ${{ secrets.BUNDLER_URL }} + VIRTUAL_BASE_SEPOLIA: ${{ secrets.VIRTUAL_BASE_SEPOLIA }} CHAIN_ID: 84532 CI: true diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 770aaab7..5f12f175 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -210,7 +210,7 @@ export const toConfiguredAnvil = async ({ port: rpcPort, codeSizeLimit: 1000000000000, forkUrl: shouldForkBaseSepolia - ? "https://virtual.base-sepolia.rpc.tenderly.co/7617b288-32ed-4f89-8a23-7195083f8bc9" + ? process.env.VIRTUAL_BASE_SEPOLIA : undefined } const instance = anvil(config) From 3e10439d518ce8a4689770ef305a1c5c82fa1399 Mon Sep 17 00:00:00 2001 From: Vasile Gabriel Marian <56271768+VGabriel45@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:14:27 +0200 Subject: [PATCH 2/6] feat: token paymaster integration (#149) Co-authored-by: VGabriel45 Co-authored-by: Joe Pegler --- src/sdk/account/utils/Constants.ts | 2 +- src/sdk/account/utils/Utils.ts | 17 ++ src/sdk/clients/createBicoBundlerClient.ts | 4 +- .../clients/createBicoPaymasterClient.test.ts | 193 +++++++++++++++++- src/sdk/clients/createBicoPaymasterClient.ts | 71 ++++++- src/sdk/clients/createNexusClient.ts | 7 +- .../clients/decorators/smartAccount/index.ts | 119 +++++++---- .../prepareTokenPaymasterUserOp.ts | 102 +++++++++ .../smartAccount/sendTokenPaymasterUserOp.ts | 99 +++++++++ .../tokenPaymaster/getSupportedTokens.ts | 36 ++++ .../tokenPaymaster/getTokenPaymasterQuotes.ts | 159 +++++++++++++++ .../decorators/tokenPaymaster/index.ts | 138 +++++++++++++ src/test/playground.test.ts | 5 +- 13 files changed, 903 insertions(+), 49 deletions(-) create mode 100644 src/sdk/clients/decorators/smartAccount/prepareTokenPaymasterUserOp.ts create mode 100644 src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts create mode 100644 src/sdk/clients/decorators/tokenPaymaster/getSupportedTokens.ts create mode 100644 src/sdk/clients/decorators/tokenPaymaster/getTokenPaymasterQuotes.ts create mode 100644 src/sdk/clients/decorators/tokenPaymaster/index.ts diff --git a/src/sdk/account/utils/Constants.ts b/src/sdk/account/utils/Constants.ts index 73e3e5a1..2524331b 100644 --- a/src/sdk/account/utils/Constants.ts +++ b/src/sdk/account/utils/Constants.ts @@ -3,7 +3,7 @@ export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000" export const MAGIC_BYTES = "0x6492649264926492649264926492649264926492649264926492649264926492" export const BICONOMY_TOKEN_PAYMASTER = - "0x00000f7365cA6C59A2C93719ad53d567ed49c14C" + "0x00000000301515A5410e0d768aF4f53c416edf19" export const DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS = "0x8EBDcA5ce92f9aBF1D1ab21de24068B2a2EaF808" export const EIP1559_UNSUPPORTED_NETWORKS: Array = [97, 56, 1442, 1101] diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 839c0fc3..0ad96ada 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -12,6 +12,7 @@ import { encodeAbiParameters, encodeFunctionData, encodePacked, + erc20Abi, hexToBytes, keccak256, parseAbi, @@ -22,6 +23,7 @@ import { toHex } from "viem" import { + BICONOMY_TOKEN_PAYMASTER, MOCK_MULTI_MODULE_ADDRESS, MODULE_ENABLE_MODE_TYPE_HASH, NEXUS_DOMAIN_NAME, @@ -391,3 +393,18 @@ export type EthersWallet = { address: Address | string provider: AnyData } + +export const getAllowance = async ( + client: PublicClient, + accountAddress: Address, + tokenAddress: Address +): Promise => { + const approval = await client.readContract({ + address: tokenAddress, + abi: erc20Abi, + functionName: "allowance", + args: [accountAddress, BICONOMY_TOKEN_PAYMASTER] + }) + + return approval as bigint +} diff --git a/src/sdk/clients/createBicoBundlerClient.ts b/src/sdk/clients/createBicoBundlerClient.ts index 7d168aa4..dbd59d21 100644 --- a/src/sdk/clients/createBicoBundlerClient.ts +++ b/src/sdk/clients/createBicoBundlerClient.ts @@ -15,7 +15,7 @@ import { type SmartAccount, createBundlerClient } from "viem/account-abstraction" -import { biconomyPaymasterContext } from "./createBicoPaymasterClient" +import { biconomySponsoredPaymasterContext } from "./createBicoPaymasterClient" import { type BicoActions, bicoBundlerActions } from "./decorators/bundler" import type { BicoRpcSchema, @@ -105,7 +105,7 @@ export const createBicoBundlerClient = ( } const defaultedPaymasterContext = parameters.paymaster - ? parameters.paymasterContext ?? biconomyPaymasterContext + ? parameters.paymasterContext ?? biconomySponsoredPaymasterContext : undefined const bundler_ = createBundlerClient({ diff --git a/src/sdk/clients/createBicoPaymasterClient.test.ts b/src/sdk/clients/createBicoPaymasterClient.test.ts index 01e84950..d002dcf3 100644 --- a/src/sdk/clients/createBicoPaymasterClient.test.ts +++ b/src/sdk/clients/createBicoPaymasterClient.test.ts @@ -6,7 +6,9 @@ import { type PublicClient, type WalletClient, createPublicClient, - createWalletClient + createWalletClient, + parseAbi, + parseUnits } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { paymasterTruthy, toNetwork } from "../../test/testSetup" @@ -15,7 +17,8 @@ import type { NetworkConfig, TestnetParams } from "../../test/testUtils" import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" import { type BicoPaymasterClient, - createBicoPaymasterClient + createBicoPaymasterClient, + toBiconomyTokenPaymasterContext } from "./createBicoPaymasterClient" import { type NexusClient, createNexusClient } from "./createNexusClient" @@ -38,6 +41,11 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { let nexusAccount: NexusAccount let nexusClient: NexusClient + const baseSepoliaUSDCAddress: Address = + "0x036cbd53842c5426634e7929541ec2318f3dcf7e" + const baseSepoliaDAIAddress: Address = + "0x7683022d84f726a96c4a6611cd31dbf5409c0ac9" + beforeAll(async () => { network = await toNetwork("PUBLIC_TESTNET") @@ -123,4 +131,185 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { // No gas fees were paid, so the balance should have decreased only by 1n expect(finalBalance).toBe(initialBalance - 1n) }) + + test("should use token paymaster to pay for gas fees, use max approval, use sendUserOperation", async () => { + const paymasterContext = toBiconomyTokenPaymasterContext({ + feeTokenAddress: baseSepoliaUSDCAddress + }) + const nexusClient = await createNexusClient({ + signer: account, + chain, + paymaster: createBicoPaymasterClient({ + transport: http(paymasterUrl) + }), + paymasterContext, + transport: http(), + bundlerTransport: http(bundlerUrl), + ...testParams + }) + + const initialBalance = await publicClient.getBalance({ + address: nexusAccountAddress + }) + + const receipt = await nexusClient.sendTokenPaymasterUserOp({ + calls: [ + { + to: recipientAddress, + value: 1n, + data: "0x" + } + ], + feeTokenAddress: baseSepoliaUSDCAddress + }) + expect(receipt.success).toBe("true") + + // Get final balance + const finalBalance = await publicClient.getBalance({ + address: nexusAccountAddress + }) + + // Check that the balance hasn't changed + // No gas fees were paid, so the balance should have decreased only by 1n + expect(finalBalance).toBe(initialBalance - 1n) + }) + + test("should use token paymaster to pay for gas fees, use max approval, use sendTransaction", async () => { + const paymasterContext = toBiconomyTokenPaymasterContext({ + feeTokenAddress: baseSepoliaUSDCAddress + }) + const nexusClient = await createNexusClient({ + signer: account, + chain, + paymaster: createBicoPaymasterClient({ + transport: http(paymasterUrl) + }), + paymasterContext, + transport: http(), + bundlerTransport: http(bundlerUrl), + ...testParams + }) + + const initialBalance = await publicClient.getBalance({ + address: nexusAccountAddress + }) + + const tokenPaymasterUserOp = await nexusClient.prepareTokenPaymasterUserOp({ + calls: [ + { + to: recipientAddress, + value: 1n, + data: "0x" + } + ], + feeTokenAddress: baseSepoliaUSDCAddress + }) + + const hash = await nexusClient.sendTransaction(tokenPaymasterUserOp) + + const receipt = await publicClient.waitForTransactionReceipt({ hash }) + + expect(receipt.status).toBe("success") + + // Get final balance + const finalBalance = await publicClient.getBalance({ + address: nexusAccountAddress + }) + + // Check that the balance hasn't changed + // No gas fees were paid, so the balance should have decreased only by 1n + expect(finalBalance).toBe(initialBalance - 1n) + }) + + test("should use token paymaster to pay for gas fees, use custom approval with token paymaster quotes", async () => { + const paymasterContext = toBiconomyTokenPaymasterContext({ + feeTokenAddress: baseSepoliaUSDCAddress + }) + const nexusClient = await createNexusClient({ + signer: account, + chain, + paymaster: createBicoPaymasterClient({ + transport: http(paymasterUrl) + }), + paymasterContext, + transport: http(), + bundlerTransport: http(bundlerUrl), + ...testParams + }) + + const usdcBalance = await publicClient.readContract({ + address: baseSepoliaUSDCAddress, + abi: parseAbi([ + "function balanceOf(address owner) public view returns (uint256 balance)" + ]), + functionName: "balanceOf", + args: [nexusClient.account.address] + }) + + expect(usdcBalance).toBeGreaterThan(0n) + + const initialBalance = await publicClient.getBalance({ + address: nexusClient.account.address + }) + + const tokenList = [baseSepoliaUSDCAddress] + const userOp = await nexusClient.prepareUserOperation({ + calls: [ + { + to: recipientAddress, + value: 1n, + data: "0x" + } + ] + }) + const quote = await paymaster.getTokenPaymasterQuotes({ userOp, tokenList }) + const usdcFeeAmount = parseUnits( + quote.feeQuotes[0].maxGasFee.toString(), + quote.feeQuotes[0].decimal + ) + + const receipt = await nexusClient.sendTokenPaymasterUserOp({ + calls: [ + { + to: recipientAddress, + value: 1n, + data: "0x" + } + ], + feeTokenAddress: baseSepoliaUSDCAddress, + customApprovalAmount: usdcFeeAmount + }) + + expect(receipt.success).toBe("true") + + const finalBalance = await publicClient.getBalance({ + address: nexusClient.account.address + }) + + expect(finalBalance).toBe(initialBalance - 1n) + }) + + test("should retrieve all supported token addresses from the token paymaster", async () => { + const paymasterContext = toBiconomyTokenPaymasterContext({ + feeTokenAddress: baseSepoliaUSDCAddress + }) + const nexusClient = await createNexusClient({ + signer: account, + chain, + paymaster: createBicoPaymasterClient({ + transport: http(paymasterUrl) + }), + paymasterContext, + transport: http(), + bundlerTransport: http(bundlerUrl), + ...testParams + }) + + const supportedTokens = await paymaster.getSupportedTokens(nexusClient) + const supportedTokenAddresses = supportedTokens.map( + (token) => token.tokenAddress + ) + expect(supportedTokenAddresses).toContain(baseSepoliaUSDCAddress) + expect(supportedTokenAddresses).toContain(baseSepoliaDAIAddress) + }) }) diff --git a/src/sdk/clients/createBicoPaymasterClient.ts b/src/sdk/clients/createBicoPaymasterClient.ts index 31682fc3..696c7bf6 100644 --- a/src/sdk/clients/createBicoPaymasterClient.ts +++ b/src/sdk/clients/createBicoPaymasterClient.ts @@ -1,11 +1,19 @@ -import { http, type OneOf, type Transport } from "viem" +import { http, type Address, type OneOf, type Transport } from "viem" import { type PaymasterClient, type PaymasterClientConfig, createPaymasterClient } from "viem/account-abstraction" +import { + type TokenPaymasterActions, + bicoTokenPaymasterActions +} from "./decorators/tokenPaymaster" -export type BicoPaymasterClient = Omit +export type BicoPaymasterClient = Omit< + PaymasterClient, + "getPaymasterStubData" +> & + TokenPaymasterActions /** * Configuration options for creating a Bico Paymaster Client. @@ -30,9 +38,30 @@ type BicoPaymasterClientConfig = Omit & > /** - * Context for the Bico Paymaster. + * Context for the Bico SPONSORED Paymaster. */ -export const biconomyPaymasterContext = { +export type PaymasterContext = { + mode: "ERC20" | "SPONSORED" + sponsorshipInfo?: { + smartAccountInfo: { + name: string + version: string + } + } + tokenInfo: { + feeTokenAddress: Address + } + expiryDuration?: number + calculateGasLimits?: boolean +} + +type ToBiconomyTokenPaymasterContextParams = { + feeTokenAddress: Address + expiryDuration?: number + calculateGasLimits?: boolean +} + +export const biconomySponsoredPaymasterContext = { mode: "SPONSORED", expiryDuration: 300, calculateGasLimits: true, @@ -44,6 +73,26 @@ export const biconomyPaymasterContext = { } } +export const toBiconomyTokenPaymasterContext = ( + params: ToBiconomyTokenPaymasterContextParams +): PaymasterContext => { + const { feeTokenAddress, expiryDuration, calculateGasLimits } = params + return { + mode: "ERC20", + sponsorshipInfo: { + smartAccountInfo: { + name: "BICONOMY", + version: "2.0.0" + } + }, + tokenInfo: { + feeTokenAddress + }, + expiryDuration: expiryDuration ?? 6000, + calculateGasLimits: calculateGasLimits ?? true + } +} + /** * Creates a Bico Paymaster Client. * @@ -64,6 +113,18 @@ export const biconomyPaymasterContext = { * @example * // Create a client with chain ID and API key * const client3 = createBicoPaymasterClient({ chainId: 1, apiKey: 'your-api-key' }) + * + * @example + * // Create a Token Paymaster Client + * const tokenPaymasterClient = createBicoPaymasterClient({ + * paymasterUrl: 'https://example.com/paymaster', + * paymasterContext: { + * mode: "ERC20", + * tokenInfo: { + * feeTokenAddress: "0x..." + * } + * }, + * }) */ export const createBicoPaymasterClient = ( parameters: BicoPaymasterClientConfig @@ -80,7 +141,7 @@ export const createBicoPaymasterClient = ( const { getPaymasterStubData, ...paymasterClient } = createPaymasterClient({ ...parameters, transport: defaultedTransport - }) + }).extend(bicoTokenPaymasterActions()) return paymasterClient } diff --git a/src/sdk/clients/createNexusClient.ts b/src/sdk/clients/createNexusClient.ts index 2ebc4dc6..7e0a6d8b 100644 --- a/src/sdk/clients/createNexusClient.ts +++ b/src/sdk/clients/createNexusClient.ts @@ -34,6 +34,7 @@ import { } from "../constants" import type { AnyData, Module } from "../modules/utils/Types" import { createBicoBundlerClient } from "./createBicoBundlerClient" +import type { PaymasterContext } from "./createBicoPaymasterClient" import { type Erc7579Actions, erc7579Actions } from "./decorators/erc7579" import { type SmartAccountActions, @@ -86,7 +87,7 @@ export type NexusClient< /** * Optional paymaster context */ - paymasterContext?: BundlerClientConfig["paymasterContext"] | undefined + paymasterContext?: PaymasterContext | undefined /** * Optional user operation configuration */ @@ -125,7 +126,7 @@ export type NexusClientConfig< } | undefined /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ - paymasterContext?: unknown + paymasterContext?: PaymasterContext /** User Operation configuration. */ userOperation?: | { @@ -197,6 +198,7 @@ export async function createNexusClient( bundlerTransport, transport, accountAddress, + paymasterContext, attesters, attesterThreshold, ...bundlerConfig @@ -222,6 +224,7 @@ export async function createNexusClient( chain, key, name, + paymasterContext, account: nexusAccount, transport: bundlerTransport }) diff --git a/src/sdk/clients/decorators/smartAccount/index.ts b/src/sdk/clients/decorators/smartAccount/index.ts index fc6b36dd..cab9d11a 100644 --- a/src/sdk/clients/decorators/smartAccount/index.ts +++ b/src/sdk/clients/decorators/smartAccount/index.ts @@ -5,15 +5,26 @@ import type { ContractFunctionArgs, ContractFunctionName, Hash, - SendTransactionParameters, Transport, TypedData, WaitForTransactionReceiptParameters, WaitForTransactionReceiptReturnType, WriteContractParameters } from "viem" -import type { SmartAccount } from "viem/account-abstraction" +import type { + SmartAccount, + UserOperation, + WaitForUserOperationReceiptReturnType +} from "viem/account-abstraction" import type { AnyData } from "../../../modules/utils/Types" +import { + type PrepareTokenPaymasterUserOpParameters, + prepareTokenPaymasterUserOp +} from "./prepareTokenPaymasterUserOp" +import { + type SendTokenPaymasterUserOpParameters, + sendTokenPaymasterUserOp +} from "./sendTokenPaymasterUserOp" import { sendTransaction } from "./sendTransaction" import { signMessage } from "./signMessage" import { signTypedData } from "./signTypedData" @@ -25,47 +36,82 @@ export type SmartAccountActions< TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined > = { /** - * Creates, signs, and sends a new transaction to the network. - * This function also allows you to sponsor this transaction if sender is a smartAccount + * Prepares and sends a user operation with token paymaster * - * - Docs: https://viem.sh/nexus-client/methods#sendtransaction.html - * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions - * - JSON-RPC Methods: - * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) - * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * @param client - The Nexus client instance + * @param args - The parameters for the token paymaster user operation + * @param args.calls - Array of transactions to be executed + * @param args.feeTokenAddress - Address of the token to be used for paying gas fees + * @param args.customApprovalAmount - Optional custom amount to approve for the paymaster (defaults to unlimited) + * + * @example + * ```ts + * const receipt = await sendTokenPaymasterUserOp(client, { + * calls: [{ + * to: "0x...", // Contract address + * data: "0x...", // Encoded function data + * value: BigInt(0) + * }], + * feeTokenAddress: "0x...", // USDC/USDT/etc address + * customApprovalAmount: BigInt(1000) // Optional: specific approval amount + * }) + * ``` + * + * @returns A promise that resolves to the user operation receipt {@link WaitForUserOperationReceiptReturnType} + */ + sendTokenPaymasterUserOp: ( + args: SendTokenPaymasterUserOpParameters + ) => Promise + /** + * Prepares a user operation with token paymaster configuration, including ERC20 token approval + * + * This function handles: + * 1. Checking current token allowance of Smart Account + * 2. Creating an approval transaction for the token paymaster if needed + * 3. Preparing the user operation with the approval and user transactions * - * @param args - {@link SendTransactionParameters} - * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} + * @param client - The NexusClient instance + * @param args.txs - Array of transactions to be executed + * @param args.feeTokenAddress - Token used for paying for the gas + * @param args.customApprovalAmount - Optional custom approval amount + * + * @returns A prepared user operation without signature (will be signed by the Smart Account when sent) * * @example - * import { createWalletClient, custom } from 'viem' - * import { mainnet } from 'viem/chains' + * ```typescript + * const userOp = await prepareTokenPaymasterUserOp(nexusClient, { + * txs: [ + * { + * to: recipientAddress, + * value: 1n, + * data: "0x" + * } + * ], + * feeTokenAddress: baseSepoliaUSDCAddress, + * customApprovalAmount: usdcFeeAmount + * }) + * ``` * - * const client = createWalletClient({ - * chain: mainnet, - * transport: custom(window.ethereum), - * }) - * const hash = await client.sendTransaction({ - * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', - * value: 1000000000000000000n, - * }) + * @throws Will throw an error if client account or paymaster context is not properly configured + */ + prepareTokenPaymasterUserOp: ( + args: PrepareTokenPaymasterUserOpParameters + ) => Promise> + /** + * Creates, signs, and sends a new transaction to the network using a smart account. + * This function also allows you to sponsor this transaction if the sender is a smart account. + * + * @param client - The client instance. + * @param args - Parameters for sending the transaction or user operation. + * @param customApprovalAmount - The amount to approve for the Biconomy Token Paymaster to be spent on gas. + * @returns The transaction hash as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. * * @example - * // Account Hoisting - * import { createWalletClient, http } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' - * import { mainnet } from 'viem/chains' + * import { sendTransaction } from '@biconomy/sdk' * - * const client = createWalletClient({ - * account: privateKeyToAccount('0x…'), - * chain: mainnet, - * transport: http(), - * }) - * const hash = await client.sendTransaction({ - * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', - * value: 1000000000000000000n, - * }) + * const hash = await nexusClient.sendTransaction({calls: [{to: '0x...', value: parseEther('0.1'), data: '0x...'}]}) + * console.log(hash) // '0x...' */ sendTransaction: < TChainOverride extends Chain | undefined = undefined, @@ -324,6 +370,9 @@ export function smartAccountActions() { >( client: Client ): SmartAccountActions => ({ + sendTokenPaymasterUserOp: (args) => sendTokenPaymasterUserOp(client, args), + prepareTokenPaymasterUserOp: (args) => + prepareTokenPaymasterUserOp(client, args), sendTransaction: (args) => sendTransaction(client, args as AnyData), signMessage: (args) => signMessage(client, args), signTypedData: (args) => signTypedData(client, args), diff --git a/src/sdk/clients/decorators/smartAccount/prepareTokenPaymasterUserOp.ts b/src/sdk/clients/decorators/smartAccount/prepareTokenPaymasterUserOp.ts new file mode 100644 index 00000000..7ca3853e --- /dev/null +++ b/src/sdk/clients/decorators/smartAccount/prepareTokenPaymasterUserOp.ts @@ -0,0 +1,102 @@ +import { + type Address, + type Chain, + type Client, + type Transport, + erc20Abi, + maxUint256 +} from "viem" +import { encodeFunctionData } from "viem" +import { + type SmartAccount, + type UserOperation, + prepareUserOperation +} from "viem/account-abstraction" +import { getAction } from "viem/utils" +import { BICONOMY_TOKEN_PAYMASTER } from "../../../account/utils/Constants" + +export type Transaction = { + to: Address + data: `0x${string}` + value: bigint +} + +/** + * Parameters for preparing a token paymaster user operation + */ +export type PrepareTokenPaymasterUserOpParameters = { + /** Array of transactions to be executed */ + calls: Transaction[] + /** Token used for paying for the gas */ + feeTokenAddress: Address + /** Optional custom approval amount for the token paymaster. If not provided, max uint256 will be used */ + customApprovalAmount?: bigint +} + +/** + * Prepares a user operation with token paymaster configuration, including ERC20 token approval + * + * This function handles: + * 1. Checking current token allowance of Smart Account + * 2. Creating an approval transaction for the token paymaster if needed + * 3. Preparing the user operation with the approval and user transactions + * + * @param client - The NexusClient instance + * @param args.txs - Array of transactions to be executed + * @param args.feeTokenAddress - Token used for paying for the gas + * @param args.customApprovalAmount - Optional custom approval amount + * + * @returns A prepared user operation without signature (will be signed by the Smart Account when sent) + * + * @example + * ```typescript + * const userOp = await prepareTokenPaymasterUserOp(nexusClient, { + * txs: [ + * { + * to: recipientAddress, + * value: 1n, + * data: "0x" + } + ], + customApprovalAmount: usdcFeeAmount + }) + * ``` + * + * @throws Will throw an error if client account or paymaster context is not properly configured + */ +export async function prepareTokenPaymasterUserOp< + account extends SmartAccount | undefined, + chain extends Chain | undefined +>( + client: Client, + args: PrepareTokenPaymasterUserOpParameters +): Promise, "signature">> { + const { calls, customApprovalAmount, feeTokenAddress } = args + + const userOp = await getAction( + client, + prepareUserOperation, + "prepareUserOperation" + )({ + calls: [ + { + to: feeTokenAddress, + data: encodeFunctionData({ + functionName: "approve", + abi: erc20Abi, + args: [BICONOMY_TOKEN_PAYMASTER, customApprovalAmount ?? maxUint256] + }), + value: BigInt(0) + }, + ...calls + ], + account: client.account as SmartAccount + }) + + const partialUserOp = { + ...userOp, + signature: undefined + } + + return partialUserOp +} diff --git a/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts b/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts new file mode 100644 index 00000000..a92ea6b0 --- /dev/null +++ b/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts @@ -0,0 +1,99 @@ +import type { Chain, Transport } from "viem" +import type { Client } from "viem" +import { maxUint256 } from "viem" +import { erc20Abi } from "viem" +import { type Address, encodeFunctionData } from "viem" +import { + type SmartAccount, + type WaitForUserOperationReceiptReturnType, + sendUserOperation, + waitForUserOperationReceipt +} from "viem/account-abstraction" +import { getAction } from "viem/utils" +import { BICONOMY_TOKEN_PAYMASTER } from "../../../account/utils/Constants" +import { + type Transaction, + prepareTokenPaymasterUserOp +} from "./prepareTokenPaymasterUserOp" + +export type SendTokenPaymasterUserOpParameters = { + calls: Transaction[] + feeTokenAddress: Address + customApprovalAmount?: bigint +} + +/** + * Prepares and sends a user operation with token paymaster + * + * @param client - The Nexus client instance + * @param args - The parameters for the token paymaster user operation + * @param args.calls - Array of transactions to be executed + * @param args.feeTokenAddress - Address of the token to be used for paying gas fees + * @param args.customApprovalAmount - Optional custom amount to approve for the paymaster (defaults to unlimited) + * + * @example + * ```ts + * const receipt = await sendTokenPaymasterUserOp(client, { + * calls: [{ + * to: "0x...", // Contract address + * data: "0x...", // Encoded function data + * value: BigInt(0) + * }], + * feeTokenAddress: "0x...", // USDC/USDT/etc address + * customApprovalAmount: BigInt(1000) // Optional: specific approval amount + * }) + * ``` + * + * @returns A promise that resolves to the user operation receipt {@link WaitForUserOperationReceiptReturnType} + */ +export async function sendTokenPaymasterUserOp< + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client, + args: SendTokenPaymasterUserOpParameters +): Promise { + const { calls, feeTokenAddress, customApprovalAmount } = args + + const userOp = await getAction( + client, + prepareTokenPaymasterUserOp, + "prepareTokenPaymasterUserOperation" + )({ + calls: [ + { + to: feeTokenAddress, + data: encodeFunctionData({ + functionName: "approve", + abi: erc20Abi, + args: [BICONOMY_TOKEN_PAYMASTER, customApprovalAmount ?? maxUint256] + }), + value: BigInt(0) + }, + ...calls + ], + feeTokenAddress, + customApprovalAmount + }) + + const partialUserOp = { + ...userOp, + signature: undefined + } + + const userOpHash = await getAction( + client, + sendUserOperation, + "sendUserOperation" + )(partialUserOp) + + const receipt = await getAction( + client, + waitForUserOperationReceipt, + "waitForUserOperationReceipt" + )({ + hash: userOpHash + }) + + return receipt +} diff --git a/src/sdk/clients/decorators/tokenPaymaster/getSupportedTokens.ts b/src/sdk/clients/decorators/tokenPaymaster/getSupportedTokens.ts new file mode 100644 index 00000000..a43b2f62 --- /dev/null +++ b/src/sdk/clients/decorators/tokenPaymaster/getSupportedTokens.ts @@ -0,0 +1,36 @@ +import type { BicoPaymasterClient } from "../../createBicoPaymasterClient" +import type { NexusClient } from "../../createNexusClient" +import type { FeeQuote } from "./getTokenPaymasterQuotes" + +/** + * Retrieves the supported tokens for the Biconomy Token Paymaster.. + * + * @param client - The Nexus client instance + * @returns A promise that resolves to an array of FeeQuote objects. + * + * @example + * ```typescript + * const supportedTokens = await paymaster.getSupportedTokens(nexusClient); + * console.log(supportedTokens); + * ``` + */ +export const getSupportedTokens = async ( + client: NexusClient +): Promise => { + const userOp = await client.prepareUserOperation({ + calls: [ + { + to: client.account.address, + data: "0x", + value: 0n + } + ] + }) + const paymaster = client.paymaster as BicoPaymasterClient + const quote = await paymaster.getTokenPaymasterQuotes({ + userOp, + tokenList: [] + }) + + return quote.feeQuotes +} diff --git a/src/sdk/clients/decorators/tokenPaymaster/getTokenPaymasterQuotes.ts b/src/sdk/clients/decorators/tokenPaymaster/getTokenPaymasterQuotes.ts new file mode 100644 index 00000000..94062b96 --- /dev/null +++ b/src/sdk/clients/decorators/tokenPaymaster/getTokenPaymasterQuotes.ts @@ -0,0 +1,159 @@ +import type { Account, Address, Chain, Client, Transport } from "viem" +import type { UserOperation } from "viem/account-abstraction" + +export type BicoTokenPaymasterRpcSchema = [ + { + Method: "pm_getFeeQuoteOrData" + Parameters: [TokenPaymasterUserOpParams, TokenPaymasterConfigParams] + ReturnType: TokenPaymasterQuotesResponse + } +] + +type PaymasterMode = "ERC20" + +export type FeeQuote = { + symbol: string + decimal: number + tokenAddress: Address + maxGasFee: number + maxGasFeeUSD: number + exchangeRate: number + logoUrl: string + premiumPercentage: string + validUntil: number +} + +export type TokenPaymasterQuotesResponse = { + mode: PaymasterMode + paymasterAddress: Address + feeQuotes: FeeQuote[] + unsupportedTokens: any[] +} + +export type TokenPaymasterUserOpParams = { + sender: Address + nonce: string + factory: Address | undefined + factoryData: `0x${string}` | undefined + callData: `0x${string}` + maxFeePerGas: string + maxPriorityFeePerGas: string + verificationGasLimit: string + callGasLimit: string + preVerificationGas: string + paymasterPostOpGasLimit: string + paymasterVerificationGasLimit: string +} + +export type TokenPaymasterConfigParams = { + mode: PaymasterMode + sponsorshipInfo: { + smartAccountInfo: { + name: string + version: string + } + } + tokenInfo: { + tokenList: Address[] + } + expiryDuration: number + calculateGasLimits: boolean +} + +export type GetTokenPaymasterQuotesParameters = { + userOp: UserOperation + tokenList: Address[] +} + +/** + * Fetches paymaster quotes for ERC20 token payment options for a given UserOperation. + * + * @param userOp - The UserOperation to get paymaster quotes for + * @param client - Viem Client configured with BicoTokenPaymaster RPC methods + * @param tokenList - Array of ERC20 token addresses to get quotes for + * + * @returns A promise of {@link TokenPaymasterQuotesResponse} + * + * @example + * ```typescript + * // Configure client with paymaster RPC + * const paymasterClient = createBicoPaymasterClient({ + * paymasterUrl + * }) + * + * // Token addresses to get quotes for + * const tokenList = [ + * "0x...", // USDT + * "0x..." // USDC + * ]; + * + * // Get paymaster quotes + * const quotes = await paymasterClient.getTokenPaymasterQuotes(userOp, tokenList); + * + * // Example response: + * // { + * // mode: "ERC20", + * // paymasterAddress: "0x...", + * // feeQuotes: [{ + * // symbol: "USDT", + * // decimal: 6, + * // tokenAddress: "0x...", + * // maxGasFee: 5000000, + * // maxGasFeeUSD: 5, + * // exchangeRate: 1, + * // logoUrl: "https://...", + * // premiumPercentage: "0.1", + * // validUntil: 1234567890 + * // }], + * // unsupportedTokens: [] + * // } + * ``` + */ +export const getTokenPaymasterQuotes = async ( + client: Client< + Transport, + Chain | undefined, + Account | undefined, + BicoTokenPaymasterRpcSchema + >, + parameters: GetTokenPaymasterQuotesParameters +): Promise => { + const { userOp, tokenList } = parameters + const quote = await client.request({ + method: "pm_getFeeQuoteOrData", + params: [ + { + sender: userOp.sender, + nonce: userOp.nonce.toString(), + factory: userOp.factory, + factoryData: userOp.factoryData, + callData: userOp.callData, + maxFeePerGas: userOp.maxFeePerGas.toString(), + maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(), + verificationGasLimit: BigInt(userOp.verificationGasLimit).toString(), + callGasLimit: BigInt(userOp.callGasLimit).toString(), + preVerificationGas: BigInt(userOp.preVerificationGas).toString(), + paymasterPostOpGasLimit: + userOp.paymasterPostOpGasLimit?.toString() ?? "0", + paymasterVerificationGasLimit: + userOp.paymasterVerificationGasLimit?.toString() ?? "0" + }, + { + mode: "ERC20", + sponsorshipInfo: { + smartAccountInfo: { + name: "BICONOMY", + version: "2.0.0" + } + }, + tokenInfo: { + tokenList + }, + expiryDuration: 6000, + calculateGasLimits: true + } + ] + }) + + return quote +} diff --git a/src/sdk/clients/decorators/tokenPaymaster/index.ts b/src/sdk/clients/decorators/tokenPaymaster/index.ts new file mode 100644 index 00000000..1483ac3a --- /dev/null +++ b/src/sdk/clients/decorators/tokenPaymaster/index.ts @@ -0,0 +1,138 @@ +import type { PaymasterClient } from "viem/account-abstraction" +import type { NexusClient } from "../../createNexusClient" +import { getSupportedTokens } from "./getSupportedTokens" +import { + type FeeQuote, + type GetTokenPaymasterQuotesParameters, + type TokenPaymasterQuotesResponse, + getTokenPaymasterQuotes +} from "./getTokenPaymasterQuotes" + +export type TokenPaymasterActions = { + /** + * Fetches paymaster quotes for ERC20 token payment options for a given UserOperation. + * + * @param userOp - The UserOperation to get paymaster quotes for + * @param client - Viem Client configured with BicoTokenPaymaster RPC methods + * @param tokenList - Array of ERC20 token addresses to get quotes for + * + * @returns A promise of {@link TokenPaymasterQuotesResponse} + * + * @example + * ```typescript + * // Configure client with paymaster RPC + * const paymasterClient = createBicoPaymasterClient({ + * paymasterUrl + * }) + * + * // Token addresses to get quotes for + * const tokenList = [ + * "0x...", // USDT + * "0x..." // USDC + * ]; + * + * // Get paymaster quotes + * const quotes = await paymasterClient.getTokenPaymasterQuotes(userOp, tokenList); + * + * // Example response: + * // { + * // mode: "ERC20", + * // paymasterAddress: "0x...", + * // feeQuotes: [{ + * // symbol: "USDT", + * // decimal: 6, + * // tokenAddress: "0x...", + * // maxGasFee: 5000000, + * // maxGasFeeUSD: 5, + * // exchangeRate: 1, + * // logoUrl: "https://...", + * // premiumPercentage: "0.1", + * // validUntil: 1234567890 + * // }], + * // unsupportedTokens: [] + * // } + * ``` + */ + getTokenPaymasterQuotes: ( + parameters: GetTokenPaymasterQuotesParameters + ) => Promise + /** + * Retrieves the supported tokens for the Biconomy Token Paymaster.. + * + * @param client - The Nexus client instance + * @returns A promise that resolves to an array of FeeQuote objects. + * + * @example + * ```typescript + * const supportedTokens = await paymaster.getSupportedTokens(nexusClient); + * console.log(supportedTokens); + * ``` + */ + getSupportedTokens: (client: NexusClient) => Promise +} + +export const bicoTokenPaymasterActions = + () => + (client: PaymasterClient): TokenPaymasterActions => ({ + /** + * Fetches paymaster quotes for ERC20 token payment options for a given UserOperation. + * + * @param userOp - The UserOperation to get paymaster quotes for + * @param client - Viem Client configured with BicoTokenPaymaster RPC methods + * @param tokenList - Array of ERC20 token addresses to get quotes for + * + * @returns A promise of {@link TokenPaymasterQuotesResponse} + * + * @example + * ```typescript + * // Configure client with paymaster RPC + * const paymasterClient = createBicoPaymasterClient({ + * paymasterUrl + * }) + * + * // Token addresses to get quotes for + * const tokenList = [ + * "0x...", // USDT + * "0x..." // USDC + * ]; + * + * // Get paymaster quotes + * const quotes = await paymasterClient.getTokenPaymasterQuotes(userOp, tokenList); + * + * // Example response: + * // { + * // mode: "ERC20", + * // paymasterAddress: "0x...", + * // feeQuotes: [{ + * // symbol: "USDT", + * // decimal: 6, + * // tokenAddress: "0x...", + * // maxGasFee: 5000000, + * // maxGasFeeUSD: 5, + * // exchangeRate: 1, + * // logoUrl: "https://...", + * // premiumPercentage: "0.1", + * // validUntil: 1234567890 + * // }], + * // unsupportedTokens: [] + * // } + * ``` + */ + getTokenPaymasterQuotes: async ( + parameters: GetTokenPaymasterQuotesParameters + ) => getTokenPaymasterQuotes(client, parameters), + /** + * Retrieves the supported tokens for the Biconomy Token Paymaster.. + * + * @param client - The Nexus client instance + * @returns A promise that resolves to an array of FeeQuote objects. + * + * @example + * ```typescript + * const supportedTokens = await paymaster.getSupportedTokens(nexusClient); + * console.log(supportedTokens); + * ``` + */ + getSupportedTokens: async (client: NexusClient) => + getSupportedTokens(client) + }) diff --git a/src/test/playground.test.ts b/src/test/playground.test.ts index 2380c8d2..bc6270e8 100644 --- a/src/test/playground.test.ts +++ b/src/test/playground.test.ts @@ -19,7 +19,6 @@ import { type NexusClient, createNexusClient } from "../sdk/clients/createNexusClient" -import { MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../sdk/constants" import type { CreateSessionDataParams, SessionData @@ -46,19 +45,21 @@ describe.skipIf(!playgroundTrue())("playground", () => { let chain: Chain let bundlerUrl: string let walletClient: WalletClient + let paymasterUrl: string + let nexusAccountAddress: Address // Test utils let publicClient: PublicClient // testClient not available on public testnets let eoaAccount: PrivateKeyAccount let recipientAddress: Address let nexusClient: NexusClient - let nexusAccountAddress: Address beforeAll(async () => { network = await toNetwork("PUBLIC_TESTNET") chain = network.chain bundlerUrl = network.bundlerUrl + paymasterUrl = network.paymasterUrl || "" eoaAccount = network.account as PrivateKeyAccount recipientAddress = eoaAccount.address From 22bc586db6aed78095940a78a2f70155cdd3a3b3 Mon Sep 17 00:00:00 2001 From: joepegler Date: Mon, 23 Dec 2024 06:54:38 +0000 Subject: [PATCH 3/6] feat: counterfactual address helper (#154) --- CHANGELOG.md | 6 ++ package.json | 2 +- src/sdk/account/toNexusAccount.ts | 2 +- .../account/utils/getCounterFactualAddress.ts | 80 +++++++++++++++++++ src/sdk/account/utils/index.ts | 1 + 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/sdk/account/utils/getCounterFactualAddress.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 200eec3d..0a014347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @biconomy/sdk +## 0.0.20 + +### Patch Changes + +- Counterfactual address helper export + ## 0.0.18 ### Patch Changes diff --git a/package.json b/package.json index 7e021980..45dcc9ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@biconomy/sdk", - "version": "0.0.19", + "version": "0.0.20", "author": "Biconomy", "repository": "github:bcnmy/sdk", "main": "./dist/_cjs/index.js", diff --git a/src/sdk/account/toNexusAccount.ts b/src/sdk/account/toNexusAccount.ts index 9fea9a74..4f475d91 100644 --- a/src/sdk/account/toNexusAccount.ts +++ b/src/sdk/account/toNexusAccount.ts @@ -288,7 +288,7 @@ export const toNexusAccount = async ( } ], functionName: "computeAccountAddress", - args: [signerAddress, index, [], 0] + args: [signerAddress, index, attesters_, attesterThreshold] })) as Address if (!addressEquals(addressFromFactory, zeroAddress)) { diff --git a/src/sdk/account/utils/getCounterFactualAddress.ts b/src/sdk/account/utils/getCounterFactualAddress.ts new file mode 100644 index 00000000..5f1a9f32 --- /dev/null +++ b/src/sdk/account/utils/getCounterFactualAddress.ts @@ -0,0 +1,80 @@ +import type { Address } from "viem" +import type { PublicClient } from "viem" +import { + MOCK_ATTESTER_ADDRESS, + RHINESTONE_ATTESTER_ADDRESS, + k1ValidatorFactoryAddress +} from "../../constants" + +/** + * Get the counterfactual address of a signer + * + * @param publicClient - The public client to use for the read contract + * @param signerAddress - The address of the signer + * @param index - The index of the account + * @param isTestnet - Whether the network is testnet + * @param attesters - The attesters to use + * @param threshold - The threshold of the attesters + * @param factoryAddress - The factory address to use + * @returns The counterfactual address + * + * @example + * ```ts + * const counterFactualAddress = await getCounterFactualAddress(publicClient, signerAddress) + * ``` + */ +export const getCounterFactualAddress = async ( + publicClient: PublicClient, + signerAddress: Address, + isTestnet = false, + index = 0n, + attesters = [RHINESTONE_ATTESTER_ADDRESS], + threshold = 1, + factoryAddress = k1ValidatorFactoryAddress +) => { + if (isTestnet) { + attesters.push(MOCK_ATTESTER_ADDRESS) + } + + return await publicClient.readContract({ + address: factoryAddress, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "eoaOwner", + type: "address" + }, + { + internalType: "uint256", + name: "index", + type: "uint256" + }, + { + internalType: "address[]", + name: "attesters", + type: "address[]" + }, + { + internalType: "uint8", + name: "threshold", + type: "uint8" + } + ], + name: "computeAccountAddress", + outputs: [ + { + internalType: "address payable", + name: "expectedAddress", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "computeAccountAddress", + args: [signerAddress, index, attesters, threshold] + }) +} diff --git a/src/sdk/account/utils/index.ts b/src/sdk/account/utils/index.ts index 64cac06c..7ba34a0a 100644 --- a/src/sdk/account/utils/index.ts +++ b/src/sdk/account/utils/index.ts @@ -4,3 +4,4 @@ export * from "./Constants.js" export * from "./getChain.js" export * from "./Logger.js" export * from "./toSigner.js" +export * from "./getCounterFactualAddress.js" From 549788bdd261cd4a40d78ba46840213422be26fd Mon Sep 17 00:00:00 2001 From: Vasile Gabriel Marian <56271768+VGabriel45@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:11:13 +0200 Subject: [PATCH 4/6] refactor: sendTokenPaymasterUserOp return type (#155) Co-authored-by: VGabriel45 Co-authored-by: Joe Pegler Co-authored-by: VGabriel45 --- .../clients/createBicoPaymasterClient.test.ts | 7 ++++-- .../clients/decorators/smartAccount/index.ts | 12 +++------ .../smartAccount/sendTokenPaymasterUserOp.ts | 25 +++++-------------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/sdk/clients/createBicoPaymasterClient.test.ts b/src/sdk/clients/createBicoPaymasterClient.test.ts index d002dcf3..f9d4d94e 100644 --- a/src/sdk/clients/createBicoPaymasterClient.test.ts +++ b/src/sdk/clients/createBicoPaymasterClient.test.ts @@ -152,7 +152,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { address: nexusAccountAddress }) - const receipt = await nexusClient.sendTokenPaymasterUserOp({ + const hash = await nexusClient.sendTokenPaymasterUserOp({ calls: [ { to: recipientAddress, @@ -162,6 +162,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { ], feeTokenAddress: baseSepoliaUSDCAddress }) + const receipt = await nexusClient.waitForUserOperationReceipt({ hash }) expect(receipt.success).toBe("true") // Get final balance @@ -268,7 +269,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { quote.feeQuotes[0].decimal ) - const receipt = await nexusClient.sendTokenPaymasterUserOp({ + const hash = await nexusClient.sendTokenPaymasterUserOp({ calls: [ { to: recipientAddress, @@ -280,6 +281,8 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { customApprovalAmount: usdcFeeAmount }) + const receipt = await nexusClient.waitForUserOperationReceipt({ hash }) + expect(receipt.success).toBe("true") const finalBalance = await publicClient.getBalance({ diff --git a/src/sdk/clients/decorators/smartAccount/index.ts b/src/sdk/clients/decorators/smartAccount/index.ts index cab9d11a..2177d9ac 100644 --- a/src/sdk/clients/decorators/smartAccount/index.ts +++ b/src/sdk/clients/decorators/smartAccount/index.ts @@ -11,11 +11,7 @@ import type { WaitForTransactionReceiptReturnType, WriteContractParameters } from "viem" -import type { - SmartAccount, - UserOperation, - WaitForUserOperationReceiptReturnType -} from "viem/account-abstraction" +import type { SmartAccount, UserOperation } from "viem/account-abstraction" import type { AnyData } from "../../../modules/utils/Types" import { type PrepareTokenPaymasterUserOpParameters, @@ -46,7 +42,7 @@ export type SmartAccountActions< * * @example * ```ts - * const receipt = await sendTokenPaymasterUserOp(client, { + * const hash = await sendTokenPaymasterUserOp(client, { * calls: [{ * to: "0x...", // Contract address * data: "0x...", // Encoded function data @@ -57,11 +53,11 @@ export type SmartAccountActions< * }) * ``` * - * @returns A promise that resolves to the user operation receipt {@link WaitForUserOperationReceiptReturnType} + * @returns A promise that resolves to the user operation hash {@link Hash} */ sendTokenPaymasterUserOp: ( args: SendTokenPaymasterUserOpParameters - ) => Promise + ) => Promise /** * Prepares a user operation with token paymaster configuration, including ERC20 token approval * diff --git a/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts b/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts index a92ea6b0..3c257a14 100644 --- a/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts +++ b/src/sdk/clients/decorators/smartAccount/sendTokenPaymasterUserOp.ts @@ -1,14 +1,9 @@ -import type { Chain, Transport } from "viem" +import type { Chain, Hash, Transport } from "viem" import type { Client } from "viem" import { maxUint256 } from "viem" import { erc20Abi } from "viem" import { type Address, encodeFunctionData } from "viem" -import { - type SmartAccount, - type WaitForUserOperationReceiptReturnType, - sendUserOperation, - waitForUserOperationReceipt -} from "viem/account-abstraction" +import { type SmartAccount, sendUserOperation } from "viem/account-abstraction" import { getAction } from "viem/utils" import { BICONOMY_TOKEN_PAYMASTER } from "../../../account/utils/Constants" import { @@ -33,7 +28,7 @@ export type SendTokenPaymasterUserOpParameters = { * * @example * ```ts - * const receipt = await sendTokenPaymasterUserOp(client, { + * const hash = await sendTokenPaymasterUserOp(client, { * calls: [{ * to: "0x...", // Contract address * data: "0x...", // Encoded function data @@ -44,7 +39,7 @@ export type SendTokenPaymasterUserOpParameters = { * }) * ``` * - * @returns A promise that resolves to the user operation receipt {@link WaitForUserOperationReceiptReturnType} + * @returns A promise that resolves to the user operation hash {@link Hash} */ export async function sendTokenPaymasterUserOp< TChain extends Chain | undefined = Chain | undefined, @@ -52,7 +47,7 @@ export async function sendTokenPaymasterUserOp< >( client: Client, args: SendTokenPaymasterUserOpParameters -): Promise { +): Promise { const { calls, feeTokenAddress, customApprovalAmount } = args const userOp = await getAction( @@ -87,13 +82,5 @@ export async function sendTokenPaymasterUserOp< "sendUserOperation" )(partialUserOp) - const receipt = await getAction( - client, - waitForUserOperationReceipt, - "waitForUserOperationReceipt" - )({ - hash: userOpHash - }) - - return receipt + return userOpHash } From e1b8f5ee2004879783a22ab830ddd81574f808f6 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Mon, 23 Dec 2024 15:15:14 +0200 Subject: [PATCH 5/6] chore: version update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45dcc9ce..215b5dd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@biconomy/sdk", - "version": "0.0.20", + "version": "0.0.21", "author": "Biconomy", "repository": "github:bcnmy/sdk", "main": "./dist/_cjs/index.js", From 87ba143ff7a72426443f14915c1422ae20f92680 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Mon, 23 Dec 2024 15:19:55 +0200 Subject: [PATCH 6/6] chore: update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a014347..ba9259a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @biconomy/sdk +## 0.0.21 + +### Patch Changes + +- Add support for token paymaster with helper functions + ## 0.0.20 ### Patch Changes