# Candide Account Abstraction > Candide Ethereum Account Abstraction developer tools for building smart accounts ## search - [Search the documentation](https://docs.candide.dev/search.md) ## account-abstraction - [EIP-7702 Delegation](https://docs.candide.dev/account-abstraction/7702/delegation.md): Add a new transaction type that allows EOAs to temporarily delegate code execution to a smart contract - [EIP-7702](https://docs.candide.dev/account-abstraction/7702/overview.md): EIP-7702 introduces a new transaction type that allows an EOA to authorize smart contract code at its own address for a transaction, enabling smart account features. - [Account Abstraction](https://docs.candide.dev/account-abstraction/intro.md): What Account Abstraction is, how ERC-4337 works, and how UserOperations, Bundlers, Paymasters, and the EntryPoint fit together. - [Ethereum Interoperability Layer (EIL): What It Means for Wallets](https://docs.candide.dev/account-abstraction/research/eil.md): Trustless cross-chain token movements without custody risks or trust assumptions, powered by Ethereum's native interoperability infrastructure. - [Safe Policies: Composable Security for Safe Accounts](https://docs.candide.dev/account-abstraction/research/safe-policies.md): Composable security policies for Safe accounts enabling role-based permissions, spending controls, and automated transaction validation. - [Safe Simulation: See What Transactions Actually Do](https://docs.candide.dev/account-abstraction/research/safe-simulation.md): Transaction simulation for Safe accounts that detects security-critical operations and verifies results cryptographically. ## forwarding-address - [Forwarding Address API Reference](https://docs.candide.dev/forwarding-address/api-reference.md): JSON-RPC API reference for the Forwarding Address service. Compute deterministic deposit addresses, activate monitoring, estimate output, and recover funds. - [Forwarding Address Integration Guide](https://docs.candide.dev/forwarding-address/integration-guide.md): Integration patterns for the Forwarding Address API. Covers TTL management, custodial patterns, and common pitfalls. - [Forwarding Address for Onramps and Deposits](https://docs.candide.dev/forwarding-address/overview.md): Let users deposit into one forwarding address from any supported chain and receive funds on their destination chain. ## instagas - [InstaGas Architecture](https://docs.candide.dev/instagas/architecture.md): InstaGas is a paymaster that enables gas sponsorship through customizable Gas Policies, supporting ERC-20 token payments as a fallback. It's future-ready, compatible with EIP-7702 and RIP-7560. - [Batch & Sponsor Transactions (via ERC-5792)](https://docs.candide.dev/instagas/batch-sponsor-transactions.md): Batch multiple actions into a single on-chain transaction, and sponsor gas for your users -- reducing costs and enhancing your app UX - [InstaGas Policy Rules Overview](https://docs.candide.dev/instagas/gas-policies.md): Understand the general, account, access, and transaction rules available to Candide gas sponsorship policies. - [Why Gas Sponsorship](https://docs.candide.dev/instagas/introduction.md): A No-Code Gas Sponsorship Paymaster for decentralized applications (dApps) - [InstaGas](https://docs.candide.dev/instagas/overview.md): Create gas policies from the Candide Dashboard or Platform API to sponsor Smart Wallet users securely. - [Real-World Applications of InstaGas Policies](https://docs.candide.dev/instagas/usecases.md): Real world applications and usecases of InstaGas Policies for Gas Sponsorship ## platform - [Gas Policy Management API](https://docs.candide.dev/platform/gas-policy-api.md): REST API reference for creating and managing team-scoped Candide gas policies, access rules, and allowed transactions. - [Platform API](https://docs.candide.dev/platform/overview.md): Manage Candide gas sponsorship policies programmatically with a team-scoped REST API. ## wallet - [Bundler](https://docs.candide.dev/wallet/abstractionkit/bundler.md): Learn how to use AbstractionKit to send ERC-4337 UserOperations to a Bundler - [Calibur Account](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md): Learn how to use Calibur7702Account for EIP-7702 smart accounts with passkey support, multi-key management, and per-key hooks - [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md): Reference for using ExternalSigner adapters with AbstractionKit accounts - [Introduction to AbstractionKit](https://docs.candide.dev/wallet/abstractionkit/introduction.md): AbstractionKit is a typescript library to easily build and send UserOperations, with first class support for Safe Accounts, Calibur Accounts, and Simple EIP-7702 Accounts - [Paymaster](https://docs.candide.dev/wallet/abstractionkit/paymaster.md): Learn how to use AbstractionKit to sponsor gas using an ERC-4337 verifying paymaster - [Safe Account V2](https://docs.candide.dev/wallet/abstractionkit/safe-account-v2.md): Learn how to offer, deploy and transact with Safe Accounts using ERC-4337 EntryPoint v0.6 - [Safe Account V3](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md): Learn how to offer, deploy and transact with Safe Accounts using ERC-4337 EntryPoint v0.7 - [Safe Account](https://docs.candide.dev/wallet/abstractionkit/safe-account.md): Learn how to offer, deploy and transact with Safe Accounts using ERC-4337 - [Safe Unified Account](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md): SDK reference for SafeMultiChainSigAccountV1 providing chain abstraction with multichain Merkle-rooted signatures for Safe Accounts - [Simple 7702 Account (EntryPoint v0.8)](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v08.md): Learn how to upgrade EOAs to Smart Accounts using Simple7702Account with EntryPoint v0.8 - [Simple 7702 Account (EntryPoint v0.9)](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v09.md): Learn how to upgrade EOAs to Smart Accounts using Simple7702AccountV09 with EntryPoint v0.9 - [Simple 7702 Account](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account.md): Learn how to upgrade EOAs to Smart Accounts with fully audited Smart Contracts - [Utilities](https://docs.candide.dev/wallet/abstractionkit/utilities.md): A set of utilities for Account Abstraction to help you focus on what matters - [API Keys & Endpoints](https://docs.candide.dev/wallet/api/authenticated-endpoints.md): Authenticated Bundler and Paymaster endpoints for production. URL format, API key setup, and rate limits. - [Public Endpoints](https://docs.candide.dev/wallet/api/public-endpoints.md): Candide provides public endpoints for our Bundler and Paymaster. No API required. Available on mainnets and testnets - [Reliability & Monitoring](https://docs.candide.dev/wallet/api/reliability.md): How Candide keeps the Bundler and Paymaster available on every supported chain, and the SLA that applies to each plan. - [Supported Networks](https://docs.candide.dev/wallet/api/supported-networks.md): Supported networks for Candide Bundler and Paymaster endpoints. - [Introduction To Candide's Voltaire](https://docs.candide.dev/wallet/bundler/erc-4337-intro.md): A new modular, developer-friendly and lightning-fast Python Bundler for Ethereum EIP-4337 Account Abstraction - [Install & Self-host Candide's Voltaire](https://docs.candide.dev/wallet/bundler/installation.md): Instructions for running Voltaire ERC-4337 Python Bundler - [Bundler API](https://docs.candide.dev/wallet/bundler/rpc-methods.md): A reference to the supported RPC methods by an ERC-4337 Bundler - [Authentication Methods in Smart Wallets](https://docs.candide.dev/wallet/guides/authentication.md): Learn how to manage user authentication with smart accounts - [Key Management with Calibur](https://docs.candide.dev/wallet/guides/calibur-key-management.md): Learn how to register, use, update, and revoke keys on a Calibur smart account - [Passkey Authentication with Calibur](https://docs.candide.dev/wallet/guides/calibur-passkeys.md): Learn how to register a WebAuthn passkey on a Calibur smart account and sign transactions with it - [Getting Started with Chain Abstraction](https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started.md): Add a new owner to your Safe on multiple chains with a single signature using chain abstraction - [Chain Abstraction with Safe Unified Account](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md): Sign once, execute on every chain. Chain abstraction for Safe Accounts using multichain signatures for seamless account sync across chains. - [Calibur Account Quickstart](https://docs.candide.dev/wallet/guides/getting-started-calibur.md): Learn how to upgrade an EOA to a Calibur smart account with EIP-7702, batch transactions, and gas sponsorship - [EIP-7702 Quickstart](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md): Learn how to upgrade an EOA to an EIP-7702-compatible Smart Account to leverage features such as batching and gas sponsorship - [Your First Gasless Smart Account Transaction](https://docs.candide.dev/wallet/guides/getting-started.md): Create a smart account and send gasless batched transactions using AbstractionKit in under 10 minutes - [Add a Social Account as a Recovery Method using Magic](https://docs.candide.dev/wallet/guides/magic.md): Learn how to offer recovery options for your users using Magic and AbstractionKit - [Multisig Smart Accounts with Safe](https://docs.candide.dev/wallet/guides/multisig.md): Create and manage multisig Safe accounts with multiple owners, dynamic threshold signing, and advanced owner management for secure shared control - [On-Chain Tracking - Adding an Identifier to Your Safe Accounts](https://docs.candide.dev/wallet/guides/onchain-identifiers.md): Learn how to add an onchain Identifier to all your ERC-4337 Safe transactions, allowing to track them on-chain - [How to Pay Gas in ERC-20 Tokens | EIP-7702](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20-eip-7702.md): Learn how to upgrade an EOA to an EIP-7702 Smart Account and let users pay gas in ERC-20s using paymasters - [Pay Gas with ERC-20 Tokens](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md): Learn how to enable users to pay gas fees with ERC-20 tokens instead of ETH - [Pay Gas in USDT on Solana](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md): Send Solana transactions from accounts with zero SOL and pay the network fee in USDT using Candide's Solana Paymaster and Tether's WDK - [Add a Google Account as a Recovery Method using Lit (Guide 1/2)](https://docs.candide.dev/wallet/guides/recovery-with-google-using-lit.md): Learn how to offer a google recovery option for your users using Lit - [How to Send a Gasless Transaction | EIP-7702](https://docs.candide.dev/wallet/guides/send-gasless-eip-7702.md): Learn how to upgrade an EOA to an EIP-7702 Smart Account and send gasless transactions - [Gas Sponsorship with Paymaster](https://docs.candide.dev/wallet/guides/send-gasless-tx.md): Learn how to sponsor gas fees using public and private gas policies - [Sign & Validate a Message with a Smart Wallet](https://docs.candide.dev/wallet/guides/signing.md): Learn how to sign messages using Safe wallet and verify its signature using EIP-1271 Signature Validation - [Simulate Transactions with Tenderly](https://docs.candide.dev/wallet/guides/simulate-transaction.md): Learn how to simulate calldata and User Operation using Tenderly to debug potential issues before sending them onchain. - [Use Turnkey with AbstractionKit](https://docs.candide.dev/wallet/guides/turnkey.md): Learn how to offer a recovery option for your users using Turnkey and AbstractionKit - [Welcome to Candide](https://docs.candide.dev/wallet/intro.md): Complete toolkit for building user-friendly Ethereum applications with Smart Accounts. Features ERC-4337 and EIP-7702 support, gas abstraction, chain abstraction, social login, and cross-chain deposit routing - [Paymaster API V3 RPC Methods](https://docs.candide.dev/wallet/paymaster/rpc-methods.md): JSON-RPC API reference for Candide's Paymaster service. Learn about sponsoring gas, and how to enable gas payments in stablecoins - [Supported Gas Payment Tokens](https://docs.candide.dev/wallet/paymaster/tokens-supported.md): Tokens accepted by Candide's Paymaster for gas payments across EVM networks and Solana. - [Enable Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md): Learn how to enable email and SMS recovery for your Safe account using Candide Guardian, a managed guardian service for secure, user-friendly account recovery - [Allowance Module v1.0.0 Migration Guide](https://docs.candide.dev/wallet/plugins/allowance-migration.md): Upgrade from Allowance Module v0.1.1 to v1.0.0. Same SDK API, new security-hardened contract address. No code changes required for most developers. - [Allowance Module SDK Reference](https://docs.candide.dev/wallet/plugins/allowance-reference.md): Complete API reference for the Safe Allowance Module methods, helpers, and advanced functions in AbstractionKit - [How to Enable Spend Permissions with the Allowance Module](https://docs.candide.dev/wallet/plugins/allowance.md): Implement recurring transactions, scheduled orders, and subscription management using the Safe Allowance Module with AbstractionKit - [Enable Recovery Module and Add Guardians](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md): Learn how to enable the Social Recovery Module and add trusted guardians for your users' Safe accounts to provide secure account recovery functionality - [How to Add Support for Passkeys Authentication](https://docs.candide.dev/wallet/plugins/passkeys.md): Utilize Passkeys for securing and backing up user Safe smart accounts through biometric authentication. - [Recover via Email / SMS](https://docs.candide.dev/wallet/plugins/recover-account-candide-guardian.md): Learn how to recover your Safe account via email or SMS verification when you've lost access to your owner keys - [Recovery Alert Subscription Guide](https://docs.candide.dev/wallet/plugins/recovery-alerts-guide.md): Learn how to set up email and SMS notifications for recovery requests to keep Safe account owners informed of any recovery attempts - [Complete Recovery Flow Guide](https://docs.candide.dev/wallet/plugins/recovery-flow-guide.md): Step-by-step guide to implementing the complete account recovery flow using guardians and the Safe Recovery Service - [SocialRecoveryModule SDK Reference](https://docs.candide.dev/wallet/plugins/recovery-module-reference.md): Complete API reference for the SocialRecoveryModule in abstractionkit, covering methods for enabling recovery, managing guardians, and executing recovery flows. - [Recovery Service SDK Reference](https://docs.candide.dev/wallet/plugins/recovery-service-sdk-reference.md): Complete API reference for the Safe Recovery Service SDK covering RecoveryByGuardian, RecoveryByCustodialGuardian, and Alerts classes for managing account recovery flows. - [Account Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md): Active onchain account recovery with guardians through a time delay for Safe Accounts. - [The Fastest Path to Production.](https://docs.candide.dev/wallet/pricing.md): Candide API pricing plans for Bundler and Paymaster. Starter, Launch, Grow, and Enterprise tiers with usage-based pricing. - [Authentication-Based Recovery API (Email / SMS)](https://docs.candide.dev/wallet/recovery/auth-api.md): API specification for Candide's Safe Recovery Service. Features email/SMS recovery, multifactor auth, and an alert system for on-chain and off-chain monitoring for active recovery. - [An overview of Safe Recovery Service API](https://docs.candide.dev/wallet/recovery/overview.md): An overview of Candide's Safe Recovery Service and how it works. Features signature aggregation, gas sponsorship, recovery request monitoring, signature storage, and auto-execution. - [Safe Recovery UX API](https://docs.candide.dev/wallet/recovery/ux-api.md): API specification for Candide's Safe Recovery Service. Features signature aggregation, gas sponsorship, recovery request monitoring, signature storage, and auto-execution. - [Solana Paymaster RPC Methods](https://docs.candide.dev/wallet/solana-paymaster/rpc-methods.md): JSON-RPC API reference for Candide's Solana Paymaster, a hosted Kora endpoint for gasless Solana transactions. Sign transactions with a fee payer and pay network fees in USDT. - [-32500 Transaction Rejected By Entrypoint Simulation](https://docs.candide.dev/wallet/technical-reference/-32500-transaction-rejected-by-entrypoint-simulation.md): Transaction rejected by entryPoint’s simulateValidation, during wallet creation or validation - [-32501 Transaction Rejected by Paymaster](https://docs.candide.dev/wallet/technical-reference/-32501-transaction-rejected-by-paymaster.md): Transaction rejected by paymaster’s validatePaymasterUserOp - [-32502 Transaction Rejected by opcode Validation](https://docs.candide.dev/wallet/technical-reference/-32502-transaction-rejected-by-opcode-validation.md): Transaction rejected because of opcode validation - [-32503 UserOperation out of time-range](https://docs.candide.dev/wallet/technical-reference/-32503-useroperation-out-of-time-range.md): Either wallet or paymaster returned a time-range, and it is already expired (or will expire soon) - [-32504 Paymaster or Aggregator Throttled or Banned](https://docs.candide.dev/wallet/technical-reference/-32504-paymaster-or-aggregator-throttled-or-banned.md): Transaction rejected because paymaster (or signature aggregator) is throttled or banned - [-32505 Paymaster or Aggregator stake or unstake-delay is too low](https://docs.candide.dev/wallet/technical-reference/-32505-stake-or-delay-too-low.md): Transaction rejected because paymaster (or signature aggregator) stake or unstake-delay is too low - [-32506 Unsupported Aggregator](https://docs.candide.dev/wallet/technical-reference/-32506-unsupported-aggregator.md): Transaction rejected because wallet specified unsupported signature aggregator - [-32507 Invalid UserOp Signature or Paymaster Signature](https://docs.candide.dev/wallet/technical-reference/-32507-invalid-siganture.md): Transaction rejected because of the wallet signature check failed or paymaster - [-32508 Paymaster Balance Too Low](https://docs.candide.dev/wallet/technical-reference/-32508-paymaster-balance-too-low.md): Transaction rejected because paymaster balance cannot cover all pending UserOperations - [-32521 Transaction Reverted](https://docs.candide.dev/wallet/technical-reference/-32521-transaction-reverted.md): Transaction was reverted during the execution phase - [-32602 Invalid UserOperation](https://docs.candide.dev/wallet/technical-reference/-32602-invalid-useroperation.md): Invalid UserOperation struct/fields - [EntryPoint Error AA10 sender already constructed](https://docs.candide.dev/wallet/technical-reference/aa10-sender-already-constructed.md): This error may occur if you attempt to create the same account multiple times - [EntryPoint Error AA13 initCode failed or OOG](https://docs.candide.dev/wallet/technical-reference/aa13-initCode-failed-or-oog.md): The initCode failed to create the account or ran out of gas. - [EntryPoint Error AA14 initCode must return sender](https://docs.candide.dev/wallet/technical-reference/aa14-initcode-must-return-sender.md): The initCode does not return the sender address. - [EntryPoint Error AA15 initCode must create sender](https://docs.candide.dev/wallet/technical-reference/aa15-initcode-must-create-sender.md): The initCode does not create an account. - [EntryPoint Error AA20 Account Not Deployed](https://docs.candide.dev/wallet/technical-reference/aa20-account-not-deployed.md): The sender of the user operation is not deployed - [EntryPoint Error AA21 Didn’t pay prefund](https://docs.candide.dev/wallet/technical-reference/aa21-didnt-pay-prefund.md): The sender did not pay the EntryPoint - [EntryPoint Error AA22 Expired or not due](https://docs.candide.dev/wallet/technical-reference/aa22-expired-or-not-due.md): Signature is outside of the specified time range - [EntryPoint Error AA23 reverted (or OOG)](https://docs.candide.dev/wallet/technical-reference/aa23-reverted-or-oog.md): The signature was rejected or it took too much gas to verify. - [EntryPoint Error AA24 Signature Error](https://docs.candide.dev/wallet/technical-reference/aa24-signature-error.md): The signature is invalid - [EntryPoint Error AA25 Invalid account nonce](https://docs.candide.dev/wallet/technical-reference/aa25-invalid-account-nonce.md): The nonce is invalid. - [EntryPoint Error AA30 Paymaster not deployed](https://docs.candide.dev/wallet/technical-reference/aa30-paymaster-not-deployed.md): The paymaster address contains no code - [EntryPoint Error AA31 Paymaster deposit too low](https://docs.candide.dev/wallet/technical-reference/aa31-paymaster-deposit-too-low.md): The paymaster is out of funds. - [EntryPoint Error AA32 Paymaster expired or not due](https://docs.candide.dev/wallet/technical-reference/aa32-paymaster-expired-or-not-due.md): The paymaster signature is outside of the specified time range. - [EntryPoint Error AA33 reverted (or OOG)](https://docs.candide.dev/wallet/technical-reference/aa33-reverted-or-oog.md): The paymaster signature was rejected or verifying the paymaster signature ran out of gas. - [EntryPoint Error AA34 Signature Error](https://docs.candide.dev/wallet/technical-reference/aa34-signature-error.md): The paymaster's signature is invalid. - [EntryPoint Error AA40 over verificationGasLimit](https://docs.candide.dev/wallet/technical-reference/aa40-over-verificationgaslimit.md): The verification gas limit was exceeded. - [EntryPoint Error AA41 too little verificationGas](https://docs.candide.dev/wallet/technical-reference/aa41-too-little-verificationgas.md): Verifying the user operation ran out of gas. - [EntryPoint Error AA50 PostOp reverted](https://docs.candide.dev/wallet/technical-reference/aa50-postop-reverted.md): An error occurred after the user operation executed. - [EntryPoint Error AA51 prefund below actualGasCost](https://docs.candide.dev/wallet/technical-reference/aa51-prefund-below-actualgascost.md): The user operation cost too much gas. - [Bundler Error Codes](https://docs.candide.dev/wallet/technical-reference/bundler-error-codes.md): In case of failure, the bundler return an error result object, with code and message. - [EVM Chain Nuances](https://docs.candide.dev/wallet/technical-reference/chain-nuances.md): This guide explores how to interact with different EVM chains with Account Abstraction, focusing on the nuances and technical differences that a developer should be aware of - [Contract Deployments](https://docs.candide.dev/wallet/technical-reference/deployments.md): All deployments to production networks for contracts used to build with Account Abstraction on Candide - [Entrypoint Error Codes](https://docs.candide.dev/wallet/technical-reference/entrypoint-error-codes.md): In case of failure, the entrypoint return an error result object, with code and message --- # Full Documentation Content [Skip to main content](#__docusaurus_skipToContent_fallback) Let users deposit from any supported chain into one address. [Read the docs](https://docs.candide.dev/forwarding-address/overview.md) [![Candide logo](/img/logo-dark.png)![Candide logo](/img/logo-dark.png)](https://docs.candide.dev/) [**Candide**](https://docs.candide.dev/)[Get Started](https://docs.candide.dev/wallet/intro.md)[Safe Account Plugins](https://docs.candide.dev/wallet/plugins/passkeys.md)[Forwarding Address](https://docs.candide.dev/forwarding-address/overview.md)[API](https://docs.candide.dev/wallet/api/supported-networks.md) [Dashboard↗](https://dashboard.candide.dev) [Company](#) * [Pricing](https://docs.candide.dev/wallet/pricing.md) * [Blog↗](https://candide.dev/blog) * [Contact Us↗](https://t.me/heymarcopolo) [Releases](https://docs.candide.dev/blog)[](https://github.com/candidelabs/)[](https://discord.gg/8q2H6BEJuf) Search # Search the documentation Open Source * [AbstractionKit](https://github.com/candidelabs/abstractionkit) * [Voltaire Bundler](https://github.com/candidelabs/voltaire) * [Smart Contracts](https://github.com/candidelabs/candide-contracts) Contact & Support * [Discord](https://discord.gg/8q2H6BEJuf) * [Telegram](https://t.me/heymarcopolo) * * [X (Twitter)](https://twitter.com/candidelabs) Status * [![OpenStatus Badge](https://candide.openstatus.dev/badge)](https://status.candide.dev/) --- # EIP-7702 Delegation EIP-7702 enhances Externally Owned Accounts (EOAs) by allowing them to perform smart contract-like operations. In this example, we show how to sign the `eip7702Auth` tuple in SimpleAccount to delegate the authorization to the smart account. For the complete example on how to upgrade an EOA to a smart account, visit [EIP-7702 Getting Started](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md). ## Setup[​](#setup "Direct link to Setup") Install dependencies ``` npm i abstractionkit ethers ``` ## Initialize Smart Account[​](#initialize-smart-account "Direct link to Initialize Smart Account") [Simple7702Account](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account.md) is a fully audited minimalist smart contract account that can be safely authorized by any EOA. It adds full support for major smart account features like batching and gas sponsorship. index.ts ``` import { Simple7702Account } from "abstractionkit"; import { Wallet } from "ethers"; const eoaDelegator = Wallet.createRandom(); const smartAccount = new Simple7702Account(eoaDelegator.address); console.log("Account address (sender): " + smartAccount.accountAddress); ``` ## Create UserOperation[​](#create-useroperation "Direct link to Create UserOperation") Calling `createUserOperation` on `Simple7702Account` will: 1. Compute the r and s values for the `eip7702Auth`. These values are part of the signature authorization tuple needed to upgrade the EOA. This is only used once, during the upgrade transaction of the EOA. 2. Determine the nonce and fetch the gas prices from the provided node RPC. 3. Estimate gas limits from the provided bundler. 4. Returns an unsigned UserOperation. index.ts ``` const bundlerUrl = process.env.BUNDLER_URL as string; const chainId = BigInt(process.env.CHAIN_ID as string); const eoaDelegatorPrivateKey = eoaDelegator.privateKey; let userOperation = await smartAccount.createUserOperation( [metaTransaction], jsonRpcNodeProvider, bundlerUrl, { eip7702Auth:{ chainId, // chainId at which the account will be upgraded, which is needed in EIP-7702 } } ); ``` ## Sign the Delegate Authorization[​](#sign-the-delegate-authorization "Direct link to Sign the Delegate Authorization") Sign the `eip7702Auth` tuple to delegate the authorization to the smart account. This is only needed during the first upgrade transaction. If the EOA is already delegated, `eip7702Auth` will be `null` and signing can be skipped. ### With a Private Key[​](#with-a-private-key "Direct link to With a Private Key") ``` import { createAndSignEip7702DelegationAuthorization } from "abstractionkit"; if (userOperation.eip7702Auth) { userOperation.eip7702Auth = createAndSignEip7702DelegationAuthorization( BigInt(userOperation.eip7702Auth.chainId), userOperation.eip7702Auth.address, BigInt(userOperation.eip7702Auth.nonce), eoaDelegatorPrivateKey ); } ``` ### With an External Signer (Callback Pattern)[​](#with-an-external-signer-callback-pattern "Direct link to With an External Signer (Callback Pattern)") Instead of passing a private key directly, you can pass a callback function for signing. This lets you use any signer: hardware wallets, WalletConnect, browser extensions, or custom signers via viem's `toAccount()`. ``` import { createAndSignEip7702DelegationAuthorization } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const account = privateKeyToAccount(eoaDelegatorPrivateKey as `0x${string}`); if (userOperation.eip7702Auth) { userOperation.eip7702Auth = await createAndSignEip7702DelegationAuthorization( BigInt(userOperation.eip7702Auth.chainId), userOperation.eip7702Auth.address, BigInt(userOperation.eip7702Auth.nonce), async (hash: string) => { // Raw hash signing. Use account.sign(), NOT signMessage() // (signMessage adds an EIP-191 prefix and produces a different recovered address) return await account.sign({ hash: hash as `0x${string}` }); } ); } ``` See the [full external signer example](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/05-external-signer.ts) on GitHub. ## Revoke Delegation[​](#revoke-delegation "Direct link to Revoke Delegation") To revoke an EIP-7702 delegation and return the EOA to a regular account, use `createRevokeDelegationTransaction`. This creates a signed transaction that delegates to `address(0)`, effectively removing the smart account code from the EOA. Revoking requires the EOA to have native tokens to pay for gas, since this is a regular Ethereum transaction (not a UserOperation). ``` import { Simple7702Account } from "abstractionkit"; const smartAccount = new Simple7702Account(eoaDelegatorPublicAddress); // Check if the EOA is currently delegated const isDelegated = await smartAccount.isDelegatedToThisAccount(nodeUrl); if (isDelegated) { const signedTransaction = await smartAccount.createRevokeDelegationTransaction( eoaDelegatorPrivateKey, nodeUrl, ); // Send the signed transaction using your preferred method (e.g., viem, ethers) } ``` See the [full revoke delegation example](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/04-revoke-delegation.ts) on GitHub. *** To run a complete upgrade example, visit [EIP-7702 Getting Started](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md). --- # EIP-7702 EIP-7702 introduces a new transaction type empowering Externally Owned Accounts (EOAs) with smart contract wallet functionality by allowing them to authorize smart contract code at their address for a transaction. This enables EOAs to perform complex operations typically reserved for smart contracts without permanently converting the EOA or changing its address. ## Key Features[​](#key-features "Direct link to Key Features") ### Batching[​](#batching "Direct link to Batching") Execute multiple operations in a single atomic transaction. A common use case is combining an ERC-20 approval followed by spending that approval. Currently, this workflow requires two separate transactions in decentralized exchanges (DEXes). Advanced batching scenarios may involve dependencies where one operation's output serves as another's input, streamlining complex workflows. ### Gas Sponsorship[​](#gas-sponsorship "Direct link to Gas Sponsorship") EIP-7702 enables accounts to pay for transactions using gas sponsorship or have third parties (such as dApps or paymasters) cover transaction fees. This facilitates various use cases: * Paying for gas in ERC-20 tokens instead of native tokens * Application operators covering transaction costs for users, eliminating the need to hold native tokens for transaction fees ### Privilege De-escalation[​](#privilege-de-escalation "Direct link to Privilege De-escalation") Users can create sub-keys or set specific permissions that are more limited than the global access of the main account. This feature improves security and control over account operations. Examples include: * Granting permission to spend ERC-20 tokens while restricting access to ETH. * Allowing a sub-key to spend only up to a certain amount per day. * Limiting interactions to specific applications, thereby reducing the risk of unauthorized access. ### Forward-compatibility with Account Abstraction[​](#forward-compatibility-with-account-abstraction "Direct link to Forward-compatibility with Account Abstraction") This EIP is designed to be highly forward-compatible with endgame account abstraction, without over-enshrining any fine-grained details of ERC-4337 or RIP-7560. Specifically: * The address that users sign can directly point to existing ERC-4337 wallet code. * The "code pathways" used are often the same as those in a fully smart contract wallet environment, making them compatible. * This approach helps avoid the issue of creating two separate code ecosystems, as they largely remain unified. While some workflows may still need workarounds, these are relatively few compared to the overall functionality. * It does not require adding any new opcodes that would become unnecessary in a future without Externally Owned Accounts (EOAs). * It allows EOAs to function like smart contracts, enabling them to be included in ERC-4337 bundles while remaining compatible with the existing EntryPoint. ## Limitations[​](#limitations "Direct link to Limitations") While EIP-7702 brings significant improvements to Ethereum EOA wallets, it's essential to understand the complete picture: * **Private Key Access Remains**: The original key always maintains admin access to the upgraded account, limiting the security benefits an EOA wallet can offer * **Portability Limitations**: Upgraded EOAs to Smart Accounts are tied to specific wallets, and transferring to a new wallet is a security-critical operation unless all wallets adopt the same smart contracts (which is unlikely) ## References[​](#references "Direct link to References") * EIP: * Discussions: * [Getting Started with EIP-7702](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md) --- # Account Abstraction Account Abstraction enables the use of smart contract accounts instead of traditional externally owned accounts (EOAs). It separates ownership from control: unlike EOAs where the private key is tightly coupled to the account, smart accounts abstract the account from the signer. ## The Problem with EOAs[​](#the-problem-with-eoas "Direct link to The Problem with EOAs") * **Gas fees create onboarding barriers.** Users must hold ETH before they can do anything. * **Common interactions require multiple transactions.** An approve-and-swap on Uniswap is two separate confirmations. * **Security is fragile.** A single seed phrase controls everything with no recovery, no spending limits, and no way to revoke access. * **Automation is impossible.** EOAs require a human to sign every transaction. ## ERC-4337: How It Works[​](#erc-4337-how-it-works "Direct link to ERC-4337: How It Works") [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) brings smart accounts to Ethereum without protocol changes. It introduces a parallel transaction flow built on these components: | Component | Role | | ----------------- | -------------------------------------------------------------------------- | | **Smart Account** | A contract that holds assets and defines its own validation logic | | **UserOperation** | A data structure packaging the user's intent, gas details, and signature | | **Bundler** | A node that collects UserOperations and submits them to the blockchain | | **EntryPoint** | A singleton contract that verifies and executes each UserOperation | | **Paymaster** | An optional contract that sponsors gas or accepts ERC-20 tokens as payment | 1. The app constructs a UserOperation and sends it to a Bundler 2. The Bundler bundles it with others and submits a transaction to the EntryPoint 3. The EntryPoint verifies the account's signature and confirms gas payment (from the account or a Paymaster) 4. The EntryPoint executes the account's calldata ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Send your first gasless transaction](https://docs.candide.dev/wallet/guides/getting-started.md) to see this flow in practice * Browse the [SDK Reference](https://docs.candide.dev/wallet/abstractionkit/introduction.md) for the full API * Read the [ERC-4337 specification](https://eips.ethereum.org/EIPS/eip-4337) for the complete standard --- # Ethereum Interoperability Layer (EIL): What It Means for Wallets *Trustless cross-chain token movements without custody risks or trust assumptions.* ## The Cross-Chain Challenge[​](#the-cross-chain-challenge "Direct link to The Cross-Chain Challenge") Users operate across multiple chains. They hold assets on Ethereum, Arbitrum, Base, Optimism, and more. Moving tokens between chains, coordinating operations across networks, and receiving funds from different ecosystems are daily requirements for wallet users. For wallet developers, this creates integration complexity. Each cross-chain solution comes with its own trust model, integration requirements, and user experience trade-offs. Users expect seamless multichain experiences, but delivering that means making decisions about which infrastructure to rely on and what trust assumptions to pass along to users. The Ethereum Foundation is building a different approach: native interoperability at the protocol level, where cross-chain token movements require no external trust assumptions. ## What is EIL?[​](#what-is-eil "Direct link to What is EIL?") The Ethereum Interoperability Layer (EIL) is Ethereum's native infrastructure for cross-chain operations. Rather than relying on external bridges or relayer networks, EIL enables trustless token movements through cryptographically enforced intents and a competitive liquidity provider network. The core principle: users express what they want to happen across chains, and the network fulfills it without ever taking custody of user funds. The entire flow is verifiable onchain, atomic, and requires no trust in any intermediary. EIL is part of Ethereum's long-term roadmap for L2 interoperability, designed to make cross-chain operations as secure and seamless as single-chain transactions. For detailed technical specifications, see the [Ethereum Foundation's EIL announcement](https://blog.ethereum.org/2025/11/18/eil) and the official [EIL Github](https://github.com/eth-infinitism/eil-contracts). ## How It Works[​](#how-it-works "Direct link to How It Works") EIL introduces Trustless Liquidity Providers (XLPs) who fulfill cross-chain intents without custodying user funds. When a user wants to move tokens from one chain to another, they express an intent: "I have 100 USDC on Arbitrum, I want 100 USDC on Base." This intent is registered onchain. XLPs observe the intent and compete to fulfill it by releasing funds to the user on Base. Once the user receives funds on the destination chain, the XLP claims the locked funds on the source chain. The critical property is atomicity. The XLP's fulfillment on the destination chain and their claim on the source chain are cryptographically linked. Either both happen or neither does. The XLP never holds user funds in custody—they provide liquidity, and the protocol ensures correctness. Verification happens through Merkle proofs that span chains, allowing contracts on one chain to verify state on another without trusting any external party. This is the same cryptographic foundation that secures Ethereum itself. ## What EIL Enables for Wallets[​](#what-eil-enables-for-wallets "Direct link to What EIL Enables for Wallets") ### Trustless Token Transfers[​](#trustless-token-transfers "Direct link to Trustless Token Transfers") A user holds 1,000 USDC on Arbitrum but needs it on Base. With EIL, they sign an intent expressing this transfer. An XLP fulfills it by releasing 1,000 USDC to the user on Base, then claims the Arbitrum funds. The user's tokens move between chains without any intermediary taking custody. No bridge operator, no relayer trust, just cryptographically enforced atomic execution. ### Cross-Chain DeFi[​](#cross-chain-defi "Direct link to Cross-Chain DeFi") A user has ETH on Optimism but wants to deposit into a yield vault on Base. With EIL, this becomes a single signed intent: "swap ETH for USDC on Optimism, move USDC to Base, deposit into vault." The entire sequence executes atomically. If any step fails, everything reverts. Users access the best yields across chains without manually bridging, swapping, and depositing in separate transactions. ### Seamless Cross-Chain Receiving[​](#seamless-cross-chain-receiving "Direct link to Seamless Cross-Chain Receiving") A user's wallet is on Base, but they're receiving a payment from someone on Polygon. With EIL, the recipient shares one address. The sender pays on Polygon, and the funds automatically appear on Base. No coordination about which chain to use, no manual bridging after receiving. The routing happens trustlessly through EIL's XLP network. ### Developer Benefits[​](#developer-benefits "Direct link to Developer Benefits") Wallet developers integrating EIL-based infrastructure don't inherit external trust assumptions. When a user asks "is this safe?", the answer is straightforward: EIL uses Ethereum's security model. There's no bridge operator to evaluate, no relayer network to trust, no custody risk to explain. Cross-chain becomes as trustworthy as single-chain. ## Candide's EIL Integration[​](#candides-eil-integration "Direct link to Candide's EIL Integration") Candide is building EIL native infrastructure for wallets, abstracting the protocol complexity into simple SDK integrations, and helper developer utilities around user UX. Some products like Safe Unified Account are designed specifically for Safe accounts, while others like Forwarding Address work with any wallet. ### Safe Unified Account[​](#safe-unified-account "Direct link to Safe Unified Account") One signature authorizes operations across all chains. Users sign once, and coordinated transactions execute across every network where they have a Safe. EIL enables the trustless cross-chain execution that makes this possible. [Learn more about Chain Abstraction with Safe Unified Account →](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md) ### Forwarding Address[​](#forwarding-address "Direct link to Forwarding Address") A single address that works on every chain. Users share one address, receive funds from any network, and assets automatically route to their preferred destination. EIL powers the trustless routing without any custody intermediaries. [Learn more about Forwarding Address →](https://docs.candide.dev/forwarding-address/overview.md) ### SDK Integration[​](#sdk-integration "Direct link to SDK Integration") Developers won't need to understand EIL internals to use these capabilities. Candide's SDK will abstract the complexity into straightforward function calls—express what you want to happen, and the SDK handles cross-chain coordination through EIL. ## Current Status[​](#current-status "Direct link to Current Status") EIL is under active development by the Ethereum Foundation. Candide's EIL-native products are being built alongside the protocol's development, with production releases planned following EIL's mainnet launch. For the latest on EIL development: * [Ethereum Foundation Blog](https://blog.ethereum.org/2025/11/18/eil) * [EIL Github](https://github.com/eth-infinitism/eil-contracts) ## Build With Us[​](#build-with-us "Direct link to Build With Us") We're looking for wallet teams interested in EIL-native cross-chain capabilities. If you're building multichain experiences and want trustless infrastructure without custody trade-offs, we want to talk. We're also looking for liquidity providers interested in becoming XLPs for stablecoin liquidity. If you're operating liquidity infrastructure and want to participate in EIL's trustless cross-chain network, reach out. Early partners will get access to our EIL integration as it develops, direct support, and the opportunity to shape how these capabilities work in production environments. Reach out directly on Telegram: **[@heymarcopolo](https://t.me/heymarcopolo)** *** *Technical questions? Reach out on [Discord](https://discord.gg/MfbK7aNWsY) or [GitHub](https://github.com/candidelabs)* --- # Safe Policies: Composable Security for Safe Accounts *Define granular security policies that validate transactions, enforce permissions, and protect against threats through onchain rules and offchain intelligence.* ## Beyond Signatures[​](#beyond-signatures "Direct link to Beyond Signatures") Safe accounts require multiple signatures for transactions, protecting against single points of failure. But signatures alone don't solve all security problems. A compromised key can still sign malicious transactions if it meets the threshold. Legitimate signers can make mistakes or approve dangerous operations without realizing it. Multisig coordination creates operational friction where routine treasury operations require the same ceremony as critical decisions. The signature requirement validates identity, not whether the action being authorized is safe or within policy bounds. Organizations work around this by keeping operational funds in hot wallets controlled by individuals, defeating the purpose of the multisig. Teams need a way to delegate specific responsibilities without opening up full account access, while maintaining security constraints that apply regardless of who signs. ## The Solution: Safe Policies[​](#the-solution-safe-policies "Direct link to The Solution: Safe Policies") Safe Policies provides composable transaction validation built on Safe's Policy Engine framework. Instead of one monolithic security check, you define multiple specialized policies that work together. Each policy focuses on a specific concern: role-based permissions, spending limits, address restrictions, delegate call protection, or offchain validation. The system routes each transaction to appropriate policies based on what the transaction is trying to do. Policies validate independently and can block execution if checks fail. This creates a defense-in-depth model where multiple security layers protect the account, and all constraints must be satisfied before execution proceeds. Policies can enforce positive permissions defining what's allowed, or negative restrictions defining what's blocked. A DeFi Manager role might have a policy authorizing specific protocol interactions, while a spending velocity policy blocks any transaction that would exceed daily limits regardless of who signs. Together, these create a complete security model that enables delegation while maintaining control. ## Policy Types[​](#policy-types "Direct link to Policy Types") | Policy Type | What It Does | Example Use | | ---------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------- | | **Role Permissions** | Authorize specific functions on specific contracts | DeFi Manager can call `deposit()` and `withdraw()` on Aave and Compound, but nothing else | | **Spending Limits** | Cap amounts per time period | Max 10,000 USDC per day, max 50,000 USDC per month | | **Velocity Limits** | Restrict transaction frequency | Max 5 transactions per hour, max 10 rebalancing operations per day | | **Address Allowlists** | Only permit transfers to approved addresses | Treasury can only pay these 15 vendor addresses | | **Address Blocklists** | Block known malicious addresses | Automatically block addresses flagged by security services | | **Position Limits** | Cap exposure to protocols or assets | Max 100,000 USD per DeFi protocol, 250,000 USD total exposure | | **Delegate Call Protection** | Time-locked allowlist or complete block | New delegates require 48-hour waiting period, or block entirely | | **Function Restrictions** | Control which functions can be called | Can interact with lending protocols but not arbitrary contracts | | **Cosigner Validation** | Offchain analysis with onchain enforcement | Transaction simulation, pattern analysis, address verification before signing | Policies are modular and composable. You select the ones relevant to your security requirements and configure them with specific parameters. Multiple policies can apply to the same Safe, creating layered protection where different concerns are validated independently. ## Use Cases[​](#use-cases "Direct link to Use Cases") ### Team Treasury with DeFi Manager[​](#team-treasury-with-defi-manager "Direct link to Team Treasury with DeFi Manager") Consider a company managing significant treasury assets with a DeFi Manager role responsible for yield optimization. The DeFi Manager needs to respond to market conditions quickly, rebalancing positions between protocols and claiming rewards without coordination overhead. But they shouldn't be able to drain the treasury or take excessive risks. **Policies configured for the DeFi Manager:** * **Function Permission Policy**: Authorized to call `deposit()`, `withdraw()`, `stake()`, `unstake()` on approved protocols (Aave, Compound, Curve). Any other function gets blocked. * **Protocol Allowlist Policy**: Can interact only with vetted protocols. Attempting to interact with unapproved contracts gets blocked with notification that new protocols require 48-hour approval. * **Position Limit Policy**: Maximum 100,000 USD exposure per protocol. Transactions that would exceed this limit get blocked. * **Daily Movement Policy**: Maximum 50,000 USD in operations per 24 hours. Once reached, further movements blocked until the window resets. **What this enables:** The DeFi Manager rebalances positions between Aave and Compound, moves 25,000 USDC from one to the other. All policy checks pass: functions are allowed, protocols are approved, position limits aren't exceeded, daily movement is within bounds. The transaction executes immediately without requiring signatures from other owners. If the DeFi Manager attempts to transfer funds to an external address, the function permission policy blocks it since `transfer()` isn't authorized. If they try to interact with a new protocol, the allowlist policy blocks it and requires the 48-hour time-locked approval process. If they attempt to move 60,000 USDC after already moving 25,000 that day, the daily movement policy blocks it. Even if the DeFi Manager role is completely compromised, an attacker cannot drain the treasury. They're constrained to legitimate operations within policy bounds, and any attempt to escalate privileges requires time delays that give the team opportunity to detect and respond. ### Individual User Protection[​](#individual-user-protection "Direct link to Individual User Protection") Individual users benefit from policies as protection layers beyond their signing key. A user sets a 1,000 USDC daily spending limit as a security measure. Even if their key is compromised through phishing or malware, the attacker cannot drain the account instantly. Combined with a velocity policy limiting transactions to 5 per hour and a cosigner policy that simulates transactions before signing, the user gets comprehensive protection. The daily limit caps total damage. The velocity limit slows down automated attacks. The cosigner blocks suspicious transactions like transfers to known scam addresses or contract interactions that would drain the wallet. These policies buy time for detection and response. A compromised key becomes much less valuable to an attacker when they can only extract 1,000 USDC per day at a constrained rate. The user likely notices the unauthorized transactions before significant loss occurs. ### Finance Operations[​](#finance-operations "Direct link to Finance Operations") Teams need to execute routine payments without multisig coordination overhead. A Finance role with policies for vendor payments can handle payroll and expenses directly while maintaining security constraints. **Configured policies:** * Vendor allowlist: can transfer only to approved addresses * Monthly budget: maximum 75,000 USDC across all payments * Transaction size limit: individual payments capped at 10,000 USDC * Token restriction: can transfer only stablecoins The Finance role executes vendor payments within these bounds. They cannot pay unauthorized recipients, exceed budget allocations, make oversized payments, or move other treasury assets. Monthly budget tracking provides automatic spending control without manual oversight. ## How It Works[​](#how-it-works "Direct link to How It Works") Safe Policies is built on Safe's Policy Engine framework. At the core is `SafePolicyGuard`, a contract that implements Safe's guard hooks. When a transaction attempts to execute, Safe calls the guard's `checkTransaction()` function before execution proceeds. The guard cannot be bypassed. SafePolicyGuard acts as a router, computing an access selector from the transaction's target address, function selector, and operation type. This selector maps to specific policy contracts. The guard calls `checkTransaction()` on the appropriate policy, passing the full transaction data. Each policy is an independent contract implementing the `IPolicy` interface. Policies maintain their own state for tracking limits, storing allowlists, or managing approvals. When called, a policy validates the transaction against its rules and either returns an approval signal or reverts with an error message explaining why the transaction was blocked. ### Cosigner Architecture[​](#cosigner-architecture "Direct link to Cosigner Architecture") The cosigner implementation has two components. The offchain service monitors pending transactions, runs validation logic including transaction simulation and pattern analysis, and signs transactions that pass all checks. The onchain cosigner policy verifies that the cosigner's signature is included with the transaction and checks it's cryptographically valid. This separation keeps complex validation logic offchain where it's flexible and cost-effective, while mandatory enforcement happens onchain where it cannot be circumvented. Users get sophisticated security checks without paying gas costs for complex onchain computation. ### Integration with Safe Unified Account[​](#integration-with-safe-unified-account "Direct link to Integration with Safe Unified Account") Policies work alongside [Safe Unified Account](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md) for multichain deployment. When configuring a Safe across multiple chains, policies can be deployed and configured on all chains with a single signature using the Merkle-rooted signature scheme. This enables consistent security posture where a DeFi Manager has the same permissions on Base, Arbitrum, and Optimism, and spending policies maintain the same caps across networks. ## Current Status[​](#current-status "Direct link to Current Status") | Component | Status | | -------------------------------- | ---------------------- | | Policy Engine framework | Safe research (exists) | | Guardrail (delegate call policy) | Safe research (exists) | | Custom policy development | Planned | | Hosted cosigner service | Planned | | AbstractionKit integration | Planned | | Policy templates and dashboard | Planned | Safe Policies packages Safe's Policy Engine framework for production use. We're building common policy implementations, hosting the cosigner service, providing SDK integration through AbstractionKit, and creating developer-friendly templates and documentation. ## Build With Us[​](#build-with-us "Direct link to Build With Us") We're looking for wallet teams who want to offer granular security controls to their users. If you're building wallets where teams need role-based treasury operations, or individual users need protection beyond their signing key, we want to talk. Early partners will get direct integration support through AbstractionKit, access to the hosted cosigner service, and the opportunity to shape which policies we prioritize based on real needs. In return, we're looking for teams who can commit engineering resources and provide concrete feedback on which security concerns matter most to their users. Reach out directly on Telegram: **[@heymarcopolo](https://t.me/heymarcopolo)** Once you message us, we'll discuss your specific security requirements and work with your team on integration. If you're still evaluating whether Safe Policies fits your roadmap, we're happy to walk through the technical details and show how policies would work in your specific context. *** *Technical questions? Reach out on [Discord](https://discord.gg/MfbK7aNWsY) or [GitHub](https://github.com/candidelabs)* --- # Safe Simulation: See What Transactions Actually Do *Transaction simulation for Safe accounts that detects security-critical operations and verifies results cryptographically.* ![Safe Simulation Transaction Preview](/img/posters/safe-simulation.png) ## The Problem: What Am I Actually Signing?[​](#the-problem-what-am-i-actually-signing "Direct link to The Problem: What Am I Actually Signing?") You're about to sign a Safe transaction. Your wallet shows some contract interaction, maybe a token approval, function calls with hex data. What is this transaction actually going to do to your Safe? Safe accounts have security-critical operations that generic wallets don't understand. You might be adding a module that can execute transactions without your signature, changing owners, modifying your multisig threshold, or executing a delegate call that runs arbitrary code with your Safe's full authority. Standard wallet interfaces show token movements and contract calls, but they don't reveal what's changing in your Safe's security model. Consider a transaction that appears to interact with a DeFi protocol. You see "approve 1000 USDC" and function calls you don't fully understand. Buried in that transaction is a call to `enableModule()` that grants a new contract permission to execute transactions from your Safe without additional signatures. If that module is malicious, your treasury is compromised the moment you sign. The approval was real, but it wasn't the dangerous part. Existing simulation tools are built for EOA wallets. They don't understand Safe's module system, owner management, or threshold changes. Teams need to see "this transaction adds module X with these permissions" or "this changes your signing threshold from 2-of-3 to 1-of-3" before they sign. Without this visibility, every signature is a trust exercise. ## The Solution: Safe-Aware Simulation[​](#the-solution-safe-aware-simulation "Direct link to The Solution: Safe-Aware Simulation") Safe Simulation understands Safe account architecture and detects security-critical operations, presenting them in clear language before you sign. The system identifies module operations, owner management changes, threshold modifications, delegate calls, and guard changes. Instead of "Call enableModule(0x1234...5678) on 0xabcd...ef00," you see "Enable new module: 0x1234...5678 (Unrecognized contract). This module will be able to execute transactions from your Safe without requiring additional signatures." Known modules like Social Recovery or Allowance are identified with context, while unknown contracts are flagged for scrutiny. Detection alone isn't sufficient. Safe Simulation uses cryptographic state proofs and multi-node consensus to verify accuracy. The state your transaction executes against is proven to match onchain reality, and multiple independent nodes must agree on the result before anything is shown to you. ## Use Cases[​](#use-cases "Direct link to Use Cases") ### Treasury Operations[​](#treasury-operations "Direct link to Treasury Operations") A DAO treasury executing DeFi strategies sees proposals that look like standard protocol interactions. Safe Simulation shows exactly what's happening: "Withdraw 50,000 USDC from Aave, deposit to Compound" and critically, what's NOT happening—no new modules, no owner changes, no delegate calls to unknown contracts. When a legitimate protocol upgrade requires approving a new contract, the simulation shows "Enable module: CompoundV3Strategy (matches known deployment)." When a phishing attack tries the same pattern, it shows "Enable module: 0xmalicious... (unrecognized contract, created 2 days ago)." ### Operational Roles[​](#operational-roles "Direct link to Operational Roles") Teams delegating operational authority through modules need to see exactly what power they're granting. A finance lead getting a spending module sees "Enable AllowanceModule for owner 0xfinance...lead with 10,000 USDC daily limit" and can verify parameters match policy before signing. When someone attempts to modify permissions, the simulation shows "Increase AllowanceModule limit from 10,000 USDC to 100,000 USDC daily"—whether legitimate or malicious, you have the information to decide. ### Individual Account Security[​](#individual-account-security "Direct link to Individual Account Security") Even individual Safe users benefit from operation visibility. When you interact with a new dApp or sign a gasless transaction from a third-party interface, you see exactly what permissions you're granting. Adding a recovery guardian shows as "Enable SocialRecoveryModule with guardians: \[addresses] and 48-hour timelock." You can verify the guardian addresses are correct before enabling recovery. ## How It Works[​](#how-it-works "Direct link to How It Works") State discovery begins by calling trace functions on RPC nodes to identify which accounts and storage slots the transaction will touch. Consensus is then established across multiple independent RPC providers, each queried for the state root at the simulation block. Unanimous agreement is required. Every provider must return an identical state root, or the simulation fails entirely. A single disagreement signals potential manipulation and halts the process. For each discovered account and storage slot, Merkle Patricia Trie proofs are retrieved from the RPC providers. These proofs cryptographically bind state data to the consensus state root. Verification happens locally on your device, computing hashes and confirming they match the agreed-upon state root. Only when every proof validates does execution proceed. With cryptographically verified state in hand, the transaction executes locally using revm, an auditable Rust EVM implementation running in your wallet's environment. This produces execution traces and state changes generated entirely on your device, eliminating reliance on remote execution services. The execution results are then analyzed against Safe's contract architecture, identifying calls to `enableModule()`, `addOwnerWithThreshold()`, `removeOwner()`, `changeThreshold()`, delegate calls, and guard modifications. Detected modules are matched against a registry of known Safe modules, surfacing context for recognized deployments while flagging unknown contracts with available metadata. *This verification approach is detailed in [Trust-Minimized Transaction Simulation Using State Proofs](https://ethresear.ch/t/trust-minimized-transaction-simulation-using-state-proofs/23857) on Ethereum Research.* ## Limitations[​](#limitations "Direct link to Limitations") Simulations assume your transaction executes in isolation at the current block. Actual onchain execution could have MEV, transaction reordering, or state changes from other transactions executing first. While every piece of state used in simulation is proven accurate through Merkle proofs, a malicious provider could theoretically hide storage slots by manipulating the initial trace call. Safe Simulation shows what operations are happening to your Safe's configuration, but it can't guarantee those operations are safe in every context or detect protocol-specific vulnerabilities. For protection against these inherent limitations, [Safe Policies](https://docs.candide.dev/account-abstraction/research/safe-policies.md) provides complementary security. While simulation shows you what a transaction will do, policies enforce rules at execution time regardless of what the simulation showed. If state changes between simulation and execution, policy constraints still apply. ## Integration[​](#integration "Direct link to Integration") Wallet developers integrate Safe Simulation through an SDK that handles detection and verification automatically. Simulation becomes a function call that takes a Safe transaction and returns detected operations with verification status. The response shows module changes, owner changes, threshold modifications, and delegate calls, along with metadata about consensus participation and proof validation. ## Current Status[​](#current-status "Direct link to Current Status") | Component | Status | | ------------------------ | -------- | | State proof verification | Complete | | Multi-node consensus | Complete | | Rust EVM Simulation | Complete | | SDK integration | Planned | The core simulation and verification is production ready. Safe specific detection logic for modules, owners, delegates, and threshold changes is complete. We're building the SDK to make integration straightforward for wallet developers. ## Build With Us[​](#build-with-us "Direct link to Build With Us") We're looking for wallet teams building on Safe who want to show users what transactions actually do before they sign. If you're working on treasury management, multisig interfaces, or any application where users need clarity about Safe operations, we want to talk. Early partners will get SDK access before public release, direct integration support, and the opportunity to shape which Safe operations we prioritize for detection. In return, we're looking for teams who can commit engineering resources to integration and provide concrete feedback on real-world usage. Reach out directly on Telegram: **[@heymarcopolo](https://t.me/heymarcopolo)** Once you message us, we'll get you set up with SDK access and work with your team on integration. If you're still evaluating whether this fits your roadmap, we're happy to walk through the technical details first. *** *Technical questions? Reach out on [Discord](https://discord.gg/MfbK7aNWsY) or [GitHub](https://github.com/candidelabs)* --- # Forwarding Address API Reference ## Protocol[​](#protocol "Direct link to Protocol") The API uses JSON-RPC 2.0 over HTTP POST with `Content-Type: application/json`. ## Authentication[​](#authentication "Direct link to Authentication") Most methods are public. Only the activation call (`account_activateForwardingAddress`) requires authentication: send `Authorization: Bearer `. Account API keys are issued by Candide on request. *** ## Discovery[​](#discovery "Direct link to Discovery") ### `forwarding_getRoutes`[​](#forwarding_getroutes "Direct link to forwarding_getroutes") Returns all routes from a given source chain with their available tokens and fees. This is the single source of truth for which destination chains and tokens are supported. Routes, tokens, and fees can change dynamically. Parameters: | Name | Type | Required | Description | | --------------- | -------- | -------- | ---------------------------------- | | `sourceChainId` | `number` | Yes | Source chain ID to get routes from | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getRoutes", "params": [{ "sourceChainId": 1 }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "routes": [ { "sourceChainId": 1, "sourceChainName": "Ethereum", "destinationChainId": 42161, "destinationChainName": "Arbitrum One", "tokens": [ { "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "decimals": 18, "destinationAddress": "0x0000000000000000000000000000000000000000", "feeBps": 50 }, { "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "symbol": "USDT", "decimals": 6, "destinationAddress": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", "feeBps": 50 } ] } ] } } ``` Token fields: | Field | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------- | | `address` | Token contract address on the source chain. `0x000...000` represents native ETH | | `symbol` | Human-readable token symbol | | `decimals` | Token decimal places (source chain) | | `destinationAddress` | Token contract address on the destination chain. Use this when verifying arrival or displaying the output token | | `feeBps` | Service fee in basis points (50 = 0.5%) | *** ### `forwarding_getMinimumAmount`[​](#forwarding_getminimumamount "Direct link to forwarding_getminimumamount") Returns the minimum deposit amount per bridge for a specific route. Use this to validate user input before calling `forwarding_estimateOutput`. Minimums are bridge-specific because each bridge has its own threshold below which deposits are not processed. Parameters: | Name | Type | Required | Description | | -------------------- | --------- | -------- | --------------------------------- | | `sourceChainId` | `number` | Yes | Source chain ID | | `destinationChainId` | `number` | Yes | Destination chain ID | | `token` | `address` | Yes | Token address on the source chain | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getMinimumAmount", "params": [{ "sourceChainId": 1, "destinationChainId": 42161, "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "bridges": { "across": { "minAmount": "500000" }, "oft": { "minAmount": "1000000" } } } } ``` | Field | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `bridges` | Object keyed by bridge identifier. Each entry contains a `minAmount` in the smallest unit of the source token. A deposit below every bridge's minimum will not be forwarded | *** ## Core[​](#core "Direct link to Core") ### `forwarding_getAddress`[​](#forwarding_getaddress "Direct link to forwarding_getaddress") Computes the deterministic CREATE2 proxy address for a given parameter set. Pure computation with no side effects. Safe to call repeatedly. The same inputs always produce the same address. Parameters: | Name | Type | Required | Description | | --------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `recipient` | `address` | Yes | Destination address that receives forwarded tokens | | `custodialWithdrawer` | `address` | Yes | Emergency recovery address that can withdraw stuck funds after a timelock. For most integrations, use the platform's address. See [custodialWithdrawer](https://docs.candide.dev/forwarding-address/integration-guide.md#the-custodialwithdrawer-role) | | `destinationChainId` | `number` | Yes | Target chain ID where assets will be delivered | | `salt` | `bytes32` | No | Optional 32-byte hex value. Use different salts to generate multiple forwarding addresses for the same recipient | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getAddress", "params": [{ "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "custodialWithdrawer": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "destinationChainId": 10 }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "address": "0xDEF456..." } } ``` *** ### `account_activateForwardingAddress`[​](#account_activateforwardingaddress "Direct link to account_activateforwardingaddress") Activates relayer monitoring for a forwarding address on specified source chains. Returns the address, active status, and TTL expiration timestamp. Requires authentication Send `Authorization: Bearer ` with this request. Keys are issued by Candide. Each account can have up to 500 active forwarding addresses; refreshing an existing address does not count against the cap. Exceeding the cap returns error `-32013`. Key behaviors: * Idempotent: calling again resets the TTL. Use this to keep an address active. * TTL-based: monitoring expires after the TTL. The address must be reactivated to resume forwarding. * Reusable: call activate again after expiration. * Same-chain forwarding: the destination chain is automatically included in the monitored set. You do not need to pass it in `sourceChainIds` to accept deposits on the destination chain itself. Parameters: | Name | Type | Required | Description | | --------------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `recipient` | `address` | Yes | Destination recipient address | | `custodialWithdrawer` | `address` | Yes | Withdrawal-authorized address. See [custodialWithdrawer](https://docs.candide.dev/forwarding-address/integration-guide.md#the-custodialwithdrawer-role) | | `destinationChainId` | `number` | Yes | Target chain ID | | `sourceChainIds` | `number[]` | Yes | Array of source chain IDs to activate monitoring on | | `salt` | `bytes32` | No | Optional salt (must match the one used in `forwarding_getAddress`) | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "account_activateForwardingAddress", "params": [{ "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "custodialWithdrawer": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "destinationChainId": 10, "sourceChainIds": [1, 42161] }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "address": "0xDEF456...", "active": true, "expiresAt": 1741132800 } } ``` | Field | Description | | ----------- | -------------------------------------------------------------------------------------------------------- | | `expiresAt` | Unix timestamp (seconds) when monitoring expires. Do not hardcode this value; the default TTL may change | *** ### `forwarding_getActivation`[​](#forwarding_getactivation "Direct link to forwarding_getactivation") Checks the activation status of a forwarding address across all source chains it was registered on. note This endpoint tracks activation status (whether the relayer is monitoring this address), not deposit or forwarding completion status. Parameters: | Name | Type | Required | Description | | --------- | --------- | -------- | ------------------------------- | | `address` | `address` | Yes | The forwarding address to check | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getActivation", "params": [{ "address": "0xDEF456..." }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "address": "0xDEF456...", "sourceChains": [ { "sourceChainId": 1, "status": "active", "expiresAt": 1741132800 }, { "sourceChainId": 42161, "status": "expired", "expiredAt": 1740528000 } ] } } ``` *** ## Estimation[​](#estimation "Direct link to Estimation") ### `forwarding_estimateOutput`[​](#forwarding_estimateoutput "Direct link to forwarding_estimateoutput") Estimates the output amount a recipient will receive after relayer and bridge protocol fees. Returns the best available bridge for the route. This method is decoupled from forwarding addresses: it takes chain IDs directly and does not require the address to be activated. Parameters: | Name | Type | Required | Description | | -------------------- | --------- | -------- | ---------------------------------------------------------------------- | | `sourceChainId` | `number` | Yes | Source chain where the deposit originates | | `destinationChainId` | `number` | Yes | Destination chain ID | | `token` | `address` | Yes | Token address on source chain (`0x000...000` for native ETH) | | `amount` | `string` | Yes | Input amount in smallest unit (e.g. `"1000000000000000000"` for 1 ETH) | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_estimateOutput", "params": [{ "sourceChainId": 1, "destinationChainId": 42161, "token": "0x0000000000000000000000000000000000000000", "amount": "1000000000000000000" }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "destinationChainId": 42161, "outputToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "outputTokenSymbol": "USDC", "bridge": "across", "outputAmount": "994005000", "relayerBotFee": "5000000", "bridgeProtocolFee": "995000" } } ``` | Field | Description | | ------------------- | ---------------------------------------------------------------------------------------------- | | `bridge` | Identifier of the bridge selected for this route (e.g. `"across"`, `"oft"`) | | `outputToken` | Token contract address on the destination chain | | `outputAmount` | Amount the recipient receives, in the smallest unit of the output token | | `relayerBotFee` | Relayer service fee in smallest unit (source token decimals). Same-chain forwards return `"0"` | | `bridgeProtocolFee` | Bridge protocol fee in smallest unit (source token decimals) | note To format `outputAmount` for display, look up the output token's decimals from [`forwarding_getRoutes`](#forwarding_getroutes) (the matching `destinationAddress` on the route's token list). *** ## Status[​](#status "Direct link to Status") Three methods return a unified, self-describing `Forward` object (see [The `Forward` object](#the-forward-object) below). Each `Forward` carries its own source context (`sourceChainId`, `sourceTxHash`, `proxyAddress`, `sourceBlockTimestamp`), so there is no wrapping "transfer" grouping type to unpack. Renamed from `forwarding_getStatus` `forwarding_getStatus` was renamed to [`forwarding_getForwardsByRecipient`](#forwarding_getforwardsbyrecipient) and its response reshaped from a per-tx `{transfers: [{bridgeStatuses}]}` grouping into a flat `{forwards: Forward[]}` list. See the [changelog](#changelog). ### `forwarding_getForwardsByRecipient`[​](#forwarding_getforwardsbyrecipient "Direct link to forwarding_getforwardsbyrecipient") Returns every confirmed forward for a recipient on a destination chain. This is the primary method for tracking deposit status: call it after activating a forwarding address to see forwards as they're picked up and delivered. Parameters: | Name | Type | Required | Description | | -------------------- | --------- | -------- | ----------------------------- | | `recipient` | `address` | Yes | Destination recipient address | | `destinationChainId` | `number` | Yes | Destination chain ID | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getForwardsByRecipient", "params": [{ "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "destinationChainId": 10 }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "forwards": [ { "forwardId": "550e8400-e29b-41d4-a716-446655440000", "route": "across", "status": "delivered", "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "sourceChainId": 1, "sourceTxHash": "0x123...abc", "proxyAddress": "0xDEF456...", "sourceBlockTimestamp": 1741132800, "destinationChainId": 10, "destinationTxHash": "0x789...def", "depositId": 12345 } ] } } ``` *** ### `forwarding_getForwardsByTx`[​](#forwarding_getforwardsbytx "Direct link to forwarding_getforwardsbytx") Returns every forward that fanned out from a single source transaction, with no recipient or destination filter. Mirrors LayerZero Scan's and Circle Iris's tx-keyed lookups. Useful when you already have the source tx hash (e.g. from watching the user's wallet) and want every forward it produced, including ones sent to other recipients in the same batch. Parameters: | Name | Type | Required | Description | | --------------- | -------- | -------- | ----------------------- | | `sourceChainId` | `number` | Yes | Source chain ID | | `sourceTxHash` | `hash` | Yes | Source transaction hash | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getForwardsByTx", "params": [{ "sourceChainId": 8453, "sourceTxHash": "0x123...abc" }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "forwards": [ { "forwardId": "550e8400-e29b-41d4-a716-446655440000", "route": "layerzero", "status": "pending", "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "sourceChainId": 8453, "sourceTxHash": "0x123...abc", "destinationChainId": 10, "guid": "0x..." } ] } } ``` An empty `forwards` array means the source tx has no confirmed forward rows yet, either because it isn't one of ours or because it hasn't been indexed yet. *** ### `forwarding_getForwardById`[​](#forwarding_getforwardbyid "Direct link to forwarding_getforwardbyid") Resolves a single forward by its opaque `forwardId` — the round-trippable UUID returned on every `Forward`. Pluck an id from a `forwarding_getForwardsByRecipient` or `forwarding_getForwardsByTx` response and poll this method for cheaper, targeted status refreshes instead of refetching the whole list. Parameters: | Name | Type | Required | Description | | ----------- | -------- | -------- | --------------------------------- | | `forwardId` | `string` | Yes | Opaque Forward UUID (`public_id`) | Request: ``` { "jsonrpc": "2.0", "id": 1, "method": "forwarding_getForwardById", "params": [{ "forwardId": "550e8400-e29b-41d4-a716-446655440000" }] } ``` Response: ``` { "jsonrpc": "2.0", "id": 1, "result": { "forward": { "forwardId": "550e8400-e29b-41d4-a716-446655440000", "route": "across", "status": "delivered", "recipient": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01", "sourceChainId": 1, "sourceTxHash": "0x123...abc", "destinationChainId": 10, "destinationTxHash": "0x789...def" } } } ``` Returns `{ "forward": null }` if the id is unknown. *** ### The `Forward` object[​](#the-forward-object "Direct link to the-forward-object") Every `Forward` is discriminated on `route` and carries a normalized `status`, regardless of which bridge protocol moved the funds. | Field | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `forwardId` | Opaque round-trippable UUID. Pass it to [`forwarding_getForwardById`](#forwarding_getforwardbyid) for a targeted refresh | | `route` | `"across"`, `"layerzero"`, `"cctp"`, or `"same_chain"` | | `status` | Normalized delivery state: `"pending"`, `"delivered"`, or `"failed"` | | `recipient` | Destination recipient address | | `sourceChainId` | Chain ID the deposit originated on | | `sourceTxHash` | Source transaction hash | | `proxyAddress` | Forwarding address the deposit was sent to (present once known) | | `sourceBlockTimestamp` | Unix timestamp (seconds) of the source block (present once known) | | `destinationChainId` | Destination chain ID (present once known) | | `destinationTxHash` | Delivery transaction hash on the destination chain (present once known) | | `refundTxHash` | Refund transaction hash, only set on Across forwards refunded to the sender | | `failureReason` | Set only when `status` is `"failed"`: `"refunded"`, `"expired"`, or `"reverted"` | | `providerSubStatus` | Raw upstream provider status string, for display or debugging | Route-specific fields, present only on the matching `route`: | Field | Route | Description | | ------------------------------------ | ----------- | ------------------------------------------------------- | | `depositId` | `across` | Across deposit ID | | `guid` | `layerzero` | LayerZero message GUID | | `sourceEid` / `destinationEid` | `layerzero` | LayerZero endpoint IDs for the source/destination chain | | `lzScanUpdatedAt` | `layerzero` | Timestamp of the last LayerZero Scan update | | `eventNonce` | `cctp` | Circle CCTP message nonce | | `sourceDomain` / `destinationDomain` | `cctp` | Circle CCTP domain IDs for the source/destination chain | | `messageHash` | `cctp` | Circle CCTP message hash | note CCTP forwards top out at `"pending"` — Circle's attestation API only confirms the message was signed, not that the destination-chain mint executed. `same_chain` forwards are always `"delivered"` immediately, since there is no bridge: delivery is the source-tx confirmation itself. *** ## Emergency Recovery[​](#emergency-recovery "Direct link to Emergency Recovery") The relayer auto-deploys the proxy contract on first deposit, so integrators typically do not need to deploy it manually. If tokens are stuck in a forwarding address (e.g. the relayer did not process them), the proxy must be deployed before funds can be withdrawn directly on-chain. There is no JSON-RPC method for deploy or withdraw. Both actions are performed on-chain from a user's wallet. Direct users to the [recovery frontend](https://forwarding-address.candidelabs.com/), which handles the deploy and withdraw transactions for the `recipient` or `custodialWithdrawer`. *** ## TypeScript Types[​](#typescript-types "Direct link to TypeScript Types") These types match the API response shapes and can serve as a reference for building your integration. ``` interface RouteToken { address: string; symbol: string; decimals: number; destinationAddress: string; // token address on destination chain feeBps: number; // basis points (50 = 0.5%) } interface Route { sourceChainId: number; sourceChainName: string; destinationChainId: number; destinationChainName: string; tokens: RouteToken[]; } interface RoutesResult { routes: Route[]; } interface MinimumAmountResult { bridges: { [bridgeName: string]: { minAmount: string }; // smallest unit, source token decimals }; } interface AddressResult { address: string; } interface ActivationResult { address: string; active: boolean; expiresAt: number; // Unix timestamp (seconds) } interface SourceChainActivation { sourceChainId: number; status: "active" | "expired"; expiresAt?: number; // present when active expiredAt?: number; // present when expired } interface ActivationStatus { address: string; sourceChains: SourceChainActivation[]; } interface EstimateResult { destinationChainId: number; outputToken: string; outputTokenSymbol: string; bridge: string; // bridge identifier selected for the route outputAmount: string; // smallest unit, output token decimals relayerBotFee: string; // smallest unit, source token decimals bridgeProtocolFee: string; // smallest unit, source token decimals } type ForwardRoute = "across" | "layerzero" | "cctp" | "same_chain"; type ForwardStatus = "pending" | "delivered" | "failed"; interface Forward { forwardId: string | null; route: ForwardRoute | string; status: ForwardStatus | string; recipient: string; sourceChainId: number; sourceTxHash: string; proxyAddress?: string; sourceBlockTimestamp?: number; // unix seconds destinationChainId?: number; destinationTxHash?: string; refundTxHash?: string; failureReason?: string; // "refunded" | "expired" | "reverted" | ... providerSubStatus?: string; // Across-specific depositId?: number; // LayerZero-specific guid?: string; sourceEid?: number; destinationEid?: number; lzScanUpdatedAt?: string; // CCTP-specific eventNonce?: string; sourceDomain?: number; destinationDomain?: number; messageHash?: string; } interface ForwardsResult { forwards: Forward[]; } ``` *** ## Changelog[​](#changelog "Direct link to Changelog") ### 2026-07-13[​](#2026-07-13 "Direct link to 2026-07-13") **Changed** * `forwarding_getStatus` has been renamed to [`forwarding_getForwardsByRecipient`](#forwarding_getforwardsbyrecipient). The response reshaped from a per-tx `{transfers: [{bridgeStatuses}]}` grouping into a flat `{forwards: Forward[]}` list of self-describing entries. See [The `Forward` object](#the-forward-object). **Added** * New [`forwarding_getForwardsByTx`](#forwarding_getforwardsbytx) endpoint. Returns every forward that fanned out from a single source transaction, with no recipient or destination filter. * New [`forwarding_getForwardById`](#forwarding_getforwardbyid) endpoint. Resolves a single forward by its opaque `forwardId`, for cheaper targeted status polling. * Every `Forward` now carries an opaque, round-trippable `forwardId` (UUID). ### 2026-05-06[​](#2026-05-06 "Direct link to 2026-05-06") **Changed** * `forwarding_activate` has been renamed to [`account_activateForwardingAddress`](#account_activateforwardingaddress) and now requires `Authorization: Bearer `. Other methods remain public. Request an account API key from Candide. **Added** * Per-account cap of 500 active forwarding addresses. Refreshing an existing address does not count against the cap. Exceeding the cap returns error code `-32013`. * New `forwarding_getStatus` endpoint. Returns confirmed forwards for a recipient on a destination chain, with per-bridge delivery statuses (`pending`, `attested`, `delivered`, `failed`, `expired`, `refunded`). Renamed to [`forwarding_getForwardsByRecipient`](#forwarding_getforwardsbyrecipient) on 2026-07-13, see above. * Same-chain forwards is now fee free. Only the gas cost is taken into account in the relayer fee. ### 2026-04-22[​](#2026-04-22 "Direct link to 2026-04-22") **Added** * New source and destination chain support: **Tempo**. Supported tokens are `USDC.e`, `USDT` (Tempo's native token), and `USDT0`. Query [`forwarding_getRoutes`](#forwarding_getroutes) with Tempo's chain ID to see accepted tokens and fees. ### 2026-04-14[​](#2026-04-14 "Direct link to 2026-04-14") **Added** * New `forwarding_getMinimumAmount` endpoint. Returns per-bridge minimum deposit amounts for a given source chain, destination chain, and token. Call this before `forwarding_estimateOutput` to validate user input. * New source and destination chain support: **BNB Chain** and **Base**. Query [`forwarding_getRoutes`](#forwarding_getroutes) with the chain's ID to see the accepted tokens and fees. * `forwarding_estimateOutput` response now includes a `bridge` field identifying which bridge was selected for the route. * `forwarding_activate` now automatically includes the destination chain in the monitored set, so same-chain forwarding works without passing it in `sourceChainIds`. **Changed** * `forwarding_getRoutes` parameter `sourceChainId` is now **required**. The endpoint returns all routes originating from that source chain. Call it once per source chain to build the full map of supported destinations. * `RouteToken` no longer includes a `minAmount` field. Minimums are now bridge-specific and returned by `forwarding_getMinimumAmount`. * `forwarding_estimateOutput` response no longer includes `outputDecimals`. To format `outputAmount`, read the output token's `decimals` from `forwarding_getRoutes` (match on the route's `destinationAddress`). **Removed** * `forwarding_estimateOutput` no longer accepts a `mode` parameter. The service selects the best available bridge automatically and returns it in the response `bridge` field. * `forwarding_setMode` has been removed. Bridging strategy is no longer user-configurable per address. * `forwarding_deploy` has been removed as a JSON-RPC method. The relayer auto-deploys the proxy on first deposit. For emergency recovery of stuck funds, users deploy and withdraw on-chain from their own wallet via the [recovery frontend](https://forwarding-address.candidelabs.com/). --- # Forwarding Address Integration Guide ## Typical Integration Flow[​](#typical-integration-flow "Direct link to Typical Integration Flow") See the [API Reference](https://docs.candide.dev/forwarding-address/api-reference.md) for details on each method. **Authentication**: The activation call ([`account_activateForwardingAddress`](https://docs.candide.dev/forwarding-address/api-reference.md#account_activateforwardingaddress)) requires `Authorization: Bearer `. Account API keys are issued by Candide; every other method is public. *** ## Keeping Addresses Alive[​](#keeping-addresses-alive "Direct link to Keeping Addresses Alive") Monitoring is TTL-based. When the activation expires, the relayer stops watching for deposits. Before presenting a forwarding address to a user, check its activation status and call `account_activateForwardingAddress` again if the TTL has expired. `account_activateForwardingAddress` is idempotent. Calling it again resets the TTL. If a deposit arrives after expiration, the funds sit in the forwarding address until either: * The address is reactivated via `account_activateForwardingAddress`, at which point the relayer picks up the deposit * The funds are recovered via the [recovery frontend](https://forwarding-address.candidelabs.com/) *** ## Multiple Addresses per Recipient[​](#multiple-addresses-per-recipient "Direct link to Multiple Addresses per Recipient") Use the optional `salt` parameter in `forwarding_getAddress` and `account_activateForwardingAddress` to generate distinct forwarding addresses for the same recipient and destination pair. Each unique salt produces a different deterministic address. This is useful for tracking individual deposits or creating per-transaction deposit addresses. Each account is capped at 500 active forwarding addresses; refreshing an existing address does not count against the cap. If you need many short-lived per-deposit addresses, plan for the cap or contact Candide. *** ## Tracking Deposit Status[​](#tracking-deposit-status "Direct link to Tracking Deposit Status") Poll [`forwarding_getForwardsByRecipient`](https://docs.candide.dev/forwarding-address/api-reference.md#forwarding_getforwardsbyrecipient) with the `recipient` and `destinationChainId` to see forwards as they're picked up and delivered. Each entry is a self-describing [`Forward`](https://docs.candide.dev/forwarding-address/api-reference.md#the-forward-object) with a normalized `status` of `pending`, `delivered`, or `failed` — you don't need to branch on which bridge (Across, LayerZero, CCTP, or same-chain) handled the deposit. If you already have the source transaction hash (e.g. from watching the user's wallet), [`forwarding_getForwardsByTx`](https://docs.candide.dev/forwarding-address/api-reference.md#forwarding_getforwardsbytx) returns every forward that transaction produced without a recipient filter. Once you have a `forwardId` from either method, poll [`forwarding_getForwardById`](https://docs.candide.dev/forwarding-address/api-reference.md#forwarding_getforwardbyid) instead of refetching the full list. There is still no webhook for deposit completion. Polling the destination chain balance directly remains a valid fallback, but the `Forward` methods give you delivery status, failure reasons, and bridge-specific tracking data (Across deposit ID, LayerZero GUID, CCTP nonce) without needing to know each provider's API. *** ## The `custodialWithdrawer` Role[​](#the-custodialwithdrawer-role "Direct link to the-custodialwithdrawer-role") The `custodialWithdrawer` is a company-controlled secure wallet that acts as a safety net for stuck funds. If funds get stuck in a forwarding address (e.g. the relayer did not process them), both the `recipient` and the `custodialWithdrawer` can withdraw directly from the deployed contract on the source chain. The recipient can always withdraw immediately. The `custodialWithdrawer` can withdraw after a timelock. This matters for every integration, not just custodial ones. If a user funds their forwarding address from an exchange or a wallet they don't control on the source chain, they may not be able to send a withdrawal transaction on that chain. Without a separate `custodialWithdrawer`, those funds are stuck permanently. | Scenario | `recipient` | `custodialWithdrawer` | Outcome | | ------------------------------- | ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------ | | Wallet or dapp (recommended) | User's address | Company's secure wallet | Company can recover stuck funds on behalf of the user after a timelock. User can always withdraw immediately | | Fully self-managed | User's address | Same as `recipient` | Only the user can recover stuck funds. If they cannot transact on the source chain, funds are unrecoverable | | Custodial (exchanges, neobanks) | End-user's address | Company's secure wallet | Company can withdraw stuck funds on behalf of the user after a timelock | caution Setting `custodialWithdrawer` to the same address as `recipient` means only the recipient can recover stuck funds. If users fund the forwarding address from an exchange or any source they don't control, and funds get stuck, there is no fallback recovery path. For most integrations, set `custodialWithdrawer` to your company's secure wallet. ### Withdrawal priority[​](#withdrawal-priority "Direct link to Withdrawal priority") * The `recipient` can always withdraw immediately from the deployed contract (no timelock) * The `custodialWithdrawer` can withdraw after a timelock period, providing a recovery path when the recipient cannot transact on the source chain ### Fund recovery[​](#fund-recovery "Direct link to Fund recovery") If funds are stuck, use the [recovery frontend](https://forwarding-address.candidelabs.com/) to recover them. *** ## Gotchas[​](#gotchas "Direct link to Gotchas") * Deposits below the per-bridge minimum are not forwarded. Query [`forwarding_getMinimumAmount`](https://docs.candide.dev/forwarding-address/api-reference.md#forwarding_getminimumamount) for the route and token to validate user input, and display the minimum clearly in your UI. * Only tokens listed in the route's `tokens` array are forwarded. Unsupported tokens sent to the address require manual recovery. * The forwarding address accepts deposits on any supported chain, including the destination chain. Do not send on unsupported chains. Check `forwarding_getRoutes` for the full list of supported chains per route. * The output token on the destination chain may have different decimals than the input token. Use the token's `decimals` from [`forwarding_getRoutes`](https://docs.candide.dev/forwarding-address/api-reference.md#forwarding_getroutes) when formatting amounts. * Cache `forwarding_getRoutes` on app load and refresh periodically. Routes, fees, and supported tokens can change. * The activation call needs an account API key (`Authorization: Bearer ...`). All other methods are public. Each account can have up to 500 active forwarding addresses; exceeding the cap returns error `-32013`. --- # Forwarding Address for Onramps and Deposits Let users deposit into one address from any supported chain and receive funds on their destination chain. [Forwarding Address Demo](https://www.youtube.com/embed/lwY-x8lTA1k)
Use Forwarding Address for onboarding, onramp, exchange withdrawal, and payment flows where the sender's source chain may not match the user's destination chain. The user receives one deposit address; Candide routes supported deposits to the destination wallet automatically. **Try it**: [forwarding-address.candidelabs.com](https://forwarding-address.candidelabs.com) ## Start Integrating[​](#start-integrating "Direct link to Start Integrating") Install the Candide skills so your AI agent knows how to wire up the Forwarding Address API. * Claude Code * Codex CLI * Other agents ``` /plugin marketplace add candidelabs/skills /plugin install candide@candide ``` Then ask: *"Integrate the Forwarding Address API."* ``` npx -y github:candidelabs/skills ``` Then ask Codex: *"Integrate the Forwarding Address API."* Paste the raw skill URL into Cursor, Windsurf, Copilot, or any chat-based agent: ``` https://raw.githubusercontent.com/candidelabs/skills/main/skills/forwarding-address/SKILL.md ``` For manual reference: [API Reference](https://docs.candide.dev/forwarding-address/api-reference.md) | [Integration Guide](https://docs.candide.dev/forwarding-address/integration-guide.md) ## Why It Matters[​](#why-it-matters "Direct link to Why It Matters") Users fund wallets from exchanges, fiat on-ramps, other wallets, and different L2s. The sender's chain often does not match the recipient's preferred chain. Forwarding Address removes that coordination. A user can receive USDT from Arbitrum into a wallet on Base, fund an L2 wallet from a CEX withdrawal network, or accept payments without asking the sender to bridge first. ## How It Works[​](#how-it-works "Direct link to How It Works") 1. Generate a deterministic Forwarding Address for a recipient and destination chain. 2. Activate monitoring for the supported source chains. 3. Show the same address to the user for deposits. 4. When funds arrive, they route automatically to the destination wallet. Funds route through self-custodial smart contracts. Integrators can configure a delayed recovery wallet as a fallback for stuck funds. ## Best Fit[​](#best-fit "Direct link to Best Fit") * Wallets that want one deposit address per user. * On-ramp and exchange funding flows where supported withdrawal chains vary. * Payment apps where sender and recipient may operate on different chains. *** *Technical questions? Reach out on [Discord](https://discord.gg/MfbK7aNWsY) or [GitHub](https://github.com/candidelabs)* --- # InstaGas Architecture ## Universal[​](#universal "Direct link to Universal") ✅ ERC-4337 ✅ EIP-7702 ✅ RIP-7560 InstaGas is an ERC-4337 compliant paymaster that works seamlessly with any Bundler or any Account Abstraction SDK. It supports gas sponsorship through customizable Gas Policies and can also handle gas payments in ERC-20 tokens as a fallback option. Future-ready, as it is compatible with [EIP-7702](https://docs.candide.dev/account-abstraction/7702/overview.md) - the proposal that extends Externally Owned Accounts (EOA) functionality to include features from smart wallets, such as transaction batching and gas sponsorship. Furthermore, it is also compatible with [RIP-7560](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7560.md), the Native Account Abstraction proposal. ## Gas Policies[​](#gas-policies "Direct link to Gas Policies") A Gas Policy is a set of customizable rules designed to sponsor gas for targeted actions. A Gas Policy can be configured as public, allowing it to be used by anyone calling the [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation) endpoint, or as private, accessible only through a unique policy ID. Gas Policies are set up for a defined time period and can include various rule types. These include account-based rules, which set limits per account, access-based rules that whitelist specific accounts, domains, or IP addresses, and transaction-based rules, which customize policies based on the dApp's contract, callable methods, input parameters, and transaction limits. The steps to set up a Gas Policy are as follows: 1. Sign up on the [dashboard](https://dashboard.candide.dev) 2. Create a new Gas Policy on a specific network. 3. Configure the policy rules. 4. Fund the policy and activate it. ![instagas-flow](/assets/images/instagas-flow-b5beb0bc658ef7a4c3465ca099ea9f89.svg) ## Smart Wallets[​](#smart-wallets "Direct link to Smart Wallets") User Operations are transactions specific to Smart Wallets. The smart wallet client constructs the User Operation based on calldata provided by the dApp, either through the Ethereum browser provider, a WalletConnect connection, or an internally integrated dApp. The client then sends a request with the User Operation to InstaGas API to determine if a matching gas policy exists. If a policy is found, the Paymaster returns a valid sponsorship signature. If no match is found, the client defaults to offering the end-user ERC-20 token options for paying gas. ## Paymaster Verifier[​](#paymaster-verifier "Direct link to Paymaster Verifier") The traditional Paymaster design sponsors gas indiscriminately, allowing anyone with access to the API key or a private sponsorship id on the client side to consume gas with minimal verification. This exposes both the Paymaster provider and its customers, who set up the endpoint, to draining attacks. We consider the use of rules and policies enforced by a Paymaster Verifier a more secure solution. The verifier checks all applicable gas policies against the submitted User Operation, offering protection against malicious actors attempting to exploit policies that were not intended or properly configured for specific use cases. This ensures that gas policies are enforced as intended. ## Paymaster Contract[​](#paymaster-contract "Direct link to Paymaster Contract") A Paymaster holds both a deposit and a stake within the global ERC-4337 EntryPoint contract. The deposit, used to cover gas fees, is distinct from the stake, which is locked to ensure security. The EntryPoint can withdraw from the Paymaster's deposit if it determines that the Paymaster has sufficient ETH to cover the gas for the operation via the handleOps method. It then calls `validatePaymasterUserOp` on the Paymaster contract to confirm the Paymaster's willingness to sponsor the operation. If `validatePaymasterUserOp` returns a 'context', the EntryPoint’s `handleOps` method subsequently calls `postOp` on the Paymaster after executing the main operation. The stake serves as protection against malicious Paymasters, ensuring that if a Paymaster behaves maliciously or attempts to launch a denial-of-service (DoS) attack on the system, its stake can be slashed as a penalty. --- # Batch & Sponsor Transactions (via ERC-5792) If you are building a Dapp, you communicate the actions to the wallet that the user performs. Instead of sending a single transaction one after the other, you can send an `array` of transactions, and the wallet will execute the bundle. ## EIP Definitions[​](#eip-definitions "Direct link to EIP Definitions") * [ERC-5792](https://eips.ethereum.org/EIPS/eip-5792) standardizes the general communication between the dApp and the wallet, with batching as the default capability. * [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) standardizes the communication of gas sponsorship capability. ![](/img/batch-transactions.png) ## Who is live?[​](#who-is-live "Direct link to Who is live?") The following apps and wallets support smart capabilities. ### Wallets[​](#wallets "Direct link to Wallets") | Wallet | Batching | Sponsor Gas | | ------------------------------------------------------------------- | -------- | --------------- | | [Ambire](https://www.ambire.com/) | ✅ | ERC-7677 | | [Coinbase Smart Wallet](https://www.base.org/builders/smart-wallet) | ✅ | ERC-7677 | | [Reown AppKit](https://reown.com/appkit) | ✅ | ERC-7677 | | [thirdweb InApp](https://thirdweb.com) | ✅ | ERC-7677 | | [Openfort](https://openfort.xyz) | ✅ | ERC-7677 | | [Safe{Wallet}](https://github.com/safe-global/safe-wallet-web) | ✅ | via its Relayer | | [Abstract Global](https://abs.xyz) | ✅ | via its Relayer | | [MetaMask Extension](https://metamask.io) | ✅ | ✖️ | ### Apps[​](#apps "Direct link to Apps") | App | Batching | Sponsor Gas | | ----------------------------------------------- | -------- | ----------- | | [PoolTogether Cabana](https://app.cabana.fi/) | ✅ | ERC-7677 | | [revoke.cash](https://revoke.cash/) | ✅ | ERC-7677 | | [Lido](https://lido.fi) | ✅ | ✖️ | | [Lifi](https://li.fi/) | ✅ | ✖️ | | [Jumper.Exchange](https://jumper.exchange/) | ✅ | ✖️ | | [PoolTogether PoolTime](https://app.cabana.fi/) | ✅ | ✖️ | | [Ekubo Exchange](https://ekubo.org/) | ✅ | ✖️ | | [Uniswap Exchange](https://app.uniswap.org) | ✅ | ✖️ | | [Vaults.fyi](https://app.vaults.fyi/) | ✅ | ✖️ | | [Spark](https://spark.fi/) | ✅ | ✖️ | | [Reserve](https://app.reserve.org/) | ✅ | ✖️ | | [Matcha](https://matcha.xyz/) | ✅ | ✖️ | | [Ethena](https://app.ethena.fi/) | ✅ | ✖️ | | [Fibrous](https://app.fibrous.finance/) | ✅ | ✖️ | | [PWN](https://app.pwn.xyz/) | ✅ | ✖️ | | [Relay](https://relay.link/bridge) | ✅ | ✖️ | ## Reference Example[​](#reference-example "Direct link to Reference Example") This example demonstrates communicating a batch of transactions and the gas sponsorship information directly to the wallet. * Live Demo [batch-and-sponsor.on-fleek.app](https://batch-and-sponsor.on-fleek.app/) * Source code [candidelabs/batch-sponsor-calls-instagas](https://github.com/candidelabs/batch-sponsor-calls-instagas) ## Quickstart with Viem/WAGMI[​](#quickstart-with-viemwagmi "Direct link to Quickstart with Viem/WAGMI") ### Installation[​](#installation "Direct link to Installation") ``` npm install viem wagmi ``` note EIP-5792 is supported beginning with viem\@2.30 and wagmi\@2.15.4 and later ### Step 1: Get Wallet Capabilities[​](#step-1-get-wallet-capabilities "Direct link to Step 1: Get Wallet Capabilities") Example using wagmi [useCapabilities](https://wagmi.sh/react/api/hooks/useCapabilities) hook * Usage * Response ``` import { useCapabilities } from 'wagmi'; const { data: capabilities } = useCapabilities({ account: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", // account address }); console.log(capabilities); ``` ``` { "1": { "paymasterService": { "supported": true }, "atomic": { "supported": true } }, "10": { "paymasterService": { "supported": true }, "atomic": { "supported": true } }, } ``` ### Step 2: Batch & Sponsor Transactions[​](#step-2-batch--sponsor-transactions "Direct link to Step 2: Batch & Sponsor Transactions") The example below uses WAGMI [useSendCalls](https://wagmi.sh/react/api/hooks/useSendCalls) hook that shows how to send two actions in a single one. Your app should fallback to sending normal transactions if the wallet doesn't support smart batching capabilities. **Gas Sponsorship** InstaGas is a no-code solution for dApps, provided that the wallet natively uses Candide's [Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md). However, this integration is not a barrier for apps wishing to sponsor gas fees for wallets that do not natively support Candide. In this step, we demonstrate how to use [ERC-7677](https://eips.ethereum.org/EIPS/eip-5792), which is an extension to ERC-5792 that standardizes the communication of gas sponsorship between the app and the wallet. 1. Create an App on [dashboard.candide.dev/api-keys](https://dashboard.candide.dev/api-keys) 2. Copy the paymaster URL link with the API key and fill in the `candidePaymasterUrl` 3. [Setup a Gas Policy](https://docs.candide.dev/instagas/overview.md#how-it-works) and customise it with [your rules](https://docs.candide.dev/instagas/gas-policies.md) ``` import { useSendCalls } from 'wagmi' import { useChainId, useSendTransaction } from 'wagmi'; import { toHex, zeroAddress } from 'viem'; const { sendCalls, data: id } = useSendCalls(); // to send batch & sponsored tx const { sendTransaction } = useSendTransaction(); // to send standard tx const chainId = useChainId(); const paymasterUrl = "https://api.candide.dev/api/v3/CHAIN_ID/API_KEY"; const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID; const tx = { to: zeroAddress as Address, value: parseGwei('0'), data: zeroAddress as Hex, }; const handleSendTx = () => { // if smart capabilities are supported, send a batched and optionally sponsored transaction try { if (!capabilities) { // Fallback to standard sendTransactions if capabilities are not available sendTransaction(tx); return; } const atomicStatus = capabilities[chainId].atomic?.status; const isAtomicSupported = atomicStatus === "supported" || atomicStatus === "ready"; if (isAtomicSupported) { sendCalls({ calls: [TEST_TX, TEST_TX], // and sponsor the tx, optionally with a sponsorshipPolicyId capabilities: { paymasterService: { url: paymasterUrl, optional: true, context: { sponsorshipPolicyId, } } }, }); } else { // if not, fallback to standard sendTransactions sendTransaction(tx); } } catch (err) { sendError(`Error sending transaction:'${err}`) console.log('Error sending transaction:', err); } }; ``` ### Step 3: Track Transaction Status[​](#step-3-track-transaction-status "Direct link to Step 3: Track Transaction Status") Example using wagmi [useCallsStatus](https://wagmi.sh/react/api/hooks/useCallsStatus) hook ``` import { useCallsStatus } from 'wagmi' const { data: callStatusData, refetch: refetchCallStatus } = useCallsStatus({ id: id?.id || '', query: { enabled: !!id, refetchInterval: (data) => data.state.data?.status === "success" ? false : 1000, }, }); ``` Optional: if you've lost track of the transaction, you can also delegate showing the status to the wallet directly using [useShowCallsStatus](https://wagmi.sh/react/api/hooks/useShowCallsStatus). ## ERC-5792 Spec[​](#erc-5792-spec "Direct link to ERC-5792 Spec") [ERC-5792](https://eips.ethereum.org/EIPS/eip-5792) allows us to standardize JSON-RPC methods for apps to communicate bundle calls to wallets. The flow is usually in this order: 1. `wallet_getCapabilities`: App checks which capabilities the wallet supports (e.g., batching, sponsorship, etc.) 2. `wallet_sendCalls`: App sends the batch transactions. 3. `wallet_getCallsStatus`: App requests to get the status (e.g., pending, confirmed, failed). 4. `wallet_showCallsStatus`: Optional method for the app to request showing the status of the transaction on the wallet interface. ### wallet\_getCapabilities[​](#wallet_getcapabilities "Direct link to wallet_getCapabilities") This RPC allows an application to request capabilities from a wallet (e.g. batch transactions, paymaster communication), without distinct discovery and permission requests. #### Invocation[​](#invocation "Direct link to Invocation") ``` { "method": "wallet_getCapabilities", "params": [address, [chainId]] } ``` #### Return[​](#return "Direct link to Return") ``` { "result": { capabilities } } ``` * Example Request * Example Response ``` ["0xd46e8dd67c5d32be8058bb8eb970870f07244567", ["0x2105", "0x14A34"]] ``` ``` { "0x0": { "flow-control": { "supported": true } }, "0x2105": { "paymasterService": { "supported": true }, "sessionKeys": { "supported": true } }, "0x14A34": { "auxiliaryFunds": { "supported": true } } } ``` ### wallet\_sendCalls[​](#wallet_sendcalls "Direct link to wallet_sendCalls") Requests that the wallet deliver a group of function calls on-chain from the user’s wallet. #### Invocation[​](#invocation-1 "Direct link to Invocation") ``` { "method": "wallet_sendCalls", "params": [{ chainId, from, calls }] } ``` #### Return[​](#return-1 "Direct link to Return") ``` { "result": { transactionHash } } ``` * Example Request * Example Response ``` [ { "version": "1.0", "from": "0x0000000000000000000000000000000000000000", "chainId": "0x01", "atomicRequired": true, "calls": [ { "to": "0x0000000000000000000000000000000000000000", "value": "0x", "data": "0x" }, { "to": "0x0000000000000000000000000000000000000000", "value": "0x", "data": "0x" } ], "capabilities": { "paymasterService": { "url": "https://...", "optional": true, "context": { "sponsorshipPolicyId": "123" } } } } } ] ``` ``` { "jsonrpc": "2.0", "id": 0, "result": "0xe67asds.." } ``` ### wallet\_getCallsStatus[​](#wallet_getcallsstatus "Direct link to wallet_getCallsStatus") Returns the status of a bundle that was sent via `wallet_sendCalls`. The identifier of the bundle is the value returned from the `wallet_sendCalls` RPC. #### Invocation[​](#invocation-2 "Direct link to Invocation") ``` { "method": "wallet_getCallsStatus", "params": [transactionHash] } ``` #### Return[​](#return-2 "Direct link to Return") ``` { "result": { calls } } ``` * Example Request * Example Response ``` { "jsonrpc": "2.0", "id": 0, "result": "0xe67asds.." } ``` ``` { "version": "1.0", "chainId": "0x01", "id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "status": 200, "atomic": true, "receipts": [ { "logs": [ { "address": "0xa922b54716264130634d6ff183747a8ead91a40b", "topics": [ "0x5a2a90727cc9d000dd060b1132a5c977c9702bb3a52afe360c9c22f0e9451a68" ], "data": "0xabcd" } ], "status": "0x1", "blockHash": "0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a", "blockNumber": "0xabcd", "gasUsed": "0xdef", "transactionHash": "0x9b7bb827c2e5e3c1a0a44dc53e573aa0b3af3bd1f9f5ed03071b100bb039eaff" } ] } ``` ### wallet\_showCallsStatus[​](#wallet_showcallsstatus "Direct link to wallet_showCallsStatus") Requests that the wallet present UI showing the status of the given bundle. This allows apps to delegate the display of the function call status to the wallet, which can most accurately render the current status of the bundle. This RPC is intended to replace the typical user experience of a dapp linking to a block explorer for a given transaction hash. #### Invocation[​](#invocation-3 "Direct link to Invocation") ``` { "method": "wallet_showCallsStatus", "params": [transactionHash] } ``` #### Return[​](#return-3 "Direct link to Return") ``` { "result": call } ``` * Example Request * Example Response ``` { "jsonrpc": "2.0", "id": 0, "result": "0xe67asds.." } ``` ``` { status: "CONFIRMED" receipt: { logs: [..] success: true blockHash: "0x84b9f48e922c6b500fa07a1e94144cb1452f2466199fc726e807d31bae6b4e52" blockNumber: "0x7dbc3c" gasUsed: "0x25c8e" transactionHash: "0xcb8c43372800586bffc7521e0ca09f8828a87d4f0003f36ea68f9cbcf3e229f9" } } ``` --- # InstaGas Policy Rules Overview InstaGas provides customizable rules for gas policies. These rules are organized into four categories: * [General Rules](https://docs.candide.dev/instagas/gas-policies.md#general-rules) * [Account Rules](https://docs.candide.dev/instagas/gas-policies.md#account-rules) * [Access Rules](https://docs.candide.dev/instagas/gas-policies.md#access-rules) * [Transaction Rules](https://docs.candide.dev/instagas/gas-policies.md#transaction-rules) ## General Rules[​](#general-rules "Direct link to General Rules") * **Effective Date Range**: Specify when the gas policy should be active. This setting is ignored if the policy is inactive. * **Active State**: Toggle to activate or deactivate the policy at any time. * **Private or Public**: Choose between public and private gas policies: * **Public Gas Policies**: Set up by dApps for wallet consumption without requiring a sponsorship policy ID. * **Private Gas Policies**: Set up by wallets, requiring a sponsorship policy ID. Used when no public gas policy matches. ![](/img/instagas/instagas-general-rules.png) ## Account Rules[​](#account-rules "Direct link to Account Rules") * **Total Max per Account**: Set an account spending limit. * Example: "Each account has a total sponsorship gas limit of 0.5 ETH." * **Max per UserOperation**: Limit transaction sponsorship. * Example: "Each UserOperation can be sponsored up to 0.0001 ETH." * **Rate Limit Per Account**: Define rate and period constraints. * Example: "Each account is allowed x sponsorships per y days." ![](/img/instagas/instagas-account-rules.png) ## Access Rules[​](#access-rules "Direct link to Access Rules") * **Account Whitelist**: Create an allowlist of accounts eligible for the gas policy. If empty, all accounts are allowed. * Example: "Only sponsor transactions from sender `0xabcd...`." * **Domains Whitelist**: Create an allowlist of origins eligible for the gas policy. If empty, all origins are allowed. * Example: "Only sponsor transactions originating from `https://app.uniswap.com`." * Partial wildcards are supported for subdomains, but the protocol scheme is required. `https://*.candide.dev` is accepted; `*.candide.dev` is not. * **IP Whitelists**: Create an allowlist of IPs eligible for the gas policy. If empty, all IPs are allowed. * Example: "Only sponsor transactions originating from IP `148.156.123.63`." ![](/img/instagas/instagas-access-rules.png) ## Transaction Rules[​](#transaction-rules "Direct link to Transaction Rules") Transaction rules limit gas sponsorship to specific smart contract interactions, callable methods, input parameters, and constraints. * **Contract Address**: Limit gas sponsorship to a specific smart contract address. * Example: "Only sponsor transactions interacting with smart contract `0x123`." * **Method**: Limit gas sponsorship to a specific callable method on the smart contract. * Example: "Only sponsor when the transaction calls the `swap` method." * **Parameters Constraints**: Limit gas sponsorship based on method parameters and constraints. * Example: "Only sponsor when `tokenIn` amount is greater than or equal to 1 ETH." * **Batch Transaction**: Design rules to sponsor transaction batches, such as an ERC-20 token approval followed by a contract deposit. * Example: "Only sponsor if the transaction includes both ERC-20 token `approval` and `deposit` calls." ![](/img/instagas/instagas-transaction-rules.png) ### Example: Uniswap Swap Transaction[​](#example-uniswap-swap-transaction "Direct link to Example: Uniswap Swap Transaction") When you want to make a swap on Uniswap, you can make a call to the `swap` function with specific parameters. For instance: `swap(400, usdt, weth)` In this example: * `swap` is the function being called, which initiates the swap transaction. * `(400, usdt, weth)` are the parameters being passed to the `swap` function, which specify the details of the swap: * `400` is the amount of tokens to be swapped. * `usdt` is the token being swapped (in this case, USDT). * `weth` is the token being swapped for (in this case, WETH). For each function, you can specify a list of parameters, and each parameter can have a constraint. For example, you can assign constraints to the parameters in the `swap` function as follows: * `amount`: greater than 350 (e.g., `amount > 350`) * `tokenIn`: equal to USDT (e.g., `tokenIn == usdt`) * `tokenOut`: equal to WETH (e.g., `tokenOut == weth`) By specifying these constraints, you can control the behavior of the swap transaction and ensure that it is executed according to your requirements. For instance, you can ensure that the swap transaction only occurs if the amount being swapped is greater than 350, and if the tokens being swapped are USDT and WETH. ## Policy configuration example[​](#policy-configuration-example "Direct link to Policy configuration example") Policies can be created and edited in the [Dashboard](https://dashboard.candide.dev) or through the [Platform API](https://docs.candide.dev/platform/overview.md). The example below is a policy creation body using the public API field names. For request validation, update semantics, and response fields, use the [Gas Policy API reference](https://docs.candide.dev/platform/gas-policy-api.md) as the canonical source. ``` { "name": "Onboarding sponsorship", "chainId": 10, "general": { "enabled": true, "private": false, "startDate": 1751500800000, "endDate": 1754179200000 }, "accountRules": { "totalMax": "0x6f05b59d3b20000", "maxPerOp": "0x5af3107a4000", "rate": 3, "ratePeriod": 86400 }, "accessRules": { "accounts": [ { "value": "0xA1b2C3d4E5f6A7b8C9d0E1f2A3b4C5d6E7f8A9b0", "label": "Beta tester" } ], "origins": [ { "value": "https://*.example.com", "label": "Our app" } ], "ips": [] }, "transactions": [ { "to": "0xE592427A0AEce92De3Edee1F18E0157C05861564", "selector": "0x414bf389", "abi": "[{\"type\":\"function\",\"name\":\"exactInputSingle\",\"constant\":false,\"payable\":true,\"inputs\":[{\"type\":\"tuple\",\"name\":\"params\",\"components\":[{\"type\":\"address\",\"name\":\"tokenIn\"},{\"type\":\"address\",\"name\":\"tokenOut\"},{\"type\":\"uint24\",\"name\":\"fee\"},{\"type\":\"address\",\"name\":\"recipient\"},{\"type\":\"uint256\",\"name\":\"deadline\"},{\"type\":\"uint256\",\"name\":\"amountIn\"},{\"type\":\"uint256\",\"name\":\"amountOutMinimum\"},{\"type\":\"uint160\",\"name\":\"sqrtPriceLimitX96\"}]}],\"outputs\":[{\"type\":\"uint256\",\"name\":\"amountOut\"}]}]", "parameters": [ { "index": "0;5", "operator": "$gte", "value": "0x0f4240" } ] } ] } ``` ### General[​](#general "Direct link to General") | field | type | description | | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- | | `enabled` | `boolean` | Whether the policy is active. Disabled policies never sponsor, regardless of dates. | | `private` | `boolean` | Private policies require the wallet to pass the policy ID when requesting sponsorship. Public policies apply without one. | | `sponsorshipPolicyId` | `string` | Assigned by InstaGas at creation. Wallets reference it as the sponsorship policy ID for private policies. | | `startDate` | `number` | Unix timestamp in milliseconds. Sponsorship starts at this time. | | `endDate` | `number` | Unix timestamp in milliseconds. Sponsorship stops at this time. | ### Account[​](#account "Direct link to Account") | field | type | description | | ------------ | ------------------ | ---------------------------------------------------------------------------------------------- | | `totalMax` | `string` (hex wei) | Lifetime sponsorship limit per account. `0x6f05b59d3b20000` is 0.5 ETH. | | `maxPerOp` | `string` (hex wei) | Maximum sponsored gas cost for a single UserOperation. `0x5af3107a4000` is 0.0001 ETH. | | `rate` | `number` | Number of sponsored UserOperations allowed per `ratePeriod`, per account. | | `ratePeriod` | `number` | Rate limit window in seconds. `86400` with `rate: 3` means 3 sponsorships per account per day. | Amounts are entered in ETH in the dashboard and stored as hex-encoded wei. ### Access[​](#access "Direct link to Access") | field | type | description | | ---------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `accounts` | `{value, label}[]` | Sender addresses eligible for sponsorship. Empty allows all senders. | | `origins` | `{value, label}[]` | Web origins eligible for sponsorship. Empty allows all origins. Subdomain wildcards are supported, and the scheme is required: `https://*.example.com` is valid, `*.example.com` is not. | | `ips` | `{value, label}[]` | IP addresses eligible for sponsorship. Empty allows all IPs. | ### Transactions[​](#transactions "Direct link to Transactions") Each entry describes one allowed contract call. Constraints within an entry must all pass for the call to qualify. | field | type | description | | ------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `to` | `string` (address) | The contract the call must target. | | `selector` | `string` (4-byte hex) | The function selector the call must invoke. | | `abi` | `string` | A JSON-stringified ABI array containing the complete function fragment, used to decode call parameters for constraint checks. | | `parameters` | `constraint[]` | Constraints on the decoded call parameters. Empty sponsors any call to this function. | Each parameter constraint: | field | type | description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `index` | `string` | Argument index. Use semicolons for nested tuples: `0;5` targets the sixth field of the first argument. | | `operator` | `string` | One of the operators below. | | `value` | `string` | The value the decoded argument is compared against. Encode `uint` and `bytes` values as `0x`-prefixed hexadecimal strings. Token amounts are in the token's smallest unit. | Supported operators: | operator | meaning | | ------------- | --------------------- | | `$eq` | Equals | | `$gt` | Greater than | | `$gte` | Greater than or equal | | `$lt` | Less than | | `$lte` | Less than or equal | | `$startsWith` | Value prefix match | The comparison operators apply to numeric parameters. Use `$eq` for addresses and exact matches. --- # Why Gas Sponsorship A no-code platform enabling applications on Ethereum, Layer 2 networks, and EVM-compatible chains to define gas policies for directly sponsoring Smart Wallet end-user gas fees **without API calls or code integration on the dApp client.** While a standard for Paymaster RPC endpoints is part of the solution, the main benefits are lost if dApps still need to write code to sponsor gas with different smart contract wallet implementations. InstaGas receives transactions from end users' Smart Wallets and checks for applicable gas policies set up by sponsors. If a matching policy is found, InstaGas returns a valid sponsorship signature. Gas policies are configured using customizable rules ranging from generic to account-based, access-based, and transaction-specific rules. Simple gas policies require only a few basic rules, while more complex ones may involve transaction rules that scan for contract interactions, input parameters, and predefined limits. InstaGas is secured against draining attacks by design, provided the gas policy is properly configured to enforce desired behavior. ![candide-instagas-logo](/assets/images/instagas-logo-4d4e68c4539f4aada36f68f86ac9a78c.png) ## The Problem[​](#the-problem "Direct link to The Problem") Without a Paymaster, anyone sending an Ethereum transaction must possess ETH to cover gas fees, forcing new users to complete KYC processes and purchase ETH before accessing any dApp. With growing demand for better user experiences and the introduction of the Account Abstraction standard, Smart Wallets have emerged to cover gas fees on behalf of users. However, this shift places dApps in a challenging position as they lose control over user onboarding processes. To sponsor gas for end-user Smart Wallets, dApps have had to implement complex, custom code for each wallet integration, with some resorting to offering their own embedded wallets to enhance user experience. --- # InstaGas InstaGas lets you sponsor gas fees for end users with customized policies tailored to specific smart contracts and use cases. Configure policies interactively in the Dashboard or automate them with the [Platform API](https://docs.candide.dev/platform/overview.md). ![candide-instagas-logo](/assets/images/instagas-logo-4d4e68c4539f4aada36f68f86ac9a78c.png) ## Who is This For?[​](#who-is-this-for "Direct link to Who is This For?") InstaGas is designed for apps that want to simplify user experience by abstracting gas costs **without complex wallet integration**. InstaGas is ideal for: * DeFi exchanges * Lending protocols * Web3 games * NFT collections and marketplaces * And more For more examples, visit our [Use Cases Categories](https://docs.candide.dev/instagas/usecases.md) page. ## How It Works[​](#how-it-works "Direct link to How It Works") 1. Set up a gas policy in the [Dashboard](https://dashboard.candide.dev) or with the [Gas Policy API](https://docs.candide.dev/platform/gas-policy-api.md) 2. Make the policy `public` by toggling the privacy rule in General Rules settings 3. Customize your policy with rules aligned with your app's needs 4. Top up your team balance and transfer funds to the policy Once your policy is active: * It becomes discoverable by all wallets using Candide's [Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md) * Any transaction matching the policy's rules will have gas fees sponsored * Monitor policy performance through the Analytics and UserOperation tabs ## Rules Overview[​](#rules-overview "Direct link to Rules Overview") InstaGas provides customizable rules for gas policies, categorized into four sections: 1. General Rules 2. Account Rules 3. Access Rules 4. Transaction Rules Learn more about [Gas Policies and Rules](https://docs.candide.dev/instagas/gas-policies.md) on the next page. To create, update, or delete policies programmatically, follow the [Platform API quickstart](https://docs.candide.dev/platform/overview.md). ## Support[​](#support "Direct link to Support") Have questions? We're here to help. [Get in touch](https://cal.com/candidelabs/30mins) with the founders for personalized guidance on setting up a gas policy. --- # Real-World Applications of InstaGas Policies InstaGas policies enable diverse possibilities across sectors where onboarding, marketing, incentives, and privacy are critical. InstaGas offers use cases across multiple industries: | Use Case | Description | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeFi Exchanges** | Cover gas fees for token swaps or liquidity provision. This is useful for marketing campaigns, new user onboarding, or high-volume traders. | | **NFT Drops** | Sponsor gas fees for fresh mints, even during high network congestion. This helps your collection find more participants and reduces all friction for collectors. | | **Lending Protocols** | Sponsor key actions such as loan creation, collateral adjustments, or repayments, by setting a minimum for deposits and withdrawals. | | **L2 Rollups and EVM Chains** | Sponsor bridging gas fees from the origin chain to your destination chain using your canonical bridge or third parties. | | **Liquid staking** | Liquid stacked tokens and their wrapped versions leave users without options to make a single transaction after staking all their native tokens. | | **Web3 Games** | Gas-free in-game transactions, allowing players to mint NFTs, transfer assets, or make a minimum purchase without needing to manage native tokens. | | **Charity and Donations** | Nonprofits can sponsor gas fees for donors. A minimum donation threshold can be set to apply for sponsorship. | | **DAO Governance Participation** | Sponsor gas fees for governance actions like voting, submitting proposals, or delegating tokens. | | **Subscription Services** | Any recurring actions, such as Dollar Cost Averaging (DCA), can automate and cover recurring transaction fees for users. | | **Private Actions** | By covering gas fees, users don't need to complete KYC to purchase ETH. It also enables ETH-less token withdrawals sent to stealth addresses. | --- # Gas Policy Management API Use this team-scoped REST API to create, inspect, update, and delete Candide gas policies programmatically. * Base URL: `https://platform-api.candide.dev` * Content type: `application/json` * Authentication: `Authorization: Bearer ` * OpenAPI: [download the complete OpenAPI 3.1 specification](https://docs.candide.dev/assets/files/platform-api-dc5d811b47ffe5ce3d700d185401d5a2.yaml) Management keys begin with `mk_` and belong to one team. Keep them in trusted server-side environments. See the [Platform API overview](https://docs.candide.dev/platform/overview.md) for a safe first integration. ## Endpoint summary[​](#endpoint-summary "Direct link to Endpoint summary") | Method | Path | Required scope | Success | | -------- | ---------------------------------------------------------- | -------------------- | ------- | | `GET` | `/v1/me` | Valid management key | `200` | | `GET` | `/v1/gas-policies` | `gas-policies:read` | `200` | | `POST` | `/v1/gas-policies` | `gas-policies:write` | `201` | | `GET` | `/v1/gas-policies/{policyId}` | `gas-policies:read` | `200` | | `PATCH` | `/v1/gas-policies/{policyId}` | `gas-policies:write` | `200` | | `DELETE` | `/v1/gas-policies/{policyId}` | `gas-policies:write` | `200` | | `PATCH` | `/v1/gas-policies/{policyId}/access-rules` | `gas-policies:write` | `200` | | `PUT` | `/v1/gas-policies/{policyId}/transactions` | `gas-policies:write` | `200` | | `POST` | `/v1/gas-policies/{policyId}/transactions` | `gas-policies:write` | `201` | | `DELETE` | `/v1/gas-policies/{policyId}/transactions/{transactionId}` | `gas-policies:write` | `200` | ## Conventions[​](#conventions "Direct link to Conventions") * `policyId` and `transactionId` are opaque strings. * Policy list pages use a zero-based `page` index. * Dates are Unix timestamps in milliseconds. * `ratePeriod` is measured in seconds. * Gas spending limits and billing amounts are hexadecimal wei strings. * Unknown, deleted, and cross-team resources return `404`. * A validation error returns `422` with field paths in `error.details`. ## Identity[​](#identity "Direct link to Identity") ### Get management key information[​](#get-management-key-information "Direct link to Get management key information") `GET /v1/me` Returns the team and scopes associated with the calling key. ``` curl --request GET \ --url https://platform-api.candide.dev/v1/me \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` ``` { "keyId": "key_123", "teamId": "team_123", "scopes": ["gas-policies:read", "gas-policies:write"] } ``` Possible scopes are `gas-policies:read` and `gas-policies:write`. This endpoint returns `401` for a missing or invalid key and `500` for an unexpected server error. ## Gas policies[​](#gas-policies "Direct link to Gas policies") ### List gas policies[​](#list-gas-policies "Direct link to List gas policies") `GET /v1/gas-policies` | Query parameter | Type | Default | Description | | --------------- | -------------------- | ------- | ---------------------------------------- | | `chainId` | integer | — | Return policies for one chain ID | | `enabled` | boolean | — | Return only enabled or disabled policies | | `page` | non-negative integer | `0` | Zero-based page index | | `pageSize` | integer | `25` | One of `10`, `25`, `50`, or `100` | ``` curl --request GET \ --url "https://platform-api.candide.dev/v1/gas-policies?chainId=11155111&enabled=false&page=0&pageSize=25" \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` ``` { "items": [ { "id": "policy_123", "name": "Onboarding sponsorship", "chainId": 11155111, "enabled": false, "private": true, "createdAt": 1784678400000, "billing": { "currency": "ETH", "balance": "0x0", "lockedBalance": "0x0", "available": "0x0" } } ], "total": 1, "page": 0, "pageSize": 25 } ``` Returns `400` for invalid filters or pagination and `403` when the key lacks `gas-policies:read`. ### Create a gas policy[​](#create-a-gas-policy "Direct link to Create a gas policy") `POST /v1/gas-policies` | Field | Type | Required | Description | | -------------- | ------------------------------------------- | -------- | ------------------------------------------ | | `name` | string | Yes | Display name, 3–50 characters | | `chainId` | integer | Yes | Chain enabled for the calling team | | `general` | [General input](#general-input) | No | General policy settings | | `accountRules` | [Account rules input](#account-rules-input) | No | Per-account limits | | `accessRules` | [Access rules input](#access-rules-input) | No | Initial account, origin, and IP allowlists | | `transactions` | [Transaction input](#transaction-input)\[] | No | Initial ordered transaction list | ``` curl --request POST \ --url https://platform-api.candide.dev/v1/gas-policies \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Onboarding sponsorship", "chainId": 11155111, "general": { "enabled": false, "private": true }, "accountRules": { "rate": 3, "ratePeriod": 86400, "totalMax": "0x6f05b59d3b20000", "maxPerOp": "0x5af3107a4000" }, "accessRules": { "origins": [ { "value": "https://app.example.com", "label": "Production app" } ] } }' ``` Returns `201` with a complete [policy detail](#policy-detail). A plan limit on mainnet policies returns `403`; invalid field values return `422`. ### Get a gas policy[​](#get-a-gas-policy "Direct link to Get a gas policy") `GET /v1/gas-policies/{policyId}` ``` curl --request GET \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` Returns `200` with a [policy detail](#policy-detail), `403` without the read scope, or `404` when the policy is unavailable to the calling team. ### Update a gas policy[​](#update-a-gas-policy "Direct link to Update a gas policy") `PATCH /v1/gas-policies/{policyId}` Provide at least one of `name`, `general`, `accountRules`, or `accessRules`. Omitted fields remain unchanged. Fields inside `general` and `accountRules` are merged over their current values. Each whitelist included in `accessRules` replaces that complete whitelist. ``` curl --request PATCH \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Production onboarding", "general": { "enabled": true }, "accountRules": { "rate": 5, "ratePeriod": 86400 } }' ``` Returns `200` with the updated [policy detail](#policy-detail). Use the dedicated access-rules endpoint for additive changes and the transactions endpoints for transaction changes. ### Delete a gas policy[​](#delete-a-gas-policy "Direct link to Delete a gas policy") `DELETE /v1/gas-policies/{policyId}` ``` curl --request DELETE \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` ``` { "id": "policy_123", "deleted": true } ``` ## Access rules[​](#access-rules "Direct link to Access rules") ### Update access rules[​](#update-access-rules "Direct link to Update access rules") `PATCH /v1/gas-policies/{policyId}/access-rules` Provide at least one of `accounts`, `origins`, or `ips`. Each provided rule accepts one operation mode: | Mode | Behavior | | --------------------- | -------------------------------------------------------------------- | | `add` and/or `remove` | Modify the existing entries. Both are idempotent; removal runs first | | `set` | Replace the complete entry list. An empty list allows all values | | `allowAll: true` | Reset the rule to allow all values | Do not combine `set` or `allowAll` with the `add`/`remove` group for the same rule. ``` curl --request PATCH \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID/access-rules \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "accounts": { "remove": ["0x1111111111111111111111111111111111111111"], "add": [ { "value": "0x2222222222222222222222222222222222222222", "label": "New beta tester" } ] }, "origins": { "set": ["https://app.example.com"] } }' ``` Returns `200` with the updated [policy detail](#policy-detail). ## Transactions[​](#transactions "Direct link to Transactions") Transaction entries are ordered. Each input must contain `to`, `abi`, `selector`, and `parameters`. ### Replace all transactions[​](#replace-all-transactions "Direct link to Replace all transactions") `PUT /v1/gas-policies/{policyId}/transactions` Replaces the complete ordered transaction list. Pass an empty array to set the list to empty. ``` curl --request PUT \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID/transactions \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "transactions": [ { "to": "0x1111111111111111111111111111111111111111", "abi": "[{\"type\":\"function\",\"name\":\"transfer\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"address\",\"name\":\"to\"},{\"type\":\"uint256\",\"name\":\"amount\"}],\"outputs\":[{\"type\":\"bool\",\"name\":\"\"}]}]", "selector": "0xa9059cbb", "parameters": [ { "index": "1", "operator": "$lte", "value": "0x0f4240" } ] } ] }' ``` Returns `200` with the updated [policy detail](#policy-detail). ### Append a transaction[​](#append-a-transaction "Direct link to Append a transaction") `POST /v1/gas-policies/{policyId}/transactions` The request body is one [transaction input](#transaction-input). ``` curl --request POST \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID/transactions \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "to": "${userop.sender}", "abi": "[{\"type\":\"function\",\"name\":\"execute\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]}]", "selector": "0x61461954", "parameters": [] }' ``` ``` { "transactionId": "transaction_123", "policy": { "id": "policy_123", "name": "Onboarding sponsorship", "chainId": 11155111, "createdAt": 1784678400000, "general": { "enabled": false, "private": true, "sponsorshipPolicyId": "sp_123", "startDate": 1784678400000, "endDate": 1787356800000 }, "accountRules": { "rate": 3, "ratePeriod": 86400, "totalMax": "0x6f05b59d3b20000", "maxPerOp": "0x5af3107a4000" }, "accessRules": { "accounts": {"allowAll": true, "entries": []}, "origins": {"allowAll": false, "entries": [{"value": "https://app.example.com", "label": "Production app"}]}, "ips": {"allowAll": true, "entries": []} }, "transactions": [ { "id": "transaction_123", "to": "${userop.sender}", "contractName": "-", "selector": "0x61461954", "abi": "[{\"type\":\"function\",\"name\":\"execute\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]}]", "parameters": [] } ], "billing": { "currency": "ETH", "balance": "0x0", "lockedBalance": "0x0", "available": "0x0" } } } ``` ### Remove a transaction[​](#remove-a-transaction "Direct link to Remove a transaction") `DELETE /v1/gas-policies/{policyId}/transactions/{transactionId}` ``` curl --request DELETE \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID/transactions/TRANSACTION_ID \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` Returns `200` with the updated policy. A transaction that is not on the policy returns `404`. ## Input models[​](#input-models "Direct link to Input models") ### General input[​](#general-input "Direct link to General input") All fields are optional and merge over the current general settings. | Field | Type | Description | | ----------- | ------- | ----------------------------------------------------------------------------- | | `enabled` | boolean | Whether the policy is active | | `private` | boolean | Require the sponsorship policy ID to match | | `startDate` | integer | Active-from time in milliseconds; cannot move more than one day into the past | | `endDate` | integer | Active-until time in milliseconds; must be after `startDate` | ### Account rules input[​](#account-rules-input "Direct link to Account rules input") All fields are optional and merge over the current account rules. | Field | Type | Description | | ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------- | | `rate` | integer | Positive sponsorship limit, or `-1` to disable rate limiting | | `ratePeriod` | integer | Required unless `rate` is `-1`; allowed values are `3600`, `43200`, `86400`, `604800`, `1296000`, `2592000`, and `3153600000` | | `totalMax` | string | Maximum total gas spending per account, in hex wei | | `maxPerOp` | string | Maximum gas spending for one UserOperation, in hex wei | ### Access rules input[​](#access-rules-input "Direct link to Access rules input") `accounts`, `origins`, and `ips` each accept an array of strings or `{"value": string, "label": string}` objects. A label is optional. An empty array means allow all. * Account values are account addresses. * Origin values are HTTPS origins. * IP values are IPv4 addresses. ### Transaction input[​](#transaction-input "Direct link to Transaction input") | Field | Type | Required | Description | | ------------ | ------------------------ | -------- | -------------------------------------------------------------------- | | `to` | string | Yes | Target contract address or the literal `"${userop.sender}"` | | `abi` | string | Yes | JSON-stringified ABI array containing the complete function fragment | | `selector` | string | Yes | Four-byte function selector with eight hex digits | | `parameters` | transaction parameter\[] | Yes | Argument constraints; may be empty | Each transaction parameter has: | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `index` | string | Argument index. Use semicolons to descend into tuples, for example `"0;3"` | | `operator` | string | `$eq`, `$gt`, `$gte`, `$lt`, `$lte`, or `$startsWith` | | `value` | string | Value to compare against. Encode `uint` and `bytes` values as `0x`-prefixed hexadecimal strings; use `"true"` or `"false"` for booleans | ## Response models[​](#response-models "Direct link to Response models") ### Policy detail[​](#policy-detail "Direct link to Policy detail") | Field | Type | Description | | -------------- | -------------- | ----------------------------------------------------------------------- | | `id` | string | Policy ID | | `name` | string | Display name | | `chainId` | integer | Sponsored chain | | `createdAt` | integer | Creation time in milliseconds since Unix epoch | | `general` | object | `enabled`, `private`, `sponsorshipPolicyId`, `startDate`, and `endDate` | | `accountRules` | object | `rate`, `ratePeriod`, `totalMax`, and `maxPerOp` | | `accessRules` | object | Account, origin, and IP access-rule states | | `transactions` | transaction\[] | Ordered allowed transactions | | `billing` | object | Policy funding information | Each returned access rule contains `allowAll` and `entries`. Each entry contains `value` and `label`; the label defaults to a dash. Each returned transaction contains its `id`, `to`, resolved `contractName` (or a dash), `selector`, `abi`, and `parameters`. Billing fields are hexadecimal wei strings: | Field | Description | | --------------- | ------------------------------------------ | | `currency` | Native token symbol for the policy's chain | | `balance` | Total policy funds | | `lockedBalance` | Funds reserved for in-flight operations | | `available` | Balance minus locked balance | ## Errors[​](#errors "Direct link to Errors") Every error response uses this envelope: ``` { "error": { "code": "validation_error", "message": "Request validation failed.", "details": { "accountRules.maxPerOp": "must be a valid non-negative hex amount" } } } ``` `details` is present on validation errors and maps field paths to reasons. | Status | Code | Meaning | | ------ | ------------------ | -------------------------------------------------------------------- | | `400` | `bad_request` | Malformed JSON, query value, unavailable team chain, or empty update | | `401` | `unauthorized` | Missing, malformed, or invalid management key | | `403` | `forbidden` | Missing scope or team plan policy limit | | `404` | `not_found` | Unknown, deleted, or cross-team policy or transaction | | `422` | `validation_error` | One or more request fields failed validation | | `500` | `internal_error` | Unexpected server error | Documented messages include: * `Request body is not valid JSON.` * `chainId must be an integer.` * `enabled must be 'true' or 'false'.` * `page must be a non-negative integer.` * `pageSize must be one of 10, 25, 50, 100.` * `Chain {chainId} is not available for this team.` * `Provide at least one field to update: name, general, accountRules, accessRules.` * `Provide at least one of: accounts, origins, ips.` * `Missing or malformed Authorization header. Use 'Authorization: Bearer '.` * `Missing management key.` * `Invalid management key.` * `This management key is missing the required scope: {scope}` * `Your plan does not allow more than {n} gas policies on mainnets. Upgrade your plan for more.` * `Gas policy not found.` * `Transaction {transactionId} not found on this policy.` * `The requested endpoint does not exist.` * `Request validation failed.` * `An unexpected error occurred.` --- # Platform API The Platform API lets you create and maintain Candide gas policies from backend services, deployment scripts, and internal tools. It provides the same policy-management capabilities as the Candide Dashboard, without requiring someone to configure every policy manually. Use it when policy configuration is part of your application's workflow—for example, provisioning a policy for each customer, updating allowlists from an admin service, or keeping policy configuration in sync across environments. ## Choose the right interface[​](#choose-the-right-interface "Direct link to Choose the right interface") | Interface | Use it for | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | [Dashboard](https://dashboard.candide.dev) | Creating and reviewing policies interactively | | Platform API | Creating, reading, updating, and deleting policies from trusted server-side code | | [Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md) | Requesting sponsorship for a UserOperation at runtime | The Platform API is a control-plane API: it configures which operations Candide may sponsor. It does not submit or sponsor UserOperations itself. ## Base URL[​](#base-url "Direct link to Base URL") ``` https://platform-api.candide.dev ``` All routes in the current version start with `/v1`. ## Authentication[​](#authentication "Direct link to Authentication") Create a team management key in the [Dashboard](https://dashboard.candide.dev), grant only the scopes your integration needs, and send it as a bearer token: ``` Authorization: Bearer mk_... ``` | Scope | Allows | | -------------------- | --------------------------------------- | | `gas-policies:read` | List and fetch gas policies | | `gas-policies:write` | Create, update, and delete gas policies | Every request is scoped to the team that owns the key. A policy belonging to another team is treated as not found. Keep management keys on the server A management key can change sponsorship rules and must never be embedded in browser code, mobile applications, public repositories, or logs. It is separate from the API key used in a Bundler or Paymaster endpoint. ## Quickstart[​](#quickstart "Direct link to Quickstart") Set your management key in the shell. Do not commit it to source control: ``` export CANDIDE_MANAGEMENT_KEY="mk_..." ``` ### 1. Verify the key[​](#1-verify-the-key "Direct link to 1. Verify the key") ``` curl --request GET \ --url https://platform-api.candide.dev/v1/me \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" ``` The response identifies the key's team and granted scopes: ``` { "keyId": "key_123", "teamId": "team_123", "scopes": ["gas-policies:read", "gas-policies:write"] } ``` ### 2. Create a policy[​](#2-create-a-policy "Direct link to 2. Create a policy") Start with the policy disabled and private. Add and review the necessary account, access, and transaction restrictions before enabling it. ``` curl --request POST \ --url https://platform-api.candide.dev/v1/gas-policies \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Onboarding sponsorship", "chainId": 11155111, "general": { "enabled": false, "private": true }, "accountRules": { "rate": 3, "ratePeriod": 86400, "totalMax": "0x6f05b59d3b20000", "maxPerOp": "0x5af3107a4000" } }' ``` The API returns the complete policy with status `201 Created`. Save its `id` for management calls. For a private policy, use `general.sponsorshipPolicyId` when requesting sponsorship from the Paymaster API. ### 3. Add restrictions[​](#3-add-restrictions "Direct link to 3. Add restrictions") Use the dedicated endpoints when you need precise list operations: * `PATCH /v1/gas-policies/{policyId}/access-rules` adds, removes, replaces, or resets access allowlists. * `POST /v1/gas-policies/{policyId}/transactions` appends one allowed transaction. * `PUT /v1/gas-policies/{policyId}/transactions` replaces the complete ordered transaction list. * `DELETE /v1/gas-policies/{policyId}/transactions/{transactionId}` removes one transaction. An empty account, origin, or IP list means **allow all values for that rule**. Review empty lists carefully before activating a policy. See the [policy rules overview](https://docs.candide.dev/instagas/gas-policies.md) for the purpose of each rule and the [Gas Policy API reference](https://docs.candide.dev/platform/gas-policy-api.md) for exact request formats. ### 4. Enable the policy[​](#4-enable-the-policy "Direct link to 4. Enable the policy") After reviewing the policy, enable it with a partial update: ``` curl --request PATCH \ --url https://platform-api.candide.dev/v1/gas-policies/POLICY_ID \ --header "Authorization: Bearer $CANDIDE_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{"general":{"enabled":true}}' ``` Omitted fields remain unchanged. ## Automation guidance[​](#automation-guidance "Direct link to Automation guidance") * Call `GET /v1/me` during setup to fail early when a key has the wrong team or scopes. * Treat policy and transaction IDs as opaque strings. * Validate HTTP status codes before reading a success response. * Read `error.code` for program logic and `error.details` for field-level validation failures. * Prefer the idempotent access-rule operations when synchronizing allowlists. * Use `PUT /transactions` only when your system owns the complete ordered list. * Keep policies disabled until all required restrictions and funding are in place. ## Machine-readable specification[​](#machine-readable-specification "Direct link to Machine-readable specification") Download the complete [OpenAPI 3.1 specification](https://docs.candide.dev/assets/files/platform-api-dc5d811b47ffe5ce3d700d185401d5a2.yaml) for client generation and agent tooling. --- # Bundler The `Bundler` class provides access to ERC-4337 Bundler JSON-RPC API methods for estimating user operation gas and sending user operations. The `Bundler` class creates a Bundler instance with a [Bundler RPC URL](https://docs.candide.dev/wallet/api/supported-networks.md) or an EIP-1193-shaped `Transport`. ## Usage[​](#usage "Direct link to Usage") ### Import[​](#import "Direct link to Import") ``` import { Bundler } from "abstractionkit"; ``` ### How to Use[​](#how-to-use "Direct link to How to Use") Initialize a Bundler with your desired bundler RPC URL: ``` const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); ``` You can also pass a custom transport when you need request cancellation, custom headers, retries, telemetry, or an injected provider: ``` import { Bundler, HttpTransport } from "abstractionkit"; const bundler = new Bundler( new HttpTransport("https://api.candide.dev/public/v3/11155111"), ); ``` Then consume Bundler methods: ``` const entrypointAddresses = await bundler.supportedEntryPoints(); ``` You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/bundler/bundler.ts) and follow along. ### Parameters[​](#parameters "Direct link to Parameters") #### bundlerRPC `string | Transport`[​](#bundlerrpc-string--transport "Direct link to bundlerrpc-string--transport") `Bundler`, `CandidePaymaster`, and `Erc7677Paymaster` accept either an RPC URL string or a `Transport`. Each service also exposes `transport`, which replaces the old `rpcUrl` field. ## Methods[​](#methods "Direct link to Methods") ### chainId[​](#chainid "Direct link to chainId") Returns the chain ID of the network the bundler is operating on. #### Usage[​](#usage-1 "Direct link to Usage") * example.ts * Return Type example.ts ``` import { Bundler } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); const getChainId = await bundler.chainId(); ``` | key | type | description | | :-------- | :---------------- | :------------------------------------- | | `chainId` | `Promise` | Target ChainId of the Bundler URL used | Example Response ``` 0xaa36a7 ``` #### JSON-RPC Method[​](#json-rpc-method "Direct link to JSON-RPC Method") [eth\_chainId](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_chainid) ### supportedEntryPoints[​](#supportedentrypoints "Direct link to supportedEntryPoints") Returns the list of the entryPoint addresses supported by the bundler #### Usage[​](#usage-2 "Direct link to Usage") * example.ts * Return Type example.ts ``` import { Bundler } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); const entrypointAddresses = await bundler.supportedEntryPoints(); ``` | key | type | description | | :--------------------- | :------------------ | :--------------------------------- | | `entrypoint addresses` | `Promise` | Support EntryPoints by the Bundler | Example Response ``` [ '0x0000000071727De22E5E9d8BAf0edAc6f37da032', '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' ] ``` #### JSON-RPC Method[​](#json-rpc-method-1 "Direct link to JSON-RPC Method") [eth\_supportedEntryPoints](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_supportedentrypoints) ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Generates and returns an estimate of how much gas is necessary to allow the user operation to complete, given a UserOperations. #### Usage[​](#usage-3 "Direct link to Usage") See a full example in [getting-started guide](https://docs.candide.dev/wallet/guides/getting-started.md) * example.ts * Param Type * Return Type example.ts ``` import { Bundler, UserOperationV7 } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); // Use createUserOperation() to help you construct the userOp below const userOperation = smartAccount.createUserOperation(..) const entryPointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; const estimation = await bundler.estimateUserOperationGas(userOperation, entryPointAddress); ``` | key | type | description | | :-------------------- | :------------------------------------------------------------------------- | :--------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | Supports UserOperations on EntryPoint v0.6, v0.7, v0.8, and v0.9 | | `entrypoint address` | `string` | Target EntryPoint | | `state_override_set?` | `StateOverrideSet` | Optional state overrides to apply during gas estimation | UserOperationV8 / UserOperationV9 | key | type | description | | :------------------------------ | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------- | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | Example Response ``` { callGasLimit: 58588n, preVerificationGas: 45628n, verificationGasLimit: 94374n } ``` #### JSON-RPC Method[​](#json-rpc-method-2 "Direct link to JSON-RPC Method") [eth\_estimateUserOperationGas](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_estimateuseroperationgas) ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") Asks the bundler to sign and submit a User Operation #### Usage[​](#usage-4 "Direct link to Usage") See a full example in [getting-started guide](https://docs.candide.dev/wallet/guides/getting-started.md) * example.ts * Param Type * Return Type example.ts ``` import { Bundler } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); const entrypointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; // Use createUserOperation() to help you construct the userOp below const userOperation = smartAccount.createUserOperation(..) const userOperationHash = await bundler.sendUserOperation(userOperation, entrypointAddress); ``` | key | type | description | | :------------------- | :------------------------------------------------------------------------- | :--------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | Supports UserOperations on EntryPoint v0.6, v0.7, v0.8, and v0.9 | | `entrypoint address` | `string` | Target EntryPoint | UserOperationV8 / UserOperationV9 | key | type | description | | :------------------------------ | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | key | type | description | | :----------- | :------- | :----------------- | | `userOpHash` | `string` | UserOperation Hash | Example Response ``` '0x0ff052095987556f476c6e6b7cdff65cc6191d2cede50a109fe6977c3287fc9a' ``` #### JSON-RPC Method[​](#json-rpc-method-3 "Direct link to JSON-RPC Method") [eth\_sendUserOperation](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_senduseroperation) ### getUserOperationByHash[​](#getuseroperationbyhash "Direct link to getUserOperationByHash") Returns a UserOperation by its hash returned from sendUserOperation ##### Usage[​](#usage-5 "Direct link to Usage") * example.ts * Param Type * Return Type example.ts ``` import { Bundler } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); const userOperationHash = "0xb348b32bc9b9e90620839c3926db401558806b03b2a46dc6de21d3a4ed8412fb"; const userOperation = await bundler.getUserOperationByHash(userOperationHash); ``` | key | type | description | | :----------- | :------- | :----------------- | | `userOpHash` | `string` | UserOperation Hash | | key | type | description | | :---------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | The user operation object, which can be version 6, 7, 8, or 9, depending on the entry point version used. | | `entryPoint` | `string` | The address of the entry point contract associated with the user operation. | | `blockNumber` | `bigint \| null` | The block number in which the user operation was included, or null if it hasn't been included yet. | | `blockHash` | `string \| null` | The hash of the block containing the user operation, or null if it hasn't been included yet. | | `transactionHash` | `string \| null` | The transaction hash associated with the user operation, or null if it hasn't been included yet. | UserOperationV8 / UserOperationV9 | key | type | description | | :------------------------------ | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | Example Response ``` { userOperation: { sender: '0xbA1Cd2470F46778c9748ce676c00Ba48D6f0b89d', nonce: '0x2', initCode: '0x', callData: '0xf34308ef0000000000000000000000001a02592a3484c2077d2e5d24482497f85e1980c6000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', callGasLimit: '0xa5e8', verificationGasLimit: '0x1825b', preVerificationGas: '0xcb5c', maxFeePerGas: '0x3b9aca1e', maxPriorityFeePerGas: '0x3b9aca00', paymasterAndData: '0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020000000000000000000000000000000000000000003000064cfa9640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064c82c6734b770b84cdf835ba3d1df6237178138c98ec5bef635cf8f3f2ac1fa3c0528a98b33c5ec5d5ab559f79ed3cac4ebb6b57de127840b433557938e46151b', signature: '0x8a45a5eadb9f2f4b552ec20fa63424dbecca28a7a6f43f065298574861869071446b622ccc17c2eab539115afceb314f859a1cc7e9289935c4fddce833cda3ca1c' }, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', blockNumber: '0x908994', blockHash: '0xd7d5471af5fdf2359031a5b0b7ac4c664b62aaaa0e4a65c248ef0fe78b7eeb9f', transactionHash: '0xc8262db778457c24d8faa043879de973d6c8699ddb518b3864004d99093ce6e4' } ``` #### JSON-RPC Method[​](#json-rpc-method-4 "Direct link to JSON-RPC Method") [eth\_getUserOperationByHash](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_getuseroperationbyhash) ### getUserOperationReceipt[​](#getuseroperationreceipt "Direct link to getUserOperationReceipt") Returns the receipt of a UserOperation by its hash returned from `sendUserOperation` #### Usage[​](#usage-6 "Direct link to Usage") * example.ts * Param Type * Return Type example.ts ``` import { Bundler } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const bundler: Bundler = new Bundler(bundlerRPC); const userOperationHash = "0xb348b32bc9b9e90620839c3926db401558806b03b2a46dc6de21d3a4ed8412fb"; const userOperationReceipt = await bundler.getUserOperationReceipt( userOperationHash ); ``` | key | type | description | | :----------- | :------- | :----------------- | | `userOpHash` | `string` | UserOperation Hash | | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | Example Response ``` { userOpHash: '0xb348b32bc9b9e90620839c3926db401558806b03b2a46dc6de21d3a4ed8412fb', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', sender: '0xba1cd2470f46778c9748ce676c00ba48d6f0b89d', nonce: '0x2', paymaster: '0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020', actualGasCost: 130079001821106, actualGasUsed: 130079, success: true, logs: '[]', receipt: { blockHash: '0xd7d5471af5fdf2359031a5b0b7ac4c664b62aaaa0e4a65c248ef0fe78b7eeb9f', blockNumber: '0x908994', from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: '0x5f780b', gasUsed: '0x1f069', logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xd7d5471af5fdf2359031a5b0b7ac4c664b62aaaa0e4a65c248ef0fe78b7eeb9f","blockNumber":"0x908994","data":"0x","logIndex":"0x5b","removed":false,"topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"transactionHash":"0xc8262db778457c24d8faa043879de973d6c8699ddb518b3864004d99093ce6e4","transactionIndex":"0x2f"},{"address":"0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020","blockHash":"0xd7d5471af5fdf2359031a5b0b7ac4c664b62aaaa0e4a65c248ef0fe78b7eeb9f","blockNumber":"0x908994","data":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x5c","removed":false,"topics":["0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471","0xb348b32bc9b9e90620839c3926db401558806b03b2a46dc6de21d3a4ed8412fb","0x000000000000000000000000ba1cd2470f46778c9748ce676c00ba48d6f0b89d","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0xc8262db778457c24d8faa043879de973d6c8699ddb518b3864004d99093ce6e4","transactionIndex":"0x2f"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xd7d5471af5fdf2359031a5b0b7ac4c664b62aaaa0e4a65c248ef0fe78b7eeb9f","blockNumber":"0x908994","data":"0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000764e60b23fb2000000000000000000000000000000000000000000000000000000000001fc1f","logIndex":"0x5d","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0xb348b32bc9b9e90620839c3926db401558806b03b2a46dc6de21d3a4ed8412fb","0x000000000000000000000000ba1cd2470f46778c9748ce676c00ba48d6f0b89d","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xc8262db778457c24d8faa043879de973d6c8699ddb518b3864004d99093ce6e4","transactionIndex":"0x2f"}]', logsBloom: '0x000000000000000000000000000001080000000000000000000000000000000000080000000000000002000100000000001000000000000000000a00000000000000000000000000000000040000000000000000010000000000000000800000000000000a0000000000002000000800000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000004000000000040400000000004000000000000000000000002000000000000000000000000000001200000000000000000000000000000100000000022000000000000000000000000000000000000000000000000000000000010000000', transactionHash: '0xc8262db778457c24d8faa043879de973d6c8699ddb518b3864004d99093ce6e4', transactionIndex: '0x2f', effectiveGasPrice: '0x69' } } ``` #### JSON-RPC Method[​](#json-rpc-method-5 "Direct link to JSON-RPC Method") [eth\_getUserOperationReceipt](https://docs.candide.dev/wallet/bundler/rpc-methods.md#eth_getuseroperationreceipt) --- # Calibur Account `Calibur7702Account` upgrades an EOA to a smart account via EIP-7702 delegation to the Calibur singleton. It provides batched transactions, passkey authentication, multi-key management with per-key hooks, and full ERC-4337 support through EntryPoint v0.8. ## When to Use Calibur Account[​](#when-to-use-calibur-account "Direct link to When to Use Calibur Account") * **Multi-key support**: Register passkeys, session keys, or secondary signers alongside the EOA root key. * **Per-key hooks**: Attach custom validation logic to individual keys (e.g., spending limits, time locks). * **Key expiration and admin separation**: Set expiration timestamps and distinguish admin keys from non-admin keys. * **WebAuthn/passkey authentication**: Sign transactions with biometrics through the WebAuthn standard. * **Production-grade contracts**: Built by Uniswap, audited by OpenZeppelin and Cantina. **Supported ERCs:** ERC-4337 (Account Abstraction), EIP-7702 (EOA delegation). **Supported key types:** | Type | Description | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Secp256k1` | Standard Ethereum EOA key (keyType = 2). Used for EOA private keys and secondary signers. | | `WebAuthnP256` | P-256 key signed through the browser's WebAuthn API (keyType = 1). Used for passkeys, Face ID, fingerprint, and security keys. The signature includes WebAuthn metadata (`authenticatorData`, `clientDataJSON`) that the contract parses before verifying the underlying P-256 signature. | | `P256` | Raw P-256 / secp256r1 key (keyType = 0). Same elliptic curve as WebAuthnP256, but with a raw `(r, s)` signature format. Used when you have direct access to a P-256 key (e.g., from a secure enclave or HSM) without the WebAuthn wrapper. | [Calibur Quickstart](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) [Upgrade an EOA to a Calibur smart account with batch transactions and gas sponsorship](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) [Passkey Authentication](https://docs.candide.dev/wallet/guides/calibur-passkeys.md) [Register a WebAuthn passkey and sign transactions with it](https://docs.candide.dev/wallet/guides/calibur-passkeys.md) [Key Management](https://docs.candide.dev/wallet/guides/calibur-key-management.md) [Register, update, and revoke keys on a Calibur account](https://docs.candide.dev/wallet/guides/calibur-key-management.md) ## Smart Contracts and Audits[​](#smart-contracts-and-audits "Direct link to Smart Contracts and Audits") The contracts were developed by Uniswap and audited by OpenZeppelin and Cantina. * [Smart Contracts](https://github.com/Uniswap/calibur) * Singleton: `0x000000009B1D0aF20D8C6d0A44e162d11F9b8f00` ## How to Use[​](#how-to-use "Direct link to How to Use") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using `Calibur7702Account`, you must have: * **Node.js**: Version 18.0 or higher. * **EIP-7702 Compatible Network**: Ethereum mainnet, Sepolia, or other EIP-7702 enabled chains. * **Private Key Access**: Required for signing authorizations and user operations. ### Installation[​](#installation "Direct link to Installation") ``` npm install abstractionkit ``` The contracts are audited by OpenZeppelin and Cantina. ### Usage[​](#usage "Direct link to Usage") ``` import { Calibur7702Account } from "abstractionkit"; const eoaAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // EOA public address const smartAccount = new Calibur7702Account(eoaAddress); ``` **Constructor defaults:** * `entrypointAddress`: `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108` (EntryPoint v0.8) * `delegateeAddress`: `0x000000009B1D0aF20D8C6d0A44e162d11F9b8f00` (Uniswap v1.0.0) Both can be overridden by passing an `overrides` object as the second constructor argument. ``` const smartAccount = new Calibur7702Account(eoaAddress, { entrypointAddress: "0x...", // optional delegateeAddress: "0x...", // optional }); ``` ## Essential Methods[​](#essential-methods "Direct link to Essential Methods") ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") Creates a UserOperation for EIP-7702 accounts that can be sent to bundlers for execution. Handles nonce fetching, gas estimation, and optional EIP-7702 delegation authorization. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const eoaAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = new Calibur7702Account(eoaAddress); const transactions = [ { to: "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336", value: 0n, data: "0x...", }, ]; const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // provider RPC "https://api.candide.dev/public/v3/11155111", // bundler RPC { eip7702Auth: { chainId: 11155111n, // required for the first UserOperation }, } ); ``` | key | type | description | | :----------------------------------------------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactions` | `SimpleMetaTransaction[]` | Array of transactions to include in the user operation | | `providerRpc?` | `string` | JSON-RPC provider URL for nonce and gas price queries | | `bundlerRpc?` | `string` | Bundler RPC URL for gas estimation | | `overrides?` | `object` | Optional overrides for gas, nonce, and EIP-7702 auth fields | | `overrides?.nonce?` | `bigint` | Set the nonce instead of querying from the RPC node | | `overrides?.callData?` | `string` | Set the callData instead of encoding the provided MetaTransactions | | `overrides?.callGasLimit?` | `bigint` | Set the callGasLimit instead of estimating via the bundler | | `overrides?.verificationGasLimit?` | `bigint` | Set the verificationGasLimit instead of estimating via the bundler | | `overrides?.preVerificationGas?` | `bigint` | Set the preVerificationGas instead of estimating via the bundler | | `overrides?.maxFeePerGas?` | `bigint` | Set the maxFeePerGas instead of querying current gas price | | `overrides?.maxPriorityFeePerGas?` | `bigint` | Set the maxPriorityFeePerGas instead of querying current gas price | | `overrides?.callGasLimitPercentageMultiplier?` | `number` | Percentage multiplier applied to estimated callGasLimit | | `overrides?.verificationGasLimitPercentageMultiplier?` | `number` | Percentage multiplier applied to estimated verificationGasLimit | | `overrides?.preVerificationGasPercentageMultiplier?` | `number` | Percentage multiplier applied to estimated preVerificationGas | | `overrides?.maxFeePerGasPercentageMultiplier?` | `number` | Percentage multiplier applied to fetched maxFeePerGas | | `overrides?.maxPriorityFeePerGasPercentageMultiplier?` | `number` | Percentage multiplier applied to fetched maxPriorityFeePerGas | | `overrides?.state_override_set?` | `StateOverrideSet` | State overrides for gas estimation | | `overrides?.dummySignature?` | `string` | Override the dummy signature used during gas estimation | | `overrides?.gasLevel?` | `GasOption` | Gas price level preference | | `overrides?.polygonGasStation?` | `PolygonChain` | Polygon chain identifier for fetching gas prices from Polygon Gas Station | | `overrides?.revertOnFailure?` | `boolean` | Whether BatchedCall should revert on individual call failure (default: true) | | `overrides?.paymasterFields?` | `ParallelPaymasterInitValues` | Paymaster init values for gas estimation. Set these to include paymaster data during gas estimation so preVerificationGas is accurate. | | `overrides?.eip7702Auth?` | `object` | EIP-7702 authorization fields. Required for the first UserOperation to delegate the EOA to the Calibur singleton. | | `overrides?.eip7702Auth?.chainId` | `string` | Chain ID in hexadecimal format where the authorization is valid | | `overrides?.eip7702Auth?.address` | `string` | Address to authorize for the EOA delegation | | `overrides?.eip7702Auth?.nonce` | `string` | Authorization nonce in hexadecimal format | | `overrides?.eip7702Auth?.yParity` | `string` | Y parity of the authorization signature | | `overrides?.eip7702Auth?.r` | `string` | R component of the authorization signature | | `overrides?.eip7702Auth?.s` | `string` | S component of the authorization signature | | `overrides?.skipGasEstimation?` | `boolean` | When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls. | SimpleMetaTransaction | key | type | description | | :---------------------------- | :------- | :----------------------------------------------------------------------------- | | `SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Calibur7702Account. | | `SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `SimpleMetaTransaction.data` | `string` | The call data for the transaction | | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The constructed unsigned UserOperation for EIP-7702 | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | #### Source code[​](#source-code "Direct link to Source code") [createUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L331) ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") Signs a UserOperation with a private key. Computes the UserOperation hash and wraps the ECDSA signature in Calibur's format. By default signs with the root key. To sign with a registered secondary key, pass its key hash via `overrides.keyHash`. * example.ts * Param Types * Return Type example.ts ``` // Sign with the root key (EOA private key) userOperation.signature = smartAccount.signUserOperation( userOperation, "0x...private-key", 11155111n // chain ID ); // Sign with a registered secondary key userOperation.signature = smartAccount.signUserOperation( userOperation, "0x...secondary-private-key", 11155111n, { keyHash: "0x...registered-key-hash" } ); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to sign | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `privateKey` | `string` | Hex-encoded private key to sign the user operation with | | `chainId` | `bigint` | Chain ID for the target blockchain | | `overrides?` | `object` | Optional overrides (keyHash for secondary keys, hookData) | | `overrides?.hookData?` | `string` | Hook data to append to the signature (default: "0x" = empty) | | `overrides?.keyHash?` | `string` | Key hash of a registered secondary key. If omitted, the root key hash is used. | | key | type | description | | :---------- | :------- | :---------------------------------------------- | | `signature` | `string` | Hex-encoded wrapped signature in Calibur format | #### Source code[​](#source-code-1 "Direct link to Source code") [signUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L675) ### signUserOperationWithSigner[​](#signuseroperationwithsigner "Direct link to signUserOperationWithSigner") Signs a UserOperation using an [`ExternalSigner`](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of a raw private key. Integrates viem, ethers, browser wallets, hardware wallets, HSMs, MPC, and WebAuthn through the same API. By default signs with the root key; pass `{ keyHash }` to sign with a registered secondary key. Since `v0.3.8`, Calibur accepts signers that implement either `signHash` or `signTypedData`. `signTypedData` signatures are wrapped into Calibur's `(keyHash, sig, hookData)` layout automatically. * example.ts * Param Types * Return Type example.ts ``` import { fromViem } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const viemAccount = privateKeyToAccount("0x...private-key"); // Sign with the root key userOperation.signature = await smartAccount.signUserOperationWithSigner( userOperation, fromViem(viemAccount), 11155111n, // chain ID ); // Sign with a registered secondary key userOperation.signature = await smartAccount.signUserOperationWithSigner( userOperation, fromViem(viemAccount), 11155111n, { keyHash: "0x...registered-key-hash" }, ); ``` See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters (viem, ethers, custom) and the [Calibur external-signer example](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/04-external-signer.ts) for a full end-to-end flow. | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to sign | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `signer` | `ExternalSigner` | A capability-oriented signer: { address, signHash?, signTypedData? }. Use one of the built-in adapters (fromPrivateKey, fromViem, fromEthersWallet, fromViemWalletClient) or implement the interface yourself to integrate hardware wallets, HSMs, MPC, or WebAuthn. Calibur requires a signer that implements signHash. | | `chainId` | `bigint` | Chain ID for the target blockchain | | `overrides?` | `object` | Optional overrides (keyHash for secondary keys, hookData) | | `overrides?.hookData?` | `string` | Hook data to append to the signature (default: "0x" = empty) | | `overrides?.keyHash?` | `string` | Key hash of a registered secondary key. If omitted, the root key hash is used. | | key | type | description | | :---------- | :------- | :---------------------------------------------- | | `signature` | `string` | Hex-encoded wrapped signature in Calibur format | #### Source code[​](#source-code-2 "Direct link to Source code") [signUserOperationWithSigner](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L719) ### formatWebAuthnSignature[​](#formatwebauthnsignature "Direct link to formatWebAuthnSignature") Formats a WebAuthn (passkey) assertion into Calibur's signature format. Use this after getting an assertion from the browser's `navigator.credentials.get()` API. The challenge for the assertion should be the UserOperation hash. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account, WebAuthnSignatureData } from "abstractionkit"; // After getting a WebAuthn assertion from the browser const webAuthnSignatureData: WebAuthnSignatureData = { authenticatorData: "0x...", clientDataJSON: '{"type":"webauthn.get","challenge":"...","origin":"https://example.com"}', challengeIndex: 36n, typeIndex: 8n, r: 0x...n, s: 0x...n, }; const keyHash = Calibur7702Account.getKeyHash(webAuthnKey); // Format the WebAuthn signature for the UserOperation userOperation.signature = smartAccount.formatWebAuthnSignature( keyHash, webAuthnSignatureData, ); ``` | key | type | description | | :----------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------- | | `keyHash` | `string` | The key hash of the registered passkey (from getKeyHash) | | `webAuthnAuth` | `object` | WebAuthn assertion data from the browser | | `webAuthnAuth.WebAuthnSignatureData` | `object` | WebAuthn assertion data matching the on-chain WebAuthn.WebAuthnAuth struct. Used when signing UserOperations with a passkey. | | `webAuthnAuth.WebAuthnSignatureData.authenticatorData` | `string` | Authenticator data bytes (hex string) | | `webAuthnAuth.WebAuthnSignatureData.clientDataJSON` | `string` | Client data JSON string (UTF-8) | | `webAuthnAuth.WebAuthnSignatureData.challengeIndex` | `bigint` | Index of the challenge in clientDataJSON | | `webAuthnAuth.WebAuthnSignatureData.typeIndex` | `bigint` | Index of the type field in clientDataJSON | | `webAuthnAuth.WebAuthnSignatureData.r` | `bigint` | ECDSA signature r component | | `webAuthnAuth.WebAuthnSignatureData.s` | `bigint` | ECDSA signature s component | | `overrides?` | `object` | Optional signature overrides (e.g., hookData) | | `overrides?.hookData?` | `string` | Hook data to append to the signature (default: "0x" = empty) | | `overrides?.keyHash?` | `string` | Key hash of a registered secondary key. If omitted, the root key hash is used. | WebAuthnSignatureData | key | type | description | | :---------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------- | | `WebAuthnSignatureData` | `object` | WebAuthn assertion data matching the on-chain WebAuthn.WebAuthnAuth struct. Used when signing UserOperations with a passkey. | | `WebAuthnSignatureData.authenticatorData` | `string` | Authenticator data bytes (hex string) | | `WebAuthnSignatureData.clientDataJSON` | `string` | Client data JSON string (UTF-8) | | `WebAuthnSignatureData.challengeIndex` | `bigint` | Index of the challenge in clientDataJSON | | `WebAuthnSignatureData.typeIndex` | `bigint` | Index of the type field in clientDataJSON | | `WebAuthnSignatureData.r` | `bigint` | ECDSA signature r component | | `WebAuthnSignatureData.s` | `bigint` | ECDSA signature s component | | key | type | description | | :---------- | :------- | :---------------------------------------------- | | `signature` | `string` | Hex-encoded wrapped signature in Calibur format | #### Source code[​](#source-code-3 "Direct link to Source code") [formatWebAuthnSignature](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L760) ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") Sends a signed UserOperation to the bundler for execution on-chain. * example.ts * Param Types * Return Type example.ts ``` const response = await smartAccount.sendUserOperation( userOperation, "https://api.candide.dev/public/v3/11155111" // bundler URL ); console.log("UserOperation hash:", response.userOperationHash); // Wait for the transaction to be included const receipt = await response.included(); console.log("Transaction receipt:", receipt); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The signed user operation to submit | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `bundlerRpc` | `string` | Bundler RPC endpoint to send the user operation to | | key | type | description | | :--------------------------- | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `response` | `object` | Response containing user operation hash and bundler details | | `response.userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `response.bundler` | `Bundler` | The Bundler class | | `response.entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `response.included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | SendUseroperationResponse | key | type | description | | :------------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `bundler` | `Bundler` | The Bundler class | | `entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | BundlerJsonRpcError | key | type | description | | :-------- | :------- | :------------------------------------ | | `code` | `number` | Bundler RPC error code | | `message` | `string` | Bundler RPC error message description | UserOperationReceiptResult | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | #### Source code[​](#source-code-4 "Direct link to Source code") [sendUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L794) ## Key Management Methods[​](#key-management-methods "Direct link to Key Management Methods") ### createSecp256k1Key[​](#createsecp256k1key "Direct link to createSecp256k1Key") Static method. Creates a secp256k1 key descriptor from an Ethereum address. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const secondaryAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"; const key = Calibur7702Account.createSecp256k1Key(secondaryAddress); console.log(key); // { keyType: 2, publicKey: "0x000...742d35Cc6634C0532925a3b844Bc9e7595f2bD18" } ``` | key | type | description | | :-------- | :------- | :---------------------------------------- | | `address` | `string` | The Ethereum address (EOA public address) | | key | type | description | | :------------------------- | :--------------- | :--------------------------------------------------------------- | | `key` | `object` | A CaliburKey with type Secp256k1 | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | CaliburKey | key | type | description | | :--------------------- | :--------------- | :--------------------------------------------------------------- | | `CaliburKey` | `object` | A key registered on a Calibur account. | | `CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | #### Source code[​](#source-code-5 "Direct link to Source code") [createSecp256k1Key](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L814) ### createWebAuthnP256Key[​](#createwebauthnp256key "Direct link to createWebAuthnP256Key") Static method. Creates a WebAuthn P-256 key descriptor from public key coordinates. Use this when registering a passkey on the account. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; // Extract x, y coordinates from a WebAuthn credential const x = 0x1234...n; // P-256 public key x coordinate const y = 0x5678...n; // P-256 public key y coordinate const webAuthnKey = Calibur7702Account.createWebAuthnP256Key(x, y); const keyHash = Calibur7702Account.getKeyHash(webAuthnKey); console.log("Key hash:", keyHash); ``` | key | type | description | | :-- | :------- | :--------------------------------------- | | `x` | `bigint` | The x coordinate of the P-256 public key | | `y` | `bigint` | The y coordinate of the P-256 public key | | key | type | description | | :------------------------- | :--------------- | :--------------------------------------------------------------- | | `key` | `object` | A CaliburKey with type WebAuthnP256 | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | CaliburKey | key | type | description | | :--------------------- | :--------------- | :--------------------------------------------------------------- | | `CaliburKey` | `object` | A key registered on a Calibur account. | | `CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | #### Source code[​](#source-code-6 "Direct link to Source code") [createWebAuthnP256Key](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L827) ### createP256Key[​](#createp256key "Direct link to createP256Key") Static method. Creates a raw P-256 (secp256r1) key descriptor from public key coordinates. Use this for non-WebAuthn P-256 keys. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const x = 0x1234...n; // P-256 public key x coordinate const y = 0x5678...n; // P-256 public key y coordinate const p256Key = Calibur7702Account.createP256Key(x, y); const keyHash = Calibur7702Account.getKeyHash(p256Key); console.log("Key hash:", keyHash); ``` | key | type | description | | :-- | :------- | :--------------------------------------- | | `x` | `bigint` | The x coordinate of the P-256 public key | | `y` | `bigint` | The y coordinate of the P-256 public key | | key | type | description | | :------------------------- | :--------------- | :--------------------------------------------------------------- | | `key` | `object` | A CaliburKey with type P256 | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | CaliburKey | key | type | description | | :--------------------- | :--------------- | :--------------------------------------------------------------- | | `CaliburKey` | `object` | A key registered on a Calibur account. | | `CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | #### Source code[​](#source-code-7 "Direct link to Source code") [createP256Key](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L840) ### getKeyHash[​](#getkeyhash "Direct link to getKeyHash") Static method. Computes the key hash for a Calibur key. Uses double hashing: `keccak256(abi.encode(uint8 keyType, bytes32 keccak256(publicKey)))`. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const key = Calibur7702Account.createSecp256k1Key("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"); const keyHash = Calibur7702Account.getKeyHash(key); console.log("Key hash:", keyHash); // bytes32 hex string, e.g. "0xabc123..." ``` | key | type | description | | :------------------------- | :--------------- | :--------------------------------------------------------------- | | `key` | `object` | The key to compute the hash for | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | CaliburKey | key | type | description | | :--------------------- | :--------------- | :--------------------------------------------------------------- | | `CaliburKey` | `object` | A key registered on a Calibur account. | | `CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | | key | type | description | | :-------- | :------- | :----------------------------------- | | `keyHash` | `string` | The key hash as a bytes32 hex string | #### Source code[​](#source-code-8 "Direct link to Source code") [getKeyHash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L854) ### createRegisterKeyMetaTransactions[​](#createregisterkeymetatransactions "Direct link to createRegisterKeyMetaTransactions") Static method. Creates meta-transactions to register a new key on the Calibur account. Returns two transactions (`[register, update]`) that must both be included in the same UserOperation. For safety, `isAdmin` is always forced to `false`. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; // Create the key to register const newKey = Calibur7702Account.createSecp256k1Key("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"); // Build register + update transactions (both required in the same UserOp) const registerTxs = Calibur7702Account.createRegisterKeyMetaTransactions( newKey, { expiration: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days } ); // Include both transactions in the UserOperation const userOperation = await smartAccount.createUserOperation( registerTxs, // array of two SimpleMetaTransactions nodeRpc, bundlerRpc, ); ``` | key | type | description | | :----------------------------------------- | :--------------- | :--------------------------------------------------------------------------- | | `key` | `object` | The key to register | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | | `settings?` | `object` | Optional key settings. isAdmin is always forced to false for safety. | | `settings?.CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `settings?.CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `settings?.CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `settings?.CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | CaliburKeySettings | key | type | description | | :------------------------------- | :-------- | :--------------------------------------------------------------------------- | | `CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | | key | type | description | | :------------- | :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `transactions` | `[SimpleMetaTransaction, SimpleMetaTransaction]` | A tuple of exactly two SimpleMetaTransactions: \[registerTx, updateTx]. Both must be included in the same UserOperation. | #### Source code[​](#source-code-9 "Direct link to Source code") [createRegisterKeyMetaTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L902) ### createRevokeKeyMetaTransaction[​](#createrevokekeymetatransaction "Direct link to createRevokeKeyMetaTransaction") Static method. Creates a meta-transaction to revoke a key from the Calibur account. Only admin keys can execute revoke operations. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const keyHash = "0x...key-hash-to-revoke"; const revokeTx = Calibur7702Account.createRevokeKeyMetaTransaction(keyHash); // Include in a UserOperation signed by an admin key const userOperation = await smartAccount.createUserOperation( [revokeTx], nodeRpc, bundlerRpc, ); ``` | key | type | description | | :-------- | :------- | :-------------------------------- | | `keyHash` | `string` | The key hash of the key to revoke | | key | type | description | | :---------------------------------------- | :------- | :----------------------------------------------------------------------------- | | `transaction` | `object` | A SimpleMetaTransaction that calls revoke(bytes32) | | `transaction.SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Calibur7702Account. | | `transaction.SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `transaction.SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `transaction.SimpleMetaTransaction.data` | `string` | The call data for the transaction | #### Source code[​](#source-code-10 "Direct link to Source code") [createRevokeKeyMetaTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L942) ### createUpdateKeySettingsMetaTransaction[​](#createupdatekeysettingsmetatransaction "Direct link to createUpdateKeySettingsMetaTransaction") Static method. Creates a meta-transaction to update settings for a registered key. Only admin keys can execute update operations. Throws if `isAdmin` is set to `true` in the settings. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const keyHash = "0x...registered-key-hash"; // Extend key expiration to 1 year from now const updateTx = Calibur7702Account.createUpdateKeySettingsMetaTransaction( keyHash, { expiration: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, } ); const userOperation = await smartAccount.createUserOperation( [updateTx], nodeRpc, bundlerRpc, ); ``` | key | type | description | | :---------------------------------------- | :-------- | :--------------------------------------------------------------------------- | | `keyHash` | `string` | The key hash of the key to update | | `settings` | `object` | New settings for the key. isAdmin must not be true (throws if true). | | `settings.CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `settings.CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `settings.CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `settings.CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | CaliburKeySettings | key | type | description | | :------------------------------- | :-------- | :--------------------------------------------------------------------------- | | `CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | | key | type | description | | :---------------------------------------- | :------- | :----------------------------------------------------------------------------- | | `transaction` | `object` | A SimpleMetaTransaction that calls update(bytes32, uint256) | | `transaction.SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Calibur7702Account. | | `transaction.SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `transaction.SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `transaction.SimpleMetaTransaction.data` | `string` | The call data for the transaction | #### Source code[​](#source-code-11 "Direct link to Source code") [createUpdateKeySettingsMetaTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1130) ### packKeySettings[​](#packkeysettings "Direct link to packKeySettings") Static method. Packs key settings into a single uint256 value. Layout: `(isAdmin << 200) | (expiration << 160) | hook`. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const packed = Calibur7702Account.packKeySettings({ hook: "0x0000000000000000000000000000000000000000", expiration: Math.floor(Date.now() / 1000) + 86400, // 1 day isAdmin: false, }); console.log("Packed settings:", packed); ``` | key | type | description | | :---------------------------------------- | :-------- | :--------------------------------------------------------------------------- | | `settings` | `object` | The key settings to pack | | `settings.CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `settings.CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `settings.CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `settings.CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | CaliburKeySettings | key | type | description | | :------------------------------- | :-------- | :--------------------------------------------------------------------------- | | `CaliburKeySettings` | `object` | Settings for a key registered on a Calibur account. All fields are optional. | | `CaliburKeySettings.hook?` | `string` | Hook contract address called during validation (zero address = no hook) | | `CaliburKeySettings.expiration?` | `number` | Unix timestamp after which the key expires (0 = never) | | `CaliburKeySettings.isAdmin?` | `boolean` | Whether the key has admin privileges | | key | type | description | | :------- | :------- | :----------------------------------------------------------------------------------------------------- | | `packed` | `bigint` | The packed settings as a single uint256 value. Layout: (isAdmin << 200) \| (expiration << 160) \| hook | #### Source code[​](#source-code-12 "Direct link to Source code") [packKeySettings](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L867) ### unpackKeySettings[​](#unpackkeysettings "Direct link to unpackKeySettings") Static method. Unpacks a uint256 settings value into a `CaliburKeySettingsResult` object with all fields populated. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const packed = 0x...n; // packed settings from the contract const settings = Calibur7702Account.unpackKeySettings(packed); console.log("Admin:", settings.isAdmin); console.log("Expiration:", settings.expiration); console.log("Hook:", settings.hook); ``` | key | type | description | | :------- | :------- | :------------------------------------------ | | `packed` | `bigint` | The packed settings uint256 value to unpack | | key | type | description | | :--------------------------------------------- | :-------- | :----------------------------------------------------------------------------------------------------------------------------- | | `settings` | `object` | Parsed key settings with all fields populated | | `settings.CaliburKeySettingsResult` | `object` | Concrete key settings returned from on-chain reads. All fields are required since the contract always returns concrete values. | | `settings.CaliburKeySettingsResult.hook` | `string` | Hook contract address called during validation (zero address = no hook) | | `settings.CaliburKeySettingsResult.expiration` | `number` | Unix timestamp after which the key expires (0 = never) | | `settings.CaliburKeySettingsResult.isAdmin` | `boolean` | Whether the key has admin privileges | CaliburKeySettingsResult | key | type | description | | :------------------------------------ | :-------- | :----------------------------------------------------------------------------------------------------------------------------- | | `CaliburKeySettingsResult` | `object` | Concrete key settings returned from on-chain reads. All fields are required since the contract always returns concrete values. | | `CaliburKeySettingsResult.hook` | `string` | Hook contract address called during validation (zero address = no hook) | | `CaliburKeySettingsResult.expiration` | `number` | Unix timestamp after which the key expires (0 = never) | | `CaliburKeySettingsResult.isAdmin` | `boolean` | Whether the key has admin privileges | #### Source code[​](#source-code-13 "Direct link to Source code") [unpackKeySettings](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L880) ## Read Methods[​](#read-methods "Direct link to Read Methods") ### isKeyRegistered[​](#iskeyregistered "Direct link to isKeyRegistered") Checks if a key is registered on this account by querying the on-chain `isRegistered(bytes32)` function. * example.ts * Param Types * Return Type example.ts ``` const keyHash = Calibur7702Account.getKeyHash(key); const isRegistered = await smartAccount.isKeyRegistered( "https://ethereum-sepolia-rpc.publicnode.com", keyHash, ); console.log("Key registered:", isRegistered); ``` | key | type | description | | :------------ | :------- | :--------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | `keyHash` | `string` | The key hash to check | | key | type | description | | :------------- | :-------- | :-------------------------------------------- | | `isRegistered` | `boolean` | True if the key is registered on this account | #### Source code[​](#source-code-14 "Direct link to Source code") [isKeyRegistered](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1196) ### getKeySettings[​](#getkeysettings "Direct link to getKeySettings") Gets the settings for a registered key. Returns a `CaliburKeySettingsResult` with hook address, expiration timestamp, and admin flag. * example.ts * Param Types * Return Type example.ts ``` const keyHash = Calibur7702Account.getKeyHash(key); const settings = await smartAccount.getKeySettings( "https://ethereum-sepolia-rpc.publicnode.com", keyHash, ); console.log("Admin:", settings.isAdmin); console.log("Expiration:", settings.expiration === 0 ? "never" : new Date(settings.expiration * 1000).toISOString()); console.log("Hook:", settings.hook); ``` | key | type | description | | :------------ | :------- | :--------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | `keyHash` | `string` | The key hash to query settings for | | key | type | description | | :--------------------------------------------- | :-------- | :----------------------------------------------------------------------------------------------------------------------------- | | `settings` | `object` | Parsed key settings with all fields populated | | `settings.CaliburKeySettingsResult` | `object` | Concrete key settings returned from on-chain reads. All fields are required since the contract always returns concrete values. | | `settings.CaliburKeySettingsResult.hook` | `string` | Hook contract address called during validation (zero address = no hook) | | `settings.CaliburKeySettingsResult.expiration` | `number` | Unix timestamp after which the key expires (0 = never) | | `settings.CaliburKeySettingsResult.isAdmin` | `boolean` | Whether the key has admin privileges | CaliburKeySettingsResult | key | type | description | | :------------------------------------ | :-------- | :----------------------------------------------------------------------------------------------------------------------------- | | `CaliburKeySettingsResult` | `object` | Concrete key settings returned from on-chain reads. All fields are required since the contract always returns concrete values. | | `CaliburKeySettingsResult.hook` | `string` | Hook contract address called during validation (zero address = no hook) | | `CaliburKeySettingsResult.expiration` | `number` | Unix timestamp after which the key expires (0 = never) | | `CaliburKeySettingsResult.isAdmin` | `boolean` | Whether the key has admin privileges | #### Source code[​](#source-code-15 "Direct link to Source code") [getKeySettings](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1222) ### getKey[​](#getkey "Direct link to getKey") Gets the full key data for a registered key, including the key type and ABI-encoded public key bytes. * example.ts * Param Types * Return Type example.ts ``` const keyHash = "0x...registered-key-hash"; const key = await smartAccount.getKey( "https://ethereum-sepolia-rpc.publicnode.com", keyHash, ); console.log("Key type:", key.keyType); // 0 = P256, 1 = WebAuthnP256, 2 = Secp256k1 console.log("Public key:", key.publicKey); ``` | key | type | description | | :------------ | :------- | :--------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | `keyHash` | `string` | The key hash to query | | key | type | description | | :------------------------- | :--------------- | :--------------------------------------------------------------- | | `key` | `object` | The CaliburKey associated with the given key hash | | `key.CaliburKey` | `object` | A key registered on a Calibur account. | | `key.CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `key.CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | CaliburKey | key | type | description | | :--------------------- | :--------------- | :--------------------------------------------------------------- | | `CaliburKey` | `object` | A key registered on a Calibur account. | | `CaliburKey.keyType` | `CaliburKeyType` | The type of cryptographic key (P256, WebAuthnP256, or Secp256k1) | | `CaliburKey.publicKey` | `string` | ABI-encoded public key bytes (hex string) | #### Source code[​](#source-code-16 "Direct link to Source code") [getKey](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1251) ### getKeys[​](#getkeys "Direct link to getKeys") Returns all keys registered on this account. Iterates `keyCount()` and `keyAt(i)` to enumerate all keys. * example.ts * Param Types * Return Type example.ts ``` const keys = await smartAccount.getKeys( "https://ethereum-sepolia-rpc.publicnode.com", ); for (const key of keys) { const keyHash = Calibur7702Account.getKeyHash(key); const settings = await smartAccount.getKeySettings(nodeRpc, keyHash); console.log("Key:", keyHash.slice(0, 18) + "..."); console.log(" Type:", key.keyType); console.log(" Admin:", settings.isAdmin); } ``` | key | type | description | | :------------ | :------- | :--------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | key | type | description | | :----- | :------------- | :-------------------------------------------------- | | `keys` | `CaliburKey[]` | Array of all registered CaliburKeys on this account | #### Source code[​](#source-code-17 "Direct link to Source code") [getKeys](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1284) ### isDelegatedToThisAccount[​](#isdelegatedtothisaccount "Direct link to isDelegatedToThisAccount") Checks if the EOA is delegated to this account's Calibur singleton. Returns `true` only when delegated to the account's `delegateeAddress`, `false` if not delegated or delegated to a different singleton. * example.ts * Param Types * Return Type example.ts ``` const isDelegated = await smartAccount.isDelegatedToThisAccount( "https://ethereum-sepolia-rpc.publicnode.com" ); if (isDelegated) { console.log("EOA is delegated to the Calibur singleton"); } else { console.log("EOA is not delegated. Include eip7702Auth in createUserOperation."); } ``` | key | type | description | | :------------ | :------- | :--------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | key | type | description | | :------------ | :-------- | :-------------------------------------------------------------------------------- | | `isDelegated` | `boolean` | True if the EOA is delegated to this account's Calibur singleton, false otherwise | #### Source code[​](#source-code-18 "Direct link to Source code") [isDelegatedToThisAccount](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1172) ### getNonce[​](#getnonce "Direct link to getNonce") Gets the account nonce from the EntryPoint. Supports parallel nonce channels via the optional `sequenceKey` parameter. * example.ts * Param Types * Return Type example.ts ``` // Default nonce (sequence key = 0) const nonce = await smartAccount.getNonce( "https://ethereum-sepolia-rpc.publicnode.com" ); console.log("Nonce:", nonce); // Parallel nonce channel const parallelNonce = await smartAccount.getNonce( "https://ethereum-sepolia-rpc.publicnode.com", 1 // sequence key ); ``` | key | type | description | | :------------- | :------- | :------------------------------------------------------------- | | `providerRpc` | `string` | JSON-RPC endpoint for blockchain queries | | `sequenceKey?` | `number` | Optional sequence key for parallel nonce channels (default: 0) | | key | type | description | | :------ | :------- | :----------------------------------------------------- | | `nonce` | `bigint` | The fully constructed nonce (sequenceKey << 64) \| seq | #### Source code[​](#source-code-19 "Direct link to Source code") [getNonce](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1185) ## Utility Methods[​](#utility-methods "Direct link to Utility Methods") ### createAccountCallData[​](#createaccountcalldata "Direct link to createAccountCallData") Static method. Encodes calldata for `executeUserOp` with BatchedCall format. All transactions (even single ones) go through the same BatchedCall path. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const transactions = [ { to: "0x...", value: 0n, data: "0x..." }, { to: "0x...", value: 1000000000000000000n, data: "0x" }, ]; const callData = Calibur7702Account.createAccountCallData(transactions); // With revertOnFailure disabled (individual calls can fail without reverting the batch) const callDataNoRevert = Calibur7702Account.createAccountCallData(transactions, false); ``` | key | type | description | | :----------------- | :------------------------ | :------------------------------------------------------------------- | | `transactions` | `SimpleMetaTransaction[]` | One or more transactions to encode into BatchedCall format | | `revertOnFailure?` | `boolean` | Whether to revert the entire batch if any call fails (default: true) | | key | type | description | | :--------- | :------- | :---------------------------------------------- | | `callData` | `string` | Encoded calldata for the executeUserOp function | #### Source code[​](#source-code-20 "Direct link to Source code") [createAccountCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L302) ### wrapSignature[​](#wrapsignature "Direct link to wrapSignature") Static method. Wraps a raw ECDSA signature in Calibur's signature format: `abi.encode(bytes32 keyHash, bytes signature, bytes hookData)`. Use this when signing externally (e.g., with viem, hardware wallet, MPC) to avoid manually ABI-encoding the wrapped signature. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; // Root key hash (bytes32 zero) for the EOA's own key const ROOT_KEY_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; const rawEcdsaSignature = "0x..."; // 65-byte ECDSA signature // Wrap for root key const wrappedSignature = Calibur7702Account.wrapSignature( ROOT_KEY_HASH, rawEcdsaSignature, ); // Wrap for a secondary key with hook data const wrappedWithHook = Calibur7702Account.wrapSignature( "0x...secondary-key-hash", rawEcdsaSignature, "0x...hook-data", ); ``` | key | type | description | | :------------- | :------- | :------------------------------------------------ | | `keyHash` | `string` | The key hash (use 0x00...00 for the EOA root key) | | `rawSignature` | `string` | The raw ECDSA signature (65 bytes, hex-encoded) | | `hookData?` | `string` | Optional hook data (default: "0x") | | key | type | description | | :---------- | :------- | :------------------------------------------------------- | | `signature` | `string` | Hex-encoded wrapped signature ready for userOp.signature | #### Source code[​](#source-code-21 "Direct link to Source code") [wrapSignature](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L172) ### createDummyWebAuthnSignature[​](#createdummywebauthnsignature "Direct link to createDummyWebAuthnSignature") Static method. Creates a dummy WebAuthn signature for gas estimation when signing with a passkey. The key hash must correspond to an actually registered key on the account, otherwise the contract's `validateUserOp` will revert. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; const keyHash = Calibur7702Account.getKeyHash(webAuthnKey); const dummySignature = Calibur7702Account.createDummyWebAuthnSignature(keyHash); // Pass as override during UserOperation creation for accurate gas estimation const userOperation = await smartAccount.createUserOperation( transactions, nodeRpc, bundlerRpc, { dummySignature }, ); ``` | key | type | description | | :-------- | :------- | :----------------------------------------------------------------------------------------------------- | | `keyHash` | `string` | The key hash of a registered passkey (from getKeyHash). Must correspond to an actually registered key. | | key | type | description | | :---------- | :------- | :-------------------------------------------------------------------------------------- | | `signature` | `string` | A dummy signature suitable for passing as dummySignature override during gas estimation | #### Source code[​](#source-code-22 "Direct link to Source code") [createDummyWebAuthnSignature](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L133) ### getUserOperationHash[​](#getuseroperationhash "Direct link to getUserOperationHash") Computes the UserOperation hash for this account's EntryPoint. Convenience wrapper that automatically uses this account's EntryPoint address. * example.ts * Param Types * Return Type example.ts ``` const userOpHash = smartAccount.getUserOperationHash( userOperation, 11155111n // chain ID ); console.log("UserOperation hash:", userOpHash); // Use this hash as the challenge for WebAuthn assertions ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The UserOperation to hash | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `chainId` | `bigint` | Target chain ID | | key | type | description | | :----- | :------- | :------------------------------------- | | `hash` | `string` | The UserOperation hash as a hex string | #### Source code[​](#source-code-23 "Direct link to Source code") [getUserOperationHash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L236) ### prependTokenPaymasterApproveToCallData[​](#prependtokenpaymasterapprovetocalldata "Direct link to prependTokenPaymasterApproveToCallData") Prepends a token `approve` call to existing calldata for a token paymaster. Decodes the existing BatchedCall, prepends an ERC-20 approve transaction, and re-encodes. * example.ts * Param Types * Return Type example.ts ``` const callDataWithApproval = smartAccount.prependTokenPaymasterApproveToCallData( userOperation.callData, "0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address "0x...", // paymaster address 1000000n // approve amount (1 USDC) ); userOperation.callData = callDataWithApproval; ``` | key | type | description | | :----------------- | :------- | :------------------------------------------------------ | | `callData` | `string` | Existing encoded calldata (executeUserOp format) | | `tokenAddress` | `string` | Address of the ERC-20 token contract to approve | | `paymasterAddress` | `string` | Address of the paymaster contract to approve as spender | | `approveAmount` | `bigint` | Amount of tokens to approve for the paymaster | | key | type | description | | :--------- | :------- | :---------------------------------------------------- | | `callData` | `string` | Re-encoded calldata with the token approval prepended | #### Source code[​](#source-code-24 "Direct link to Source code") [prependTokenPaymasterApproveToCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1370) ### createInvalidateNonceMetaTransaction[​](#createinvalidatenoncemetatransaction "Direct link to createInvalidateNonceMetaTransaction") Static method. Creates a meta-transaction to invalidate nonces up to a given value. All nonces below the specified value become invalid, preventing replay of previously signed but unsubmitted UserOperations. * example.ts * Param Types * Return Type example.ts ``` import { Calibur7702Account } from "abstractionkit"; // Invalidate all nonces below 100 const invalidateTx = Calibur7702Account.createInvalidateNonceMetaTransaction(100n); const userOperation = await smartAccount.createUserOperation( [invalidateTx], nodeRpc, bundlerRpc, ); ``` | key | type | description | | :--------- | :------- | :---------------------------------------------------------- | | `newNonce` | `bigint` | The new nonce value (all nonces below this are invalidated) | | key | type | description | | :---------------------------------------- | :------- | :----------------------------------------------------------------------------- | | `transaction` | `object` | A SimpleMetaTransaction that calls invalidateNonce(uint256) | | `transaction.SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Calibur7702Account. | | `transaction.SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `transaction.SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `transaction.SimpleMetaTransaction.data` | `string` | The call data for the transaction | #### Source code[​](#source-code-25 "Direct link to Source code") [createInvalidateNonceMetaTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Calibur/Calibur7702Account.ts#L1155) --- # External Signers `ExternalSigner` is the AbstractionKit interface for signing UserOperations without passing raw private keys to the SDK. It plugs viem, ethers, hardware wallets, HSMs, MPC services, passkeys, or any custom signing backend into the same API. Available since AbstractionKit v0.3.2. ## Quick start[​](#quick-start "Direct link to Quick start") ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromViem } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const signer = fromViem(privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)); const safe = SafeAccount.initializeNewAccount([signer.address]); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` ## Picking an adapter[​](#picking-an-adapter "Direct link to Picking an adapter") AbstractionKit ships built-in adapters for the common signing sources, plus an open `ExternalSigner` interface for custom backends. | Signing source | Adapter | Use when | | ---------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------- | | viem `LocalAccount` | `fromViem(localAccount)` | The project uses viem. Works for `privateKeyToAccount`, `toAccount`, and wagmi connector accounts. | | viem `WalletClient` | `fromViemWalletClient(client)` | The signer is a browser or injected JSON-RPC wallet (typed-data signing only). | | ethers `Wallet` / `HDNodeWallet` | `fromEthersWallet(wallet)` | The project already uses ethers v6. | | Raw private key string | `fromPrivateKey(privateKey)` | You want every owner (private key, HSM, hardware) to flow through the same async interface. | | Safe passkey owner | `fromSafeWebauthn(params)` | Signing a Safe UserOperation with a WebAuthn credential. | | HSM, MPC, hardware wallet, remote signer | custom `ExternalSigner` | Implement the `signHash` and/or `signTypedData` methods your service exposes. | If your project already uses viem, use `fromViem`; if it uses ethers, use `fromEthersWallet`. You don't need to install both. ## Account compatibility[​](#account-compatibility "Direct link to Account compatibility") Each account class exposes its own signing method and accepts a different subset of adapters. The returned signature is already formatted for the account, so assign it directly to `userOperation.signature`. | Account | Method | Compatible adapters | | --------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `SafeMultiChainSigAccountV1` (single UserOperation) | `signUserOperationWithSigners(userOperation, signers, chainId)` | `fromViem`, `fromViemWalletClient`, `fromEthersWallet`, `fromPrivateKey`, `fromSafeWebauthn`, custom | | `SafeAccountV0_2_0`, `SafeAccountV0_3_0`, `SafeAccountV1_5_0_M_0_3_0` | `signUserOperationWithSigners(userOperation, signers, chainId)` | `fromViem`, `fromViemWalletClient`, `fromEthersWallet`, `fromPrivateKey`, `fromSafeWebauthn`, custom | | `SafeMultiChainSigAccountV1` (multi-op Merkle path) | `signUserOperationsWithSigners(userOperationsToSign, signers)` | `fromViem`, `fromViemWalletClient`, `fromEthersWallet`, `fromPrivateKey`, `fromSafeWebauthn`, custom | | `Simple7702Account`, `Simple7702AccountV09` | `signUserOperationWithSigner(userOperation, signer, chainId)` | `fromViem`, `fromViemWalletClient`, `fromEthersWallet`, `fromPrivateKey`, custom | | `Calibur7702Account` | `signUserOperationWithSigner(userOperation, signer, chainId)` | `fromViem`, `fromViemWalletClient`, `fromEthersWallet`, `fromPrivateKey`, custom | `SafeAccountV1_5_0_M_0_3_0` is the Safe v1.5.0 contracts variant with 4337 module 0.3.0: it extends `SafeAccountV0_3_0` (EntryPoint v0.7) but deploys with the Safe L2 v1.5.0 singleton instead of v1.4.1. See its [source](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV1_5_0_M_0_3_0.ts#L28). For Safe accounts, pass one signer per owner required by the threshold. For multi-chain Safe signing, pass the UserOperations and chain IDs together: ``` const signatures = await safe.signUserOperationsWithSigners( [ { chainId: 11155111n, userOperation: op1 }, { chainId: 84532n, userOperation: op2 }, ], [signer], ); ``` Under the hood, every signer implements `signHash`, `signTypedData`, or both, and each account picks the scheme it prefers. Since `v0.3.8`, `Simple7702Account`, `Simple7702AccountV09`, `Calibur7702Account`, and `SafeMultiChainSigAccountV1` all accept typed-data-only signers where the account supports EIP-712 signing. Capability mismatches throw offline with an actionable error before any wallet prompt is shown. `fromSafeWebauthn` is Safe-specific because it produces an EIP-1271 contract signature using Safe's WebAuthn verifier. ## Adapter recipes[​](#adapter-recipes "Direct link to Adapter recipes") ### viem local account[​](#viem-local-account "Direct link to viem local account") `fromViem` is the preferred viem adapter for scripts, backends, and any local private-key account. It supports both hash and typed-data signing. ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromViem } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const localAccount = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const signer = fromViem(localAccount); const safe = SafeAccount.initializeNewAccount([signer.address]); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` Full example: [`signer/fromViem.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/signer/fromViem.ts). ### viem WalletClient[​](#viem-walletclient "Direct link to viem WalletClient") Use `fromViemWalletClient` for browser wallets and injected JSON-RPC wallets. It exposes `signTypedData`, so it works with Safe accounts, Safe multi-chain signing, Simple7702 accounts, and Calibur. ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromViemWalletClient } from "abstractionkit"; import { createWalletClient, custom } from "viem"; const walletClient = createWalletClient({ account: userAddress, transport: custom(window.ethereum), }); const signer = fromViemWalletClient(walletClient); const safe = SafeAccount.initializeNewAccount([signer.address]); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` Full example: [`signer/fromViemWalletClient.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/signer/fromViemWalletClient.ts). ### ethers Wallet[​](#ethers-wallet "Direct link to ethers Wallet") `fromEthersWallet` accepts any ethers v6 `Wallet` or `HDNodeWallet`. ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromEthersWallet } from "abstractionkit"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.PRIVATE_KEY as string); const signer = fromEthersWallet(wallet); const safe = SafeAccount.initializeNewAccount([signer.address]); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` Full example: [`signer/fromEthersWallet.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/signer/fromEthersWallet.ts). ### Raw private key[​](#raw-private-key "Direct link to Raw private key") For a single-owner private-key setup, the sync `signUserOperation` method is the shortest path and needs no adapter: ``` userOperation.signature = smartAccount.signUserOperation(userOperation, [privateKey], chainId); ``` Use `fromPrivateKey` when you want every owner (private key, HSM, hardware) to share the same async `ExternalSigner` interface in a multi-owner setup: ``` import { fromPrivateKey } from "abstractionkit"; const signer = fromPrivateKey(process.env.PRIVATE_KEY as `0x${string}`); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` ### Custom signer[​](#custom-signer "Direct link to Custom signer") Use a custom `ExternalSigner` for HSMs, MPC providers, hardware wallets, secure enclaves, or remote signing APIs. Implement only the methods your service supports. ``` import type { ExternalSigner } from "abstractionkit"; const signer: ExternalSigner = { address: deviceAddress as `0x${string}`, signHash: async (hash) => { return (await hsmClient.signHash(hash)) as `0x${string}`; }, signTypedData: async (typedData) => { return (await mpcClient.signTypedData(typedData)) as `0x${string}`; }, }; ``` Full example: [`signer/customSigner.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/signer/customSigner.ts). ### Safe WebAuthn (passkeys)[​](#safe-webauthn-passkeys "Direct link to Safe WebAuthn (passkeys)") `fromSafeWebauthn` is Safe-specific. It returns a contract-signature signer, sets `type: "contract"`, and handles Safe's WebAuthn signature encoding. ``` import { SafeMultiChainSigAccountV1, fromSafeWebauthn } from "abstractionkit"; const signer = fromSafeWebauthn({ publicKey: { x, y }, isInit: userOperation.nonce === 0n, accountClass: SafeMultiChainSigAccountV1, getAssertion: async (challenge) => { return getWebauthnAssertion(challenge); }, }); userOperation.signature = await safe.signUserOperationWithSigners( userOperation, [signer], chainId, ); ``` When creating the UserOperation, pass `expectedSigners: [{ x, y }]` so gas estimation uses the WebAuthn dummy signature size instead of the EOA dummy signature size. See the [Passkeys guide](https://docs.candide.dev/wallet/plugins/passkeys.md#sign-with-fromsafewebauthn) and the [`passkeys/index.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/passkeys/index.ts) example. ## ExternalSigner shape[​](#externalsigner-shape "Direct link to ExternalSigner shape") ``` type ExternalSigner = { address: `0x${string}` } & ( | { signHash: (hash: `0x${string}`, context: SignContext) => `0x${string}` | Promise<`0x${string}`>; signTypedData?: (data: TypedData, context: SignContext) => `0x${string}` | Promise<`0x${string}`>; } | { signHash?: (hash: `0x${string}`, context: SignContext) => `0x${string}` | Promise<`0x${string}`>; signTypedData: (data: TypedData, context: SignContext) => `0x${string}` | Promise<`0x${string}`>; } ) & { type?: "ecdsa" | "contract"; }; ``` The discriminated union enforces at compile time that every signer implements at least one of `signHash` or `signTypedData`. The optional `type` field defaults to `"ecdsa"`. Safe accounts use `"contract"` for dynamic-length EIP-1271 signatures, including WebAuthn verifier contracts. Both `signHash` and `signTypedData` may return either a signature directly or a `Promise` of one (`Hex | Promise`). A local-key signer can return synchronously without wrapping the result in a `Promise`; async signers (HSMs, MPC, remote APIs) work unchanged. Canonical source: [`src/signer/types.ts`](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/signer/types.ts#L157). ## End-to-end examples[​](#end-to-end-examples "Direct link to End-to-end examples") * [Signer adapter examples](https://github.com/candidelabs/abstractionkit-examples/tree/main/signer) * [Simple7702 EntryPoint v0.9 external signer](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/05-external-signer.ts) * [Calibur external signer](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/04-external-signer.ts) * [Safe Unified Account external signer](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner-with-external-signer.ts) * [Safe passkeys example](https://github.com/candidelabs/abstractionkit-examples/blob/main/passkeys/index.ts) --- # Introduction to AbstractionKit ![image](/assets/images/abstractionkit-poster-1b4b91d16d122bb536d13ec0a11b2866.png) A TypeScript library for building and sending ERC-4337 UserOperations with first-class support for Safe Accounts, Calibur Accounts, and Simple EIP-7702 Accounts. New Safe examples default to `SafeMultiChainSigAccountV1` on EntryPoint v0.9, and new EIP-7702 Simple Account examples default to `Simple7702AccountV09`. Pick by what the user already has: for a new wallet, create a `SafeMultiChainSigAccountV1`; to upgrade an existing EOA in place and keep its address, use `Simple7702AccountV09` via EIP-7702. AbstractionKit abstracts the complexities of the ERC-4337 standard while providing developers with maximum flexibility to build Smart Wallets. Use Safe Accounts, construct callData, estimate and sponsor gas (fully or with ERC-20 tokens), and send UserOperations to Bundlers - all with full type safety. * AbstractionKit is agnostic of: * Ethereum interface libraries: ethers, web3.js, viem/wagmi * Bundler implementation: Plug and play from any bundler provider * Paymaster services: use any 3rd party paymaster, or build your own ## Current Defaults[​](#current-defaults "Direct link to Current Defaults") | Integration | Recommended class | EntryPoint | Notes | | -------------------------------------------- | ---------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Safe account, single-chain or multichain | `SafeMultiChainSigAccountV1` | v0.9 | Same class works for normal single-chain Safe UserOperations and Safe Unified Account multichain signing. | | Legacy Safe v0.7 compatibility | `SafeAccountV0_3_0` | v0.7 | Use when you need the older Safe 4337 module v0.3.0. | | Legacy Safe v0.6 compatibility | `SafeAccountV0_2_0` | v0.6 | Use only for older Safe 4337 module v0.2.0 integrations or migration paths. | | EIP-7702 Simple Account | `Simple7702AccountV09` | v0.9 | Recommended for new EOA delegation flows. | | EIP-7702 Simple Account v0.8 | `Simple7702Account` | v0.8 | Use for legacy integrations and v0.8 to v0.9 migration flows. | | EIP-7702 account with passkeys and multi-key | `Calibur7702Account` | v0.8 | The Calibur account by Uniswap. Adds passkey authentication, multi-key management, and per-key hooks to EIP-7702 EOA upgrades. See [Calibur Account](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md). | | Standard sponsorship or ERC-20 token gas | `Erc7677Paymaster` | v0.6-v0.9 | Recommended for new one-shot sponsorship and token-gas integrations. It auto-detects providers such as Candide and Pimlico and handles provider-specific metadata, supported token data, exchange rates, and paymaster addresses internally. | | Candide paymaster flows | `CandidePaymaster` | v0.6-v0.9 | Candide-specific client. It also supports one-shot sponsorship, but use it mainly when you need Candide-specific APIs or the v0.9 `commit -> sign -> finalize` flow that reduces wallet or remote-signer latency. | ## Why AbstractionKit[​](#why-abstractionkit "Direct link to Why AbstractionKit") Smart contract wallets are challenging to build. We learned this firsthand while developing [Candide Wallet](https://candidewallet.com), one of the first Smart Wallets. We wrote smart contracts, conducted audits, built a compliant Bundler, and designed paymaster gas features. Then we realized we had invested more resources in infrastructure than in our actual product—the consumer-facing application. The infrastructure we built wasn't just useful for us; it's what all developers need. We refocused our efforts to create AbstractionKit: an SDK that enables you to build smart account applications in days, not months. ### Peak Efficiency[​](#peak-efficiency "Direct link to Peak Efficiency") AbstractionKit is engineered for optimal performance by minimizing HTTPS requests to core infrastructure (Node, Bundler, Paymaster). We achieve this by using cryptographic techniques to calculate complex smart account signatures offline. Fewer HTTPS requests reduce your infrastructure costs while delivering a significantly faster user experience. Don't just take our word for it—verify the results yourself by comparing AbstractionKit's performance against other account abstraction libraries in the [AA-SDK-Benchmark](https://aa-sdk-benchmark.on-fleek.app/). ### Secure Smart Accounts[​](#secure-smart-accounts "Direct link to Secure Smart Accounts") AbstractionKit supports [Safe Accounts](https://docs.candide.dev/wallet/abstractionkit/safe-account.md), the battle-tested smart accounts securing billions of dollars in assets across Ethereum and other EVM-compatible chains. We chose Safe for its unparalleled security track record, extensive audits, and widespread community trust, making it the industry standard for secure smart contract wallets. For EIP-7702 EOA upgrades, AbstractionKit supports [Calibur Account](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md) (by Uniswap, audited by OpenZeppelin and Cantina) with passkey authentication, multi-key management, and per-key hooks, as well as [Simple7702Account](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account.md) for lightweight single-signer upgrades. ### Extensible[​](#extensible "Direct link to Extensible") Each core component—Bundler, Paymaster, and Account—is built as a separate, modular unit, allowing for tailored configurations and extensions. Build custom smart account plugins, integrate social logins through third-party authentication services, or use alternative smart account implementations beyond Safe and Simple Accounts. ### Vendor-Agnostic[​](#vendor-agnostic "Direct link to Vendor-Agnostic") AbstractionKit strictly adheres to Ethereum Account Abstraction standards, ensuring your product works with Candide's infrastructure and any other compliant provider, including Alchemy, Pimlico, and Etherspot. While Candide is the longest-standing provider in this space, our commitment to open standards means your product's longevity isn't tied to any single vendor. If Candide ceased operations, your application would continue functioning with alternative providers. Candide's Voltaire bundler maintains the highest compliance in the industry, as verified on the [ERC-4337 Bundlers website](https://www.erc4337.io/bundlers). ## Installation[​](#installation "Direct link to Installation") * npm * yarn ``` npm i abstractionkit ``` ``` yarn add abstractionkit ``` ## Current SDK version[​](#current-sdk-version "Direct link to Current SDK version") The docs track AbstractionKit v0.4.0. Source links throughout the reference pages point at the `v0.4.0` tag. To install the documented version explicitly: ``` npm install abstractionkit@0.4.0 ``` ## Resources[​](#resources "Direct link to Resources") * **Source code**: [github.com/candidelabs/abstractionkit](https://github.com/candidelabs/abstractionkit) * **Full changelog**: [github.com/candidelabs/abstractionkit/blob/main/CHANGELOG.md](https://github.com/candidelabs/abstractionkit/blob/main/CHANGELOG.md) — exhaustive list of every release, including breaking changes and migration snippets. Release highlights also appear in the [blog](https://docs.candide.dev/blog). * **Examples**: [github.com/candidelabs/abstractionkit-examples](https://github.com/candidelabs/abstractionkit-examples) * **npm**: [npmjs.com/package/abstractionkit](https://www.npmjs.com/package/abstractionkit) --- # Paymaster AbstractionKit includes two paymaster clients. `CandidePaymaster` can sponsor one-shot UserOperations, but new integrations should prefer the standard `Erc7677Paymaster` for one-shot sponsorship and ERC-20 token gas flows. `Erc7677Paymaster` auto-detects supported providers such as Candide and Pimlico and handles provider-specific metadata, supported token data, exchange rates, and paymaster addresses internally. Use `CandidePaymaster` when you need Candide-specific APIs or the EntryPoint v0.9 two-phase `commit -> sign -> finalize` flow. **Supports:** * Gas sponsorship through Gas Policies * ERC-20 Token Sponsorship * Multi-EntryPoint ## Which Paymaster Client to Use[​](#which-paymaster-client-to-use "Direct link to Which Paymaster Client to Use") Both clients are supported; neither is deprecated. `Erc7677Paymaster` implements the standard [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) protocol, so integrations stay portable across providers (Candide, Pimlico, Alchemy, or self-hosted) with no vendor lock-in. `CandidePaymaster` is the dedicated client for Candide-hosted paymasters and adds features the standard does not cover, such as parallel signing phases. | Flow | Recommended client | Method | | ----------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------ | | Sponsored gas, one-shot | `Erc7677Paymaster` | Recommended standard path: `createPaymasterUserOperation(..., context?)` | | ERC-20 token gas | `Erc7677Paymaster` | `createPaymasterUserOperation(..., { token })` | | Provider metadata and supported token details for Candide/Pimlico | `Erc7677Paymaster` | Handled automatically from the paymaster URL | | Candide-specific one-shot sponsorship | `CandidePaymaster` | Supported via `createSponsorPaymasterUserOperation(...)` | | Two-phase paymaster signing to reduce user waiting time | `CandidePaymaster` | `createSponsorPaymasterUserOperation(..., { signingPhase })` | ## Usage[​](#usage "Direct link to Usage") ### Import[​](#import "Direct link to Import") ``` import { CandidePaymaster } from "abstractionkit"; ``` ### How to Use[​](#how-to-use "Direct link to How to Use") Initialize a Paymaster with your RPC URL. Get an API key from the [dashboard](https://dashboard.candide.dev). paymaster.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC = "https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); ``` Then consume Paymaster methods: ``` const supportedEntryPoints = await paymaster.getSupportedEntrypoints(); ``` ## Methods[​](#methods "Direct link to Methods") ### createSponsorPaymasterUserOperation[​](#createsponsorpaymasteruseroperation "Direct link to createSponsorPaymasterUserOperation") Returns the paymaster data if the the userOperation has a Gas Policy. Otherwise it returns an error message. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. #### Usage[​](#usage-1 "Direct link to Usage") * example.ts * Param Type * Return Type example.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC = "https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const sponsorshipPolicyId = '1234'; // Use createUserOperation() to help you construct a userOp const userOperation = smartAccount.createUserOperation(..) const { userOperation: sponsoredUserOperation, sponsorMetadata } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, sponsorshipPolicyId, // optional ); ``` | key | type | description | | :---------------------------- | :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `smartAccount` | `SmartAccountWithEntrypoint` | The smart account instance. Used to resolve the EntryPoint address. | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | UserOperation to Sponsor. Supports EntryPoint v0.6, v0.7, v0.8, and v0.9 | | `bundlerRpc` | `string` | Bundler URL to estimate the gas | | `sponsorshipPolicyId` | `string?` | Optional sponsorship policy id if using a Private Gas Policy | | `context` | `object` | Optional additional context to pass to the paymaster RPC (token, signingPhase). | | `context.token` | `string?` | ERC-20 token address, if paying gas in erc-20 tokens (optional) | | `context.sponsorshipPolicyId` | `string?` | Sponsorship Policy ID if using a private gas policy (optional) | | `context.signingPhase` | `"commit" \| "finalize"?` | Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). Use "commit" to request initial paymaster fields and gas limits before the user signs, then "finalize" to attach the final paymaster signature after. | | `overrides` | `GasPaymasterUserOperationOverrides?` | Optional overrides for gas limits, multipliers, entrypoint, and state overrides used during gas estimation. | | key | type | description | | :---------------------------- | :------------------------------------------------------------------------- | :------------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | UserOperation with paymaster data included | | `sponsorMetadata` | `object` | Information about the Gas Policy Sponsor | | `sponsorMetadata.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsorMetadata.description` | `string` | A short description of the sponsor | | `sponsorMetadata.url` | `string` | Website of the Sponsor | | `sponsorMetadata.icons` | `string[]` | Logos or Icons that the Sponsor is using to promote their Gas Policy | #### Source code[​](#source-code "Direct link to Source code") [createSponsorPaymasterUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L571) ### fetchSupportedERC20TokensAndPaymasterMetadata[​](#fetchsupportederc20tokensandpaymastermetadata "Direct link to fetchSupportedERC20TokensAndPaymasterMetadata") Returns a promise with the supported erc20 tokens and their exchange rate, along with the paymaster metadata #### Usage[​](#usage-2 "Direct link to Usage") * example.ts * Param Types * Response Type example.ts ``` import { CandidePaymaster, SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const paymaster = new CandidePaymaster(paymasterRPC); const supportedERC20Tokens = await paymaster.fetchSupportedERC20TokensAndPaymasterMetadata(SafeAccount.DEFAULT_ENTRYPOINT_ADDRESS); ``` | key | type | description | | :----------- | :-------- | :----------------------------------- | | `entrypoint` | `string?` | EntryPoint Address. Defaults to V0.7 | | key | type | description | | :------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ | | `SupportedERC20TokensAndMetadataWithExchangeRate` | `Promise` | A promise with the list of supported erc20 tokens and their exchange rate | SupportedERC20TokensAndMetadataV7WithExchangeRate | key | type | description | | :---------------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------- | | `paymasterMetadata` | `object` | The Paymaster metadata | | `paymasterMetadata.name` | `string` | The name of the Paymaster. | | `paymasterMetadata.description` | `string` | A brief description of the Paymaster. | | `paymasterMetadata.icons` | `string[]` | An array of icon URLs representing the Paymaster's brand or logo. | | `paymasterMetadata.address` | `string` | The contract address of the Paymaster. | | `paymasterMetadata.sponsoredEventTopic` | `string` | The event topic that will be emitted when a UserOperation is sponsored by the Paymaster. | | `paymasterMetadata.dummyPaymasterAndData` | `object` | Dummy Paymaster data used for gas estimation. | | `paymasterMetadata.dummyPaymasterAndData.paymaster` | `string` | The address of the Paymaster. | | `paymasterMetadata.dummyPaymasterAndData.paymasterVerificationGasLimit` | `bigint` | The amount of gas allocated for the Paymaster verification step. | | `paymasterMetadata.dummyPaymasterAndData.paymasterPostOpGasLimit` | `bigint` | The amount of gas allocated for the Paymaster post-operation step. | | `paymasterMetadata.dummyPaymasterAndData.paymasterData` | `string` | Encoded data for the Paymaster. | | `tokens` | `object` | Supported erc20 tokens with Exchange Rate | | `tokens.name` | `string` | The name of the ERC20 token. | | `tokens.symbol` | `string` | The symbol representing the ERC20 token. | | `tokens.address` | `string` | The contract address of the ERC20 token. | | `tokens.decimals` | `number` | The number of decimal places for the token. | | `tokens.exchangeRate` | `bigint` | The exchange rate of the token. | SupportedERC20TokensAndMetadataV6WithExchangeRate | key | type | description | | :---------------------------------------- | :--------- | :--------------------------------------------------------------------------------------- | | `paymasterMetadata` | `object` | The Paymaster metadata | | `paymasterMetadata.name` | `string` | The name of the Paymaster. | | `paymasterMetadata.description` | `string` | A brief description of the Paymaster. | | `paymasterMetadata.icons` | `string[]` | An array of icon URLs representing the Paymaster's brand or logo. | | `paymasterMetadata.address` | `string` | The contract address of the Paymaster. | | `paymasterMetadata.sponsoredEventTopic` | `string` | The event topic that will be emitted when a UserOperation is sponsored by the Paymaster. | | `paymasterMetadata.dummyPaymasterAndData` | `string` | Dummy Paymaster data used for gas estimation. | | `tokens` | `object` | Supported erc20 tokens with Exchange Rate | | `tokens.name` | `string` | The name of the ERC20 token. | | `tokens.symbol` | `string` | The symbol representing the ERC20 token. | | `tokens.address` | `string` | The contract address of the ERC20 token. | | `tokens.decimals` | `number` | The number of decimal places for the token. | | `tokens.exchangeRate` | `bigint` | The exchange rate of the token. | Example Response ``` { tokens: [ { name: 'USD Coin', symbol: 'USDC', address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', decimals: 6, exchangeRate: 123n }, { name: 'Tether USDT', symbol: 'USDT', address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', decimals: 6, exchangeRate: 123n }, { name: 'Dai Stablecoin', symbol: 'DAI', address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', decimals: 18, exchangeRate: 123n } ], paymasterMetadata: { name: 'CANDIDE Paymaster', description: 'CANDIDE Paymaster a fast, secure and feature-rich 4337 Paymaster', icons: [], address: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', sponsoredEventTopic: '0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471', dummyPaymasterAndData: { paymaster: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', paymasterVerificationGasLimit: '0xffff', paymasterPostOpGasLimit: '0xffff', paymasterData: '0x00010000000000ffff000000000000000000000000000000000000000000000000000000000000ffff010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011c' } } } ``` ### createTokenPaymasterUserOperation[​](#createtokenpaymasteruseroperation "Direct link to createTokenPaymasterUserOperation") Estimates gas limits and returns the user operation with the paymaster data for ERC-20 Token sponsorship. Token Approval Reset Some tokens like USDT do not allow changing a non-zero allowance directly. For known tokens (e.g. USDT on Ethereum mainnet), the SDK automatically prepends an `approve(0)` call before the actual approval. You can also manually trigger this behavior for other tokens by setting `resetApproval: true` in the overrides. The `overrides` parameter also accepts an `entrypoint` field, which allows you to manually specify the EntryPoint address instead of relying on auto-detection from the UserOperation structure. #### Usage[​](#usage-3 "Direct link to Usage") * example.ts * Param Types * Response Type ``` import { SafeMultiChainSigAccountV1 as SafeAccount, CandidePaymaster } from "abstractionkit"; const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT test token const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const ownerPublicAddress = "0x2Ef844456580b6e1E22e1D584EBbC2467D9298B2" const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) // Use createUserOperation() to help you construct a userOp let userOperation = smartAccount.createUserOperation(..); const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const { userOperation: sponsoredUserOperation, tokenQuote } = await paymaster.createTokenPaymasterUserOperation( smartAccount, userOperation, erc20TokenAddress, bundlerRPC, ); userOperation = sponsoredUserOperation; ``` | key | type | description | | :----------------------------------------------------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `smartAccount` | `PrependTokenPaymasterApproveAccount` | The smart account object that created the target userOperation. Must implement prependTokenPaymasterApproveToCallData so the paymaster token approval can be prepended to the callData. | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | The userOperation to sponsor gas with erc-20 paymaster | | `tokenAddress` | `string` | The address of the token to approve. | | `bundlerRpc` | `string` | The Bundler RPC to estimate the gas | | `context` | `object` | Optional additional context to pass to the paymaster RPC. \`token\` is always derived from \`tokenAddress\`, so pass \`undefined\` unless you need other fields (e.g. \`signingPhase\`). | | `context.token` | `string?` | ERC-20 token address, if paying gas in erc-20 tokens (optional) | | `context.sponsorshipPolicyId` | `string?` | Sponsorship Policy ID if using a private gas policy (optional) | | `context.signingPhase` | `"commit" \| "finalize"?` | Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). Use "commit" to request initial paymaster fields and gas limits before the user signs, then "finalize" to attach the final paymaster signature after. | | `overrides` | `object` | Overrides for gas limits, multipliers, entrypoint, and the approval reset behavior. | | `overrides.entrypoint` | `string?` | Set the EntryPoint address instead of auto-detecting it from the UserOperation structure. | | `overrides.resetApproval` | `boolean?` | When true, prepend an approve(0) call before the actual token approval. Required for tokens like USDT that do not allow changing a non-zero allowance directly. Automatically applied for known tokens (e.g. USDT on Ethereum mainnet). | | `overrides.callGasLimit` | `bigint` | Set the callGasLimit instead of estimating gas using the bundler. | | `overrides.verificationGasLimit` | `bigint` | Set the verificationGasLimit instead of estimating gas using the bundler. | | `overrides.preVerificationGas` | `bigint` | Set the preVerificationGas instead of estimating gas using the bundler. | | `overrides.callGasLimitPercentageMultiplier` | `number` | Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler. | | `overrides.verificationGasLimitPercentageMultiplier` | `number` | Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler. | | `overrides.preVerificationGasPercentageMultiplier` | `number` | Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler. | | `overrides.stateOverrideSetType` | `object` | Pass state overrides for gas estimation, including balance, nonce, code, and state or stateDiff for the account at each address. | | `overrides.stateOverrideSetType.[address: string]` | `object` | Overrides for a specific address | | `overrides.stateOverrideSetType.[address: string].balance` | `bigint` | Override the balance of the address | | `overrides.stateOverrideSetType.[address: string].nonce` | `bigint` | Override the nonce of the address | | `overrides.stateOverrideSetType.[address: string].code` | `string` | Override the code of the address | | `overrides.stateOverrideSetType.[address: string].state` | `Dictionary` | Override the storage slots of the address | | `overrides.stateOverrideSetType.[address: string].stateDiff` | `Dictionary` | Apply state differences to the storage slots of the address | | key | type | description | | :------------------------ | :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | The userOperation with paymaster data and token approval prepended | | `tokenQuote` | `object` | The exchange rate and maximum token cost the paymaster applied to this UserOperation. Populated on the token-payment flow; absent when called under the \`signingPhase: "finalize"\` path (no cost recomputation). | | `tokenQuote.token` | `string` | ERC-20 token contract address used to pay gas | | `tokenQuote.exchangeRate` | `bigint` | Exchange rate scaled by 10^18 (1 ETH expressed in the token's smallest unit) | | `tokenQuote.tokenCost` | `bigint` | Maximum token cost charged for this UserOperation (token's smallest unit) | Example Response ``` { userOperation: { sender: '0xb8741a449d50ed0dcfe395287f85be152884c8d9', nonce: 10n, initCode: '0x', callData: '0x541d63c8...095ea7b3...', callGasLimit: 116807n, verificationGasLimit: 75441n, preVerificationGas: 50444n, maxFeePerGas: 66195658616n, maxPriorityFeePerGas: 120000n, paymasterAndData: '0x3fE285DcD76BCcE4Ac92d38A6F2F8E964041e020...', signature: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' }, tokenQuote: { token: '0xFa5854FBf9964330d761961F46565AB7326e5a3b', exchangeRate: 1001219705870085130n, tokenCost: 3391931975665260n, }, } ``` #### Source code[​](#source-code-1 "Direct link to Source code") [createTokenPaymasterUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L612) ### calculateUserOperationErc20TokenMaxGasCost[​](#calculateuseroperationerc20tokenmaxgascost "Direct link to calculateUserOperationErc20TokenMaxGasCost") Calculates the maximum gas cost in ERC-20 tokens for a given userOperation #### Usage[​](#usage-4 "Direct link to Usage") * example.ts * Param Types * Return Types ``` import { CandidePaymaster } from "abstractionkit"; const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT test token // Use createUserOperation() to help you construct a userOp const userOperation = smartAccount.createUserOperation(..) const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const cost = await paymaster.calculateUserOperationErc20TokenMaxGasCost( userOperation, erc20TokenAddress, ); ``` | key | type | description | | :------------------ | :------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | UserOperation to calculate cost for. Supports EntryPoint v0.6, v0.7, v0.8, and v0.9 | | `erc20TokenAddress` | `string` | ERC-20 Token Address | | key | type | description | | :----- | :---------------- | :---------------------------------------------------------------------- | | `cost` | `Promise` | Returns maximum amount that the operation will cost in the erc-20 token | Example Response ``` 3391931975665260 ``` #### Source code[​](#source-code-2 "Direct link to Source code") [calculateUserOperationErc20TokenMaxGasCost](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L712) ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") ### getPaymasterMetaData[​](#getpaymastermetadata "Direct link to getPaymasterMetaData") Returns the metadata associated with the Paymaster, along with dummyPaymasterAndData useful for gas estimates #### Usage[​](#usage-5 "Direct link to Usage") * example.ts * Param Type * Return Type example.ts ``` import { CandidePaymaster, SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const paymasterResult = await paymaster.getPaymasterMetaData(SafeAccount.DEFAULT_ENTRYPOINT_ADDRESS); ``` | key | type | description | | :----------- | :------- | :--------------------------------- | | `entrypoint` | `string` | Target EntryPoint Contract Address | | key | type | description | | :------------------ | :------------------------------------------------------------ | :---------------------------------------------------------------------------------- | | `paymastermetadata` | `Promise` | Returns a promise with the paymaster metadata associated with the target entrypoint | PaymasterMetadataV7 | key | type | description | | :---------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------- | | `name` | `string` | The name of the Paymaster. | | `description` | `string` | A brief description of the Paymaster. | | `icons` | `string[]` | An array of icon URLs representing the Paymaster's brand or logo. | | `address` | `string` | The contract address of the Paymaster. | | `sponsoredEventTopic` | `string` | The event topic that will be emitted when a UserOperation is sponsored by the Paymaster. | | `dummyPaymasterAndData` | `object` | Dummy Paymaster data used for gas estimation. | | `dummyPaymasterAndData.paymaster` | `string` | The address of the Paymaster. | | `dummyPaymasterAndData.paymasterVerificationGasLimit` | `bigint` | The amount of gas allocated for the Paymaster verification step. | | `dummyPaymasterAndData.paymasterPostOpGasLimit` | `bigint` | The amount of gas allocated for the Paymaster post-operation step. | | `dummyPaymasterAndData.paymasterData` | `string` | Encoded data for the Paymaster. | PaymasterMetadataV6 | key | type | description | | :---------------------- | :--------- | :--------------------------------------------------------------------------------------- | | `name` | `string` | The name of the Paymaster. | | `description` | `string` | A brief description of the Paymaster. | | `icons` | `string[]` | An array of icon URLs representing the Paymaster's brand or logo. | | `address` | `string` | The contract address of the Paymaster. | | `sponsoredEventTopic` | `string` | The event topic that will be emitted when a UserOperation is sponsored by the Paymaster. | | `dummyPaymasterAndData` | `string` | Dummy Paymaster data used for gas estimation. | Example Response ``` { name: 'CANDIDE Paymaster', description: 'CANDIDE Paymaster a fast, secure and feature-rich 4337 Paymaster', icons: [], address: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', sponsoredEventTopic: '0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471', dummyPaymasterAndData: { paymaster: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', paymasterVerificationGasLimit: '0xffff', paymasterPostOpGasLimit: '0xffff', paymasterData: '0x00010000000000ffff000000000000000000000000000000000000000000000000000000000000ffff010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011c' } } ``` #### source code[​](#source-code-3 "Direct link to source code") [getPaymasterMetaData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L334) ### getSupportedEntrypoints[​](#getsupportedentrypoints "Direct link to getSupportedEntrypoints") Returns the supported Entrypoints by the paymaster #### Usage[​](#usage-6 "Direct link to Usage") * example.ts * Return Type example.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const paymasterResult = await paymaster.getSupportedEntrypoints(); ``` | key | type | description | | :--------------------- | :------------------ | :------------------------------------------- | | `entrypoint addresses` | `Promise` | A promise of a list of entrypoints addresses | Example Response ``` [ '0x0000000071727De22E5E9d8BAf0edAc6f37da032', '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' ] ``` #### source code[​](#source-code-4 "Direct link to source code") [getSupportedEntrypoints](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L311) ### isSupportedERC20Token[​](#issupportederc20token "Direct link to isSupportedERC20Token") Checks if a particular ERC-20 token is accepted as gas payment by the paymaster. Returns a boolean #### Usage[​](#usage-7 "Direct link to Usage") * example.ts * Param Types * Response Type example.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC="https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT on sepolia testnet const isSupported = await paymaster.isSupportedERC20Token(erc20TokenAddress); ``` | key | type | description | | :------------------ | :------- | :---------------------------------------------------- | | `erc20TokenAddress` | `string` | ERC-20 Token Address | | `entrypoint?` | `string` | Target EntryPoint Address. Defaults to ENTRYPOINT\_V7 | | key | type | description | | :------------ | :-------- | :------------------------------------------- | | `isSupported` | `boolean` | Returns true if the erc20 token is supported | Example Response ``` true ``` #### Source[​](#source "Direct link to Source") [isSupportedERC20Token](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L351) ### getSupportedERC20TokenData[​](#getsupportederc20tokendata "Direct link to getSupportedERC20TokenData") Returns the token data given an erc20 address #### Usage[​](#usage-8 "Direct link to Usage") * example.ts * Param Types * Return Type example.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC = "https://api.candide.dev/public/v3/11155111"; const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const erc20TokenData = await paymaster.getSupportedERC20TokenData(erc20TokenAddress); ``` | key | type | description | | :------------------ | :------- | :---------------------------------------------------- | | `erc20TokenAddress` | `string` | ERC-20 Token Address | | `entrypoint?` | `string` | Target EntryPoint Address. Defaults to ENTRYPOINT\_V7 | | key | type | description | | :----------- | :---------------------------- | :----------- | | `ERC20Token` | `Promise` | ERC-20 Token | | key | type | description | | :--------- | :------- | :------------------------------------------ | | `name` | `string` | The name of the ERC20 token. | | `symbol` | `string` | The symbol representing the ERC20 token. | | `address` | `string` | The contract address of the ERC20 token. | | `decimals` | `number` | The number of decimal places for the token. | Example Response ``` { name: 'Candide Test Token', symbol: 'CTT', address: '0xFa5854FBf9964330d761961F46565AB7326e5a3b', decimals: 18, fee: 0n, exchangeRate: 1001219705870085130n } ``` #### Source code[​](#source-code-5 "Direct link to Source code") [getSupportedERC20TokenData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L367) ### fetchTokenPaymasterExchangeRate[​](#fetchtokenpaymasterexchangerate "Direct link to fetchTokenPaymasterExchangeRate") Fetches the current exchange rate for an ERC-20 token from the paymaster. The exchange rate represents the token cost per unit of native currency, used to calculate how much of the token is needed to cover gas fees. #### Usage[​](#usage-9 "Direct link to Usage") * example.ts * Param Types * Return Type example.ts ``` import { CandidePaymaster } from "abstractionkit"; const paymasterRPC = "https://api.candide.dev/public/v3/11155111"; const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC); const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT on sepolia testnet const exchangeRate = await paymaster.fetchTokenPaymasterExchangeRate(erc20TokenAddress); ``` | key | type | description | | :------------------ | :-------- | :------------------------------------------------------------------------------ | | `erc20TokenAddress` | `string` | ERC-20 Token Address | | `entrypoint` | `string?` | EntryPoint Address that the paymaster is supporting. Defaults to ENTRYPOINT\_V7 | | key | type | description | | :------------- | :------- | :-------------------------------------------- | | `exchangeRate` | `bigInt` | Returns the exchange rate to the native token | Example Response ``` 1001219705870085130n ``` #### Source code[​](#source-code-6 "Direct link to Source code") [fetchTokenPaymasterExchangeRate](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L750) ### createPaymasterUserOperation[​](#createpaymasteruseroperation "Direct link to createPaymasterUserOperation") Private on CandidePaymaster `CandidePaymaster.createPaymasterUserOperation` is a private helper, not part of the public API. Use [`createSponsorPaymasterUserOperation`](#createsponsorpaymasteruseroperation) for gas sponsorship or [`createTokenPaymasterUserOperation`](#createtokenpaymasteruseroperation) for ERC-20 token payment instead. `Erc7677Paymaster` exposes a public method with the same name; that one is the recommended entry point for the standard ERC-7677 flow and is documented in the [Erc7677Paymaster section](#erc7677paymaster) below. #### Source code[​](#source-code-7 "Direct link to Source code") [createPaymasterUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/CandidePaymaster.ts#L530) ## Erc7677Paymaster[​](#erc7677paymaster "Direct link to Erc7677Paymaster") `Erc7677Paymaster` is a provider-agnostic [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) paymaster client. It speaks `pm_getPaymasterStubData` and `pm_getPaymasterData`, so it works with any compliant provider (Candide, Pimlico, Alchemy, or a self-hosted paymaster). Provider is auto-detected from the RPC URL; for Candide and Pimlico it also fetches exchange rates and paymaster addresses automatically for ERC-20 token flows. For new one-shot sponsorship and ERC-20 gas flows, prefer `Erc7677Paymaster`. For Candide and Pimlico URLs, it handles provider-specific metadata and token quote details behind the scenes. `CandidePaymaster` also supports one-shot sponsorship, but it is most useful when you need Candide-specific APIs or two-phase paymaster signing with `signingPhase: "commit"` and `signingPhase: "finalize"`, which lets the user sign while the paymaster prepares its signature in parallel and reduces wallet or remote-signer latency. ### Usage[​](#usage-10 "Direct link to Usage") #### Import[​](#import-1 "Direct link to Import") ``` import { Erc7677Paymaster } from "abstractionkit"; ``` #### Sponsored UserOperation[​](#sponsored-useroperation "Direct link to Sponsored UserOperation") example.ts ``` import { Erc7677Paymaster } from "abstractionkit"; const bundlerRpc = "https://api.candide.dev/public/v3/11155111"; const paymaster = new Erc7677Paymaster(bundlerRpc); // Use createUserOperation() to help you construct a userOp let userOperation = await smartAccount.createUserOperation(/* ... */); const { userOperation: sponsoredOp } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerRpc, { sponsorshipPolicyId: "sp_..." }, // provider-specific context ); userOperation = sponsoredOp; ``` #### ERC-20 token sponsorship[​](#erc-20-token-sponsorship "Direct link to ERC-20 token sponsorship") Passing `{ token }` in the context triggers the ERC-20 gas flow automatically. For Candide and Pimlico the exchange rate is fetched for you; for unknown providers, supply `exchangeRate` in the context. `tokenQuote` surfaces the exchange rate and max token cost applied to the UserOperation so you can display the charge to the user without a second round trip. example.ts ``` const bundlerRpc = "https://api.candide.dev/public/v3/11155111"; const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; // USDC on sepolia // Candide or Pimlico (auto-detected): exchange rate fetched from the RPC const { userOperation: tokenOp, tokenQuote } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerRpc, { token: usdcAddress }, ); userOperation = tokenOp; // Unknown provider: supply exchangeRate (scaled by 1e18) const { userOperation: tokenOp2 } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerRpc, { token: usdcAddress, exchangeRate: "1000000000000000000" }, ); userOperation = tokenOp2; ``` #### Source code[​](#source-code-8 "Direct link to Source code") [Erc7677Paymaster](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/Erc7677Paymaster.ts#L204) | [createPaymasterUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/paymaster/Erc7677Paymaster.ts#L409) --- # Safe Account V2 **Safe Account V2** uses the original Safe Singleton and adds ERC-4337 functionality using a module/fallback handler. The V2 contracts, known as the `SafeAccountV0_2_0` class in AbstractionKit, support **EntryPoint v0.6**. ## Import[​](#import "Direct link to Import") ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; ``` ## How to Use[​](#how-to-use "Direct link to How to Use") Initialize a new Safe Account and calculate its address: ``` const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // Safe owner pub address const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const accountAddress = smartAccount.accountAddress; ``` ## Constructor[​](#constructor "Direct link to Constructor") Creates a `SafeAccountV0_2_0` instance for an existing deployed Safe account. For new (undeployed) accounts, use the static `initializeNewAccount` method instead. ### Usage[​](#usage "Direct link to Usage") ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const smartAccount = new SafeAccount(accountAddress); ``` * Param Types * Return Type | key | type | description | | :------------------------------------ | :---------------------------- | :------------------------------------------------------ | | `accountAddress` | `string` | The on-chain address of the deployed Safe account. | | `overrides?` | `object` | Optional overrides for module and EntryPoint addresses. | | `overrides?.safe4337ModuleAddress?` | `string` | Override the default Safe 4337 module address. | | `overrides?.entrypointAddress?` | `string` | Override the default EntryPoint address. | | `overrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `overrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :------------------ | :------------------ | :------------------------------------------------------------------------------ | | `SafeAccountV0_2_0` | `SafeAccountV0_2_0` | An instance of the SafeAccountV0\_2\_0 class connected to the existing account. | #### Source code[​](#source-code "Direct link to Source code") [constructor](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L43) ## Methods[​](#methods "Direct link to Methods") The **Essentials** methods provide all necessary functionalities with support for overrides, offering a streamlined approach. ### initializeNewAccount[​](#initializenewaccount "Direct link to initializeNewAccount") Initializes a new SafeAccount class given a list of owners' public addresses. Only needs to be called on the first transaction when the account has not been deployed yet. #### Usage[​](#usage-1 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Account address (sender): " + smartAccount.accountAddress); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :------------------ | :------------------ | :------------------------------------------------------------------- | | `SafeAccount class` | `SafeAccountV0_2_0` | An instance of the Safe V2 Account and the initialization parameters | Example Response ``` Account address(sender) : 0x1a02592A3484c2077d2E5D24482497F85e1980C6 ``` #### Source code[​](#source-code-1 "Direct link to Source code") [initializeNewAccount](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L108) ### isDeployed[​](#isdeployed "Direct link to isDeployed") Static method that checks whether a Safe account is already deployed on-chain. Use it to decide between connecting to an existing account (`new SafeAccountV0_2_0(address)`) and initializing a counterfactual one (`SafeAccountV0_2_0.initializeNewAccount(owners)`). Once an account is deployed, the factory data carried by `initializeNewAccount` is no longer needed and including it would waste gas. This only checks for non-empty bytecode at `accountAddress`. It does not verify that the deployed code is a Safe or that its on-chain configuration matches a given owner set. #### Usage[​](#usage-2 "Direct link to Usage") * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const account = (await SafeAccount.isDeployed(accountAddress, nodeRpcUrl)) ? new SafeAccount(accountAddress) : SafeAccount.initializeNewAccount([ownerPublicAddress]); ``` | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `accountAddress` | `string` | The Safe account address to check. | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | key | type | description | | :----------- | :----------------- | :------------------------------------------------------------------------------------- | | `isDeployed` | `Promise` | Resolves to true if non-empty bytecode is deployed at accountAddress, false otherwise. | #### Source code[​](#source-code-2 "Direct link to Source code") [isDeployed](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L263) ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") This method determines the nonce, fetches the gas prices, estimates gas limits, and returns a UserOperation to be signed. You can override any of these values using the overrides parameter. #### Usage[​](#usage-3 "Direct link to Usage") This example mints the same NFT twice in a single UserOperation. * example.ts * Param Types * Return Type example.ts ``` import { MetaTransaction } from "abstractionkit"; const jsonRpcNodeProvider = "https://rpc2.sepolia.org"; const bundlerUrl = "https://api.candide.dev/public/v3/11155111"; const transaction: MetaTransaction = { to: "0xD9de104e3386d9A45a61BcE269c43E48B534e4E7", // NFT contract address value: 0n, data: "0x1249c58b", // mint() } let userOperation = await smartAccount.createUserOperation( [transaction, transaction], // batch transactions to mint 2 NFTs jsonRpcNodeProvider, bundlerUrl, ) console.log(userOperation); ``` | Param Name | Param Type | Description | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | Transactions | MetaTransaction\[]key type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation | | Provider RPC | string | The node URL. It is used to fetch the current nonce and fetch gas prices | | Bundler URL | string | The Bundler URL. It is used to fetch the gas limits | | Overrides | CreateUserOperationOverrides, optional objectkey type descriptionnonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) callGasLimitPercentageMultiplier number Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler verificationGasLimitPercentageMultiplier number Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler preVerificationGasPercentageMultiplier number Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler maxFeePerGasPercentageMultiplier number Set the maxFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node maxPriorityFeePerGasPercentageMultiplier number Set the maxPriorityFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node state\_override\_set? object Pass state overrides for gas estimation state\_override\_set?.\[address: string] object Overrides for a specific address state\_override\_set?.\[address: string].balance bigint Override the balance of the address state\_override\_set?.\[address: string].nonce bigint Override the nonce of the address state\_override\_set?.\[address: string].code string Override the code of the address state\_override\_set?.\[address: string].state Dictionary\ Override the storage slots of the address state\_override\_set?.\[address: string].stateDiff Dictionary\ Apply state differences to the storage slots of the address dummySignerSignaturePairs\[] object Provide dummy signatures for the operation dummySignerSignaturePairs\[].SignerSignaturePair\[] object Signature Pair dummySignerSignaturePairs\[].SignerSignaturePair\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key dummySignerSignaturePairs\[].SignerSignaturePair\[].signature string signature dummySignerSignaturePairs\[].SignerSignaturePair\[].isContractSignature boolean isContractSignature expectedSigners\[] object A array of expected signers that will sign over the transaction. This improves the gas estimates. expectedSigners\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key webAuthnSharedSigner string Specify the WebAuthn shared signer webAuthnSignerFactory string Specify the WebAuthn signer factory webAuthnSignerSingleton string Specify the WebAuthn signer singleton eip7212WebAuthnPrecompileVerifier string Specify the EIP-7212 WebAuthn precompile verifier eip7212WebAuthnContractVerifier string Specify the EIP-7212 WebAuthn contract verifier safeModuleExecutorFunctionSelector SafeModuleExecutorFunctionSelector Specify the function selector for the safe module executor multisendContractAddress string Specify the multisend contract address gasLevel object Multiplier to determine the gas price for the user operation gasLevel.Slow 1 Default Slow Gas gasLevel.Medium 1.2 Medium Gas with a 20% increase gasLevel.Fast 1.5 Medium Gas with a 50% increase polygonGasStation object To specify the polygon network polygonGasStation.Mainnet v2 Polygon PoS Mainnet polygonGasStation.ZkMainnet zkevm Polygon zkEVM Mainnet polygonGasStation.Amoy amoy Polygon Amoy PoS Testnet polygonGasStation.Cardona cardona Polygon Cardona zkEVM Testnet isMultiChainSignature? boolean Whether this is a multi-chain signature using Merkle proofs parallelPaymasterInitValues? ParallelPaymasterInitValues Paymaster fields for parallel signing (EntryPoint v0.9) skipGasEstimation? boolean When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls that require a placeholder signature. initCode string The initCode of the account, only needed if the account is not yet on-chain and needs to be created | CreateUserOperationOverrides allows you to override any of the default parameters for the user operation | | Param Name | Param Type | Description | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------- | | UserOperation | Promise\UserOperation objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) initCode string The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymasterAndData string Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas signature string The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yetJsonRpcError objectkey type descriptioncode number JSON RPC error code message string JSON RPC error message descriptionBundlerJsonRpcError objectkey type descriptioncode number Bundler RPC error code message string Bundler RPC error message description | Returns a UserOperation on success, or the RPC/bundler error on failure. | Example Response ``` { sender: '0x44e3cb9acd92ab055d3251994352bb8fe0e20879', nonce: 1n, initCode: '0x', callData: '0x541d63c800000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b52600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001048d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b200d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b00d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b000000000000000000000000000000000000000000000000000000000000000000000000000000000000', callGasLimit: 95085n, verificationGasLimit: 62187n, preVerificationGas: 46156n, maxFeePerGas: 1625933544n, maxPriorityFeePerGas: 1200000000n, paymasterAndData: '0x', signature: '0x00000000000000000000000041c6297bd9573e8d979a272db4f6576a98f639a7e6874055a627769401dc46d01143551ccaa473364ace4340ec395c546dccb725e1eac2639ecef443d229f0071b' } ``` #### Source code[​](#source-code-3 "Direct link to Source code") [createUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L321) ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") This method takes a userOperation, the private keys of the owner of the account, and the chainId and returns the signature field. * example.ts * Param Types * Return Type example.ts ``` const chainId = BigInt("11155111"); // sepolia chain ID const privateKey = "0x4cad764980d84fc6684ca839cae2c78be5432e292fa98416e11687ceb9096a03"; const userOperation = {..} const signature = smartAccount.signUserOperation( userOperation, [privateKey], chainId, ); console.log(signature); ``` | key | type | description | | :----------------------- | :---------------- | :------------------------------------------ | | `userOperation` | `UserOperationV6` | userOperation to sign | | `privateKeys` | `string[]` | private keys of owners/signers | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | key | type | description | | :---------------- | :------- | :-------------------------------------------------------------------------------------------------------------- | | `Signature field` | `string` | UserOperation Signature with the data passed into the account along with the nonce during the verification step | Example Response ``` 0x00000000000000000000000041c6297bd9573e8d979a272db4f6576a98f639a7e6874055a627769401dc46d01143551ccaa473364ace4340ec395c546dccb725e1eac2639ecef443d229f0071b ``` #### Source code[​](#source-code-4 "Direct link to Source code") [signUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L438) ### signUserOperationWithSigners[​](#signuseroperationwithsigners "Direct link to signUserOperationWithSigners") Signs a UserOperation using one or more [`ExternalSigner`s](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of raw private keys. Integrates viem, ethers, hardware wallets, HSMs, MPC services, and WebAuthn through the same API. example.ts ``` import { fromEthersWallet } from "abstractionkit"; import { Wallet } from "ethers"; const wallet = new Wallet("0x..."); userOperation.signature = await smartAccount.signUserOperationWithSigners( userOperation, [fromEthersWallet(wallet)], chainId, ); ``` See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters and custom-signer integrations. #### Source code[​](#source-code-5 "Direct link to Source code") [signUserOperationWithSigners](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L465) ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") This method sends the UserOperation to the bundler to be executed on-chain. It returns a promise `SendUseroperationResponse` object to confirm the on-chain inclusion of the UserOperation. * example.ts * Param Types * Return Type example.ts ``` const sendUserOperationResponse = await smartAccount.sendUserOperation(userOperation, bundlerUrl) console.log("sendUserOperationResponse: ", sendUserOperationResponse); console.log("UserOperation sent. Waiting to be included..."); const receipt = await sendUserOperationResponse.included() console.log("receipt: ", receipt); ``` | key | type | description | | :-------------- | :---------------- | :-------------------------------- | | `userOperation` | `UserOperationV6` | userOperation to send | | `bundlerRpc` | `string` | bundler rpc to send userOperation | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | key | type | description | | :------------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `bundler` | `Bundler` | The Bundler class | | `entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | BundlerJsonRpcError | key | type | description | | :-------- | :------- | :------------------------------------ | | `code` | `number` | Bundler RPC error code | | `message` | `string` | Bundler RPC error message description | UserOperationReceiptResult | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | Example Response ``` sendUserOperationResponse: { userOperationHash: '0x61b3e2c57ad7ad1ae788f0ac84c79b28aab8aeaf872be173cadc72ab8b3d4418', bundler: { rpcUrl: 'https://api.candide.dev/public/v3/11155111' }, entrypointAddress: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' } UserOperation sent. Waiting to be included... receipt: { userOpHash: '0x61b3e2c57ad7ad1ae788f0ac84c79b28aab8aeaf872be173cadc72ab8b3d4418', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', sender: '0x44e3cb9acd92ab055d3251994352bb8fe0e20879', nonce: '0x2', paymaster: '0x0000000000000000000000000000000000000000', actualGasCost: 261844423573004, actualGasUsed: 185893, success: true, logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000ee2567da7e0c000000000000000000000000000000000000000000000000000000000002d625","logIndex":"0x140","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x61b3e2c57ad7ad1ae788f0ac84c79b28aab8aeaf872be173cadc72ab8b3d4418","0x00000000000000000000000044e3cb9acd92ab055d3251994352bb8fe0e20879","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"}]', receipt: { blockHash: '0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69', blockNumber: '0x4e4d65', from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: '0x1c3ffde', gasUsed: '0x2dbfd', logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x00000000000000000000000000000000000000000000000000013cd7ed43f8b8","logIndex":"0x13a","removed":false,"topics":["0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4","0x00000000000000000000000044e3cb9acd92ab055d3251994352bb8fe0e20879"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0x44e3cb9acd92ab055d3251994352bb8fe0e20879","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x","logIndex":"0x13b","removed":false,"topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x000000000000000000000000d556564bacf6feac2e26ff70695f8250cea8c29e"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x","logIndex":"0x13c","removed":false,"topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x","logIndex":"0x13d","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000044e3cb9acd92ab055d3251994352bb8fe0e20879","0x0000000000000000000000000000000000000000000000000000000000000023"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x","logIndex":"0x13e","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000044e3cb9acd92ab055d3251994352bb8fe0e20879","0x0000000000000000000000000000000000000000000000000000000000000024"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0x44e3cb9acd92ab055d3251994352bb8fe0e20879","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x","logIndex":"0x13f","removed":false,"topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x000000000000000000000000d556564bacf6feac2e26ff70695f8250cea8c29e"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xba4e1221571d457b4a01db81be6c3ca8e1dcf0117c2c383425e8379853345a69","blockNumber":"0x4e4d65","data":"0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000ee2567da7e0c000000000000000000000000000000000000000000000000000000000002d625","logIndex":"0x140","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x61b3e2c57ad7ad1ae788f0ac84c79b28aab8aeaf872be173cadc72ab8b3d4418","0x00000000000000000000000044e3cb9acd92ab055d3251994352bb8fe0e20879","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25","transactionIndex":"0xc5"}]', logsBloom: '0x000000000000100000000000000000000000000000000000000000000000000000080000000000000022080100000000001000000000008000000200000020000000000000020000000000080000000008100000000000000000000000002000020000000a0800000000000000000800000000000000000000000014000200000000000000000000000000000008000040000000000200000000000000000000000000000002000000400000400000000200000000000000000002200008000000000002000000000001000008000000000000000000080800000000000020000040000000000000000000000000000200000000000000000100000000000000', transactionHash: '0x00289aec83e4f8a109e2026e9e7f9a122bcf66116b1fc9c48099d668eec49f25', transactionIndex: '0xc5', effectiveGasPrice: '0xc6e9e20' } } ``` #### Source code[​](#source-code-6 "Direct link to Source code") [sendUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L1078) ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") The **Advanced** methods offer fine control and customization, catering to developers who require detailed configurations for their specific requirements. ### createAccountAddress[​](#createaccountaddress "Direct link to createAccountAddress") Calculates the Account address from the initial owners #### Usage[​](#usage-4 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const safeAddress = SafeAccount.createAccountAddress( [ownerPublicAddress], ); console.log("Account address (sender): " + safeAddress); ``` | key | type | description | | :-------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides` | `object` | Override values to change the initialization default values | | `initCodeOverrides.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :---------------------- | :------- | :-------------------- | | `Smart Account Address` | `string` | Smart Account Address | Example Response ``` Account address(sender) : 0x1a02592A3484c2077d2E5D24482497F85e1980C6 ``` #### Source code[​](#source-code-7 "Direct link to Source code") [createAccountAddress](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L71) ### createInitCode[​](#createinitcode "Direct link to createInitCode") Calculates the initCode needed to deploy the account on-chain. #### Usage[​](#usage-5 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccount } from "abstractionkit"; const owner1PublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const owner2PublicAddress = "0x4991A5360e5da9BAF62fF644d89F46268e5159eA"; const initCode = SafeAccount.createInitCode([ownerPublicAddress, owner2PublicAddress], 2); console.log("initCode: ", initCode); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :--------- | :------- | :-------------------------------------------------------------------------------------------------- | | `initCode` | `string` | The initCode of the account, only needed if the account is not yet on-chain and needs to be created | Example Response ``` initCode: 0x... ``` #### Source code[​](#source-code-8 "Direct link to Source code") [createInitCode](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L293) ### createAccountAddressAndInitCode[​](#createaccountaddressandinitcode "Direct link to createAccountAddressAndInitCode") Calculates the Safe address and the initCode needed to deploy the account on-chain. #### Usage[​](#usage-6 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; let [accountAddress, initCode] = SafeAccount.createAccountAddressAndInitCode( [ownerPublicAddress], ); console.log("Account address (sender): " + accountAddress); console.log("initCode: ", initCode); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :---------------- | :------- | :-------------------------------------- | | `account address` | `string` | The Safe Account Address | | `initcode` | `string` | The initCode field in the userOperation | Example Response ``` Account address(sender) : 0x1a02592A3484c2077d2E5D24482497F85e1980C6 initCode: 0x... ``` #### Source code[​](#source-code-9 "Direct link to Source code") [createAccountAddressAndInitCode](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L232) ### createInitializerCallData[​](#createinitializercalldata "Direct link to createInitializerCallData") Creates the initializer calldata #### Usage[​](#usage-7 "Direct link to Usage") * Example * Param Types * Return Types ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const initializeCallData = SafeAccount.createInitializerCallData( [ownerPublicAddress], // owners 1, //threshold ); console.log("initializeCallData: " + initializeCallData); ``` | key | type | description | | :------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `overrides?` | `object` | Override values to change the initialization default values | | `overrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `overrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `overrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `overrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `overrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `overrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :--------- | :------- | :----------------------- | | `calldata` | `string` | The initializer calldata | Example Response ``` initializeCallData: 0xb63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d3100000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c22600000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-10 "Direct link to Source code") [createInitializerCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L257) ### createAccountCallDataSingleTransaction[​](#createaccountcalldatasingletransaction "Direct link to createAccountCallDataSingleTransaction") Encodes calldata for a single MetaTransaction to be executed by a Safe account. #### Usage[​](#usage-8 "Direct link to Usage") In this example, we make a transfer of 1 wei to a random address. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const callData = smartAccount.createAccountCallDataSingleTransaction({ to: "0x1a02592A3484c2077d2E5D24482497F85e1980C6", value: 1, data: "0x", }); console.log("callData: " + callData); ``` | key | type | description | | :---------------------------------------------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `metaTransaction` | `object` | The MetaTransaction to create calldata for | | `metaTransaction.MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `metaTransaction.MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `metaTransaction.MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `metaTransaction.MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `metaTransaction.MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | | `safeModuleExecutorFunctionSelector` | `object` | Safe has two executor functions executeUserOpWithErrorString and executeUserOp | | `safeModuleExecutorFunctionSelector.executeUserOpWithErrorString` | `0x541d63c8` | The MetaTransaction to create calldata for | | `safeModuleExecutorFunctionSelector.executeUserOp` | `0x7bb37428` | The MetaTransaction to create calldata for | | key | type | description | | :--------- | :------- | :------------------------------------------------------------------------- | | `callData` | `string` | CallData to be includes in the user operation to send a single transaction | Example Response ``` callData : 0xf34308ef000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-11 "Direct link to Source code") [createAccountCallDataSingleTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L280) ### createAccountCallDataBatchTransactions[​](#createaccountcalldatabatchtransactions "Direct link to createAccountCallDataBatchTransactions") Encodes calldata for a list of MetaTransactions to be executed by a Safe account. #### Usage[​](#usage-9 "Direct link to Usage") In this example, we make a transfer to 2 different random addresses, 1 wei each. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_2_0 as SafeAccount, MetaTransaction, } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const tx1: MetaTransaction = { to: "0x1a02592A3484c2077d2E5D24482497F85e1980C6", value: 1, data: "0x", }; const tx2: MetaTransaction = { to: "0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020", value: 1, data: "0x", }; const callData = SafeAccount.createAccountCallDataBatchTransactions([tx1, tx2]); console.log("callData: " + callData); ``` | key | type | description | | :---------------------------------------------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `metaTransaction[]` | `object` | The MetaTransaction to create calldata for | | `metaTransaction[].MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `metaTransaction[].MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `metaTransaction[].MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `metaTransaction[].MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `metaTransaction[].MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | | `safeModuleExecutorFunctionSelector` | `object` | Safe has two executor functions executeUserOpWithErrorString and executeUserOp | | `safeModuleExecutorFunctionSelector.executeUserOpWithErrorString` | `0x541d63c8` | The MetaTransaction to create calldata for | | `safeModuleExecutorFunctionSelector.executeUserOp` | `0x7bb37428` | The MetaTransaction to create calldata for | | key | type | description | | :--------- | :------- | :------------------------------------------------------------------------- | | `callData` | `string` | CallData to be includes in the user operation to send a single transaction | Example Response ``` callData : 0xf34308ef000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-12 "Direct link to Source code") [createAccountCallDataBatchTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L315) ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Estimates gas limits for a UserOperation. #### Usage[​](#usage-10 "Direct link to Usage") * example.ts * Param Types * Response Type ``` import { SafeAccountV0_2_0 as SafeAccount, UserOperationV6 } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) // Use createUserOperation() to help you construct the userOp below let userOperation: UserOperationV6 = smartAccount.createUserOperation(..); const [preVerificationGas, verificationGasLimit, callGasLimit] = await estimateUserOperationGas(userOperation, bundlerRPC); ``` | key | type | description | | :---------------------------------------------------------------------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV6` | userOperation to send | | `bundlerRpc` | `string` | bundler rpc to send userOperation | | `overrides?` | `object` | overrides for the default values | | `overrides?.stateOverrideSet?` | `object` | Timestamp the signature will be valid after | | `overrides?.stateOverrideSet?.[address: string]` | `object` | Overrides for a specific address | | `overrides?.stateOverrideSet?.[address: string].balance` | `bigint` | Override the balance of the address | | `overrides?.stateOverrideSet?.[address: string].nonce` | `bigint` | Override the nonce of the address | | `overrides?.stateOverrideSet?.[address: string].code` | `string` | Override the code of the address | | `overrides?.stateOverrideSet?.[address: string].state` | `Dictionary` | Override the storage slots of the address | | `overrides?.stateOverrideSet?.[address: string].stateDiff` | `Dictionary` | Apply state differences to the storage slots of the address | | `overrides?.dummySignerSignaturePairs[]?` | `object` | Provide dummy signatures for the operation | | `overrides?.dummySignerSignaturePairs[]?.SignerSignaturePair[]` | `object` | Signature Pair | | `overrides?.dummySignerSignaturePairs[]?.SignerSignaturePair[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `overrides?.dummySignerSignaturePairs[]?.SignerSignaturePair[].signature` | `string` | signature | | `overrides?.dummySignerSignaturePairs[]?.SignerSignaturePair[].isContractSignature` | `boolean` | isContractSignature | | `overrides?.expectedSigners?` | `object` | The expected signers that will sign over the transaction. This improves the gas estimates. | | `overrides?.expectedSigners?.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `overrides?.webAuthnSharedSigner?` | `string` | Specify the WebAuthn shared signer | | `overrides?.webAuthnSignerFactory?` | `string` | Specify the WebAuthn signer factory | | `overrides?.webAuthnSignerSingleton?` | `string` | Specify the WebAuthn signer singleton | | `overrides?.webAuthnSignerProxyCreationCode?` | `string` | Specify the WebAuthn signer proxy creation code | | `overrides?.eip7212WebAuthnPrecompileVerifier?` | `string` | Specify the EIP-7212 WebAuthn precompile verifier | | `overrides?.eip7212WebAuthnContractVerifier?` | `string` | Specify the EIP-7212 WebAuthn contract verifier | UserOperationV6 | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | Parameter | Type | Description | | :------------ | :--------------------------------- | :---------------------------------------------------------------------------------- | | gas estimates | Promise<\[bigint, bigint, bigint]> | Returns the gas estimates of preVerificationGas, verificationGasLimit, callGasLimit | Example Response ``` [ 46840n, 64545n, 102761n ] ``` #### Source code[​](#source-code-13 "Direct link to Source code") [estimateUserOperationGas](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L407) ### getUserOperationEip712Hash[​](#getuseroperationeip712hash "Direct link to getUserOperationEip712Hash") Creates a UserOperation EIP-712 hash. #### Usage[​](#usage-11 "Direct link to Usage") * example * Param Types * Return Types example ``` import { SafeAccountV0_2_0 as SafeAccount, UserOperationV6 } from "abstractionkit"; const userOperation: UserOperationV6 = smartAccount.createUserOperation(..) const safeUserOpHash = SafeAccount.getUserOperationEip712Hash(userOperation, chainId); console.log(safeUserOpHash); ``` | key | type | description | | :----------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | UserOperation to hash | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `userOperation.initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `userOperation.signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | Overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.entrypointAddress?` | `string` | Target entrypoint. Defaults to EP v0.6 | | `overrides?.safe4337ModuleAddress?` | `string` | Defaults to official 4337 safe module address | | key | type | description | | :-------------- | :------- | :----------------- | | `userOperation` | `string` | userOperation hash | #### Source code[​](#source-code-14 "Direct link to Source code") [getUserOperationEip712Hash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L162) ### formatEip712SignaturesToUseroperationSignature[​](#formateip712signaturestouseroperationsignature "Direct link to formatEip712SignaturesToUseroperationSignature") A static method to format a list of EIP-712 signatures to a UserOperation signature. #### Usage[​](#usage-12 "Direct link to Usage") * ethers example * viem example * Param Types * Response Type ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; import { Wallet } from "ethers"; const ownerPrivateKey = process.env.PRIVATE_KEY as string; const signer = new Wallet(ownerPrivateKey); const ownerPublicAddress = signer.address; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); let userOperation = ... // smartAccount.createUserOperation(..) const safeUserOpHash = SafeAccount.getUserOperationEip712Hash(userOperation, chainId); const signature = signer.signingKey.sign(safeUserOpHash).serialized; const formatedSig = SafeAccount.formatEip712SignaturesToUseroperationSignature([ownerPublicAddress], [signature]); userOperation.signature = formatedSig; ``` ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; import { privateKeyToAccount } from "viem"; const ownerPrivateKey = process.env.PRIVATE_KEY as string; const signer = privateKeyToAccount(process.env.PRIVATE_KEY1 as `0x${string}`); const ownerPublicAddress = signer.address; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); let userOperation = ... // smartAccount.createUserOperation(..) const chainId = BigInt(process.env.CHAIN_ID as string); const safeUserOpHash = SafeAccount.getUserOperationEip712Hash( userOperation, chainId ) as `0x${string}`; const signature = await signer.sign({ hash: safeUserOpHash }); const formatedSig = SafeAccount.formatEip712SignaturesToUseroperationSignature([ownerPublicAddress], [signature]); userOperation.signature = formatedSig; ``` | key | type | description | | :---------------------------------- | :--------- | :---------------------------------------------------------- | | `signersAddresses` | `string[]` | Provide dummy signatures for the operation | | `signatures` | `string[]` | Provide dummy signatures for the operation | | `overrides?` | `object` | overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.isMultiChainSignature?` | `boolean` | Whether this is a multi-chain signature using Merkle proofs | | `overrides?.merkleProof?` | `string` | Merkle proof for multi-chain signature verification | | key | type | description | | :---------- | :------- | :-------------------- | | `signature` | `string` | The EIP-712 Signature | Example Response ``` 0x0000000000000000000000006da39f6f7b0d2c0035084d3c313350697b3167ff591a84bf0b4bb4741224b5d226682ec306544c091e2b6535042c900b459282edfe98e393d552963ca8db11731c ``` #### Source code[​](#source-code-15 "Direct link to Source code") [formatEip712SignaturesToUseroperationSignature](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L521) ### isModuleEnabled[​](#ismoduleenabled "Direct link to isModuleEnabled") Checks if a specified module is enabled for a given Safe account. This function returns a boolean indicating the module's status. * Param Types * Return Types | key | type | description | | :-------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain. | | `moduleAddress` | `string` | The module address to check if enabled. | | key | type | description | | :---------- | :-------- | :------------------------------------------------- | | `isEnabled` | `boolean` | Indicates whether the specified module is enabled. | ### createAddOwnerWithThresholdMetaTransactions[​](#createaddownerwiththresholdmetatransactions "Direct link to createAddOwnerWithThresholdMetaTransactions") Creates a meta-transaction to add a new owner to the Safe account with a specified threshold. This function returns an object containing the meta-transaction details, including the target address, encoded data, and transaction value. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------------- | | `newOwner` | `object` | The public address of the new owner to be added | | `newOwner.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | The new threshold value for owner confirmations. | | `overrides` | `object` | Optional Overrides for the default values used in the transaction. | | `overrides.nodeRpcUrl` | `string?` | The JSON-RPC API url for the target chain, to check if the new webauthn owner is deployed or not | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string?` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string?` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string?` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string?` | Address for the WebAuthn signer singleton. | | key | type | description | | :--------------------------------- | :------- | :------------------------------------------------------------------------------- | | `Promise` | `object` | The meta-transaction object for adding a new owner with the specified threshold. | | `Promise.to` | `string` | The target address for the meta-transaction (Safe account address). | | `Promise.data` | `string` | The encoded function call data for adding an owner. | | `Promise.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### createSwapOwnerMetaTransactions[​](#createswapownermetatransactions "Direct link to createSwapOwnerMetaTransactions") Creates a meta-transaction to swap an owner in the Safe account. If a new owner verifier is not already deployed, it will deploy one and fetch the previous owner automatically. This function returns a promise that resolves to a list of meta-transactions containing the necessary details for the swap and deployment. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :------------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous owner. | | `newOwner` | `object` | The public address of the new owner to be added. | | `newOwner.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `oldOwner` | `Signer` | The public address of the owner to be replaced. | | `overrides` | `object` | Overrides for the default values used in the transaction. | | `overrides.prevOwner` | `string` | If set, it will be used as the previous owner instead of fetching it. | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string` | Address for the WebAuthn signer singleton. | | key | type | description | | :------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction[]` | `object` | A promise that resolves to a list of meta-transactions for swapping the specified owner and deploying the verifier if necessary. | | `MetaTransaction[].to` | `string` | The target address for each meta-transaction (Safe account address). | | `MetaTransaction[].data` | `string` | The encoded function call data for the swap owner transaction. | | `MetaTransaction[].value` | `bigint` | The value to be sent with the meta-transaction, set to zero for these functions. | ### createRemoveOwnerMetaTransaction[​](#createremoveownermetatransaction "Direct link to createRemoveOwnerMetaTransaction") Creates a meta-transaction to remove an owner from the Safe account, fetching the previous owner if not provided. This function returns a promise that resolves to a meta-transaction object containing the necessary details for the removal. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :------------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous owner. | | `ownerToDelete` | `object` | The public address of the owner to be deleted. | | `ownerToDelete.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | The new threshold value for owner confirmations after removal. | | `overrides` | `object` | Optional Overrides for the default values used in the transaction. | | `overrides.prevOwner` | `string` | If set, it will be used as the previous owner instead of fetching it. | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string` | Address for the WebAuthn signer singleton. | | key | type | description | | :---------------------- | :------- | :--------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | A promise that resolves to the meta-transaction object for removing the specified owner. | | `MetaTransaction.to` | `string` | The target address for the meta-transaction (Safe account address). | | `MetaTransaction.data` | `string` | The encoded function call data for removing the owner. | | `MetaTransaction.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### createDisableModuleMetaTransaction[​](#createdisablemodulemetatransaction "Direct link to createDisableModuleMetaTransaction") Creates a meta-transaction to disable a module from the Safe account. The function automatically fetches the previous module address in the linked list if not provided in the overrides. This function returns a promise that resolves to a meta-transaction object containing the necessary details for disabling the module. * Param Types * Return Types | key | type | description | | :---------------------------- | :------- | :------------------------------------------------------------------------------ | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous module address. | | `moduleToDisableAddress` | `string` | The address of the module to be disabled. | | `accountAddress` | `string` | The Safe account address for which to disable the module. | | `overrides` | `object` | Optional overrides for the default values used in the transaction. | | `overrides.prevModuleAddress` | `string` | If set, it will be used as the previous module address instead of fetching it. | | `overrides.modulesStart` | `string` | Starting address for the module pagination query. | | `overrides.modulesPageSize` | `bigint` | Page size for the module pagination query. | | key | type | description | | :---------------------- | :------- | :----------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | A promise that resolves to the meta-transaction object for disabling the specified module. | | `MetaTransaction.to` | `string` | The target address for the meta-transaction (Safe account address). | | `MetaTransaction.data` | `string` | The encoded function call data for disabling the module. | | `MetaTransaction.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### verifyWebAuthnSignatureForMessageHash[​](#verifywebauthnsignatureformessagehash "Direct link to verifyWebAuthnSignatureForMessageHash") A static method that verifies a webAuthn signed hash message for Passkeys, and can be also used even if the safe account hasn't been deployed yet. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :------- | :------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain. | | `webAuthnPublicKey` | `object` | The x and y coordinates of the webAuthn public key | | `webAuthnPublicKey.x` | `bigint` | The x coordinate of the webAuthn public key | | `webAuthnPublicKey.y` | `bigint` | The y coordinate of the webAuthn public key | | `message` | `string` | The hashed message to verify | | `signature` | `string` | The signed message | | `overrides` | `object` | Optional overrides for webAuthn verify signature message | | `overrides.webAuthnSignerSingleton` | `string` | Specify the WebAuthn signer singleton | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Specify the EIP-7212 WebAuthn precompile verifier | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Specify the EIP-7212 WebAuthn contract verifier | | key | type | description | | :-------- | :----------------- | :--------------------------------------------- | | `isValid` | `Promise` | Returns true if the message is valid signature | ### getUserOperationEip712Data[​](#getuseroperationeip712data "Direct link to getUserOperationEip712Data") A static method that returns the EIP-712 domain data for a UserOperation. * Param Types * Return Types | key | type | description | | :----------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | UserOp to hash | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `userOperation.initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `userOperation.signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | undefined | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.entrypointAddress?` | `string` | Address of the entrypoint contract | | `overrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | key | type | description | | :---------------------------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `domain` | `object` | Safe userOperation typed data domain | | `domain.chainId` | `number` | target chain id | | `domain.verifyingContract` | `string` | safe 4337 module contract on traget entrypoint | | `types` | `Record` | The Safe Typed structured data to be signed | | `messageValue` | `object` | Safe userOperation typed message value | | `messageValue.safe` | `string` | the userop sender address of the safe account | | `messageValue.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `messageValue.initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `messageValue.callData` | `string` | The data to pass to the sender during the main execution call | | `messageValue.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `messageValue.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `messageValue.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `messageValue.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `messageValue.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `messageValue.paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `messageValue.signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | ### createMigrateToSafeAccountV0\_3\_0MetaTransactions[​](#createmigratetosafeaccountv0_3_0metatransactions "Direct link to createMigrateToSafeAccountV0_3_0MetaTransactions") Creates an array of meta-transactions to migrate a Safe account from EntryPoint v0.6 (module v0.2.0) to EntryPoint v0.7 (module v0.3.0). The migration disables the v0.6 module, enables the v0.7 module, and updates the fallback handler. #### Usage[​](#usage-13 "Direct link to Usage") ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const smartAccount = new SafeAccount(accountAddress); const migrationMetaTransactions = await smartAccount.createMigrateToSafeAccountV0_3_0MetaTransactions(nodeRpcUrl); // Include these meta-transactions in a UserOperation to execute the migration const userOperation = await smartAccount.createUserOperation( migrationMetaTransactions, nodeRpcUrl, bundlerUrl, ); ``` * Param Types * Return Types | key | type | description | | :--------------------------------- | :------- | :-------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | `overrides?` | `object` | Optional overrides for contract addresses and pagination. | | `overrides?.safeV06ModuleAddress?` | `string` | Override the Safe v0.6 module address to disable. | | `overrides?.safeV07ModuleAddress?` | `string` | Override the Safe v0.7 module address to enable. | | `overrides?.pageSize?` | `bigint` | Page size for the module pagination query. | | `overrides?.modulesStart?` | `string` | Starting address for the module pagination query. | | key | type | description | | :------------------------ | :------- | :---------------------------------------------------------------------------------------------------------------- | | `MetaTransaction[]` | `object` | Array of meta-transactions that disable the v0.6 module, enable the v0.7 module, and update the fallback handler. | | `MetaTransaction[].to` | `string` | The target address for the meta-transaction. | | `MetaTransaction[].data` | `string` | The encoded function call data. | | `MetaTransaction[].value` | `bigint` | The value to send with the meta-transaction (0n for these operations). | #### Source code[​](#source-code-16 "Direct link to Source code") [createMigrateToSafeAccountV0\_3\_0MetaTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_2_0.ts#L367) ## Audits[​](#audits "Direct link to Audits") * [Audits by Ackee and OpenZeppelin](https://github.com/safe-global/safe-modules/blob/main/modules/4337/docs/v0.2.0/audit.md) * [Contracts developed by the Safe Protocol Team](https://github.com/safe-global/safe-modules/tree/main/modules/4337) --- # Safe Account V3 **Safe Account V3** uses the original Safe Singleton and adds ERC-4337 functionality using a module/fallback handler. The V3 contracts, known as the `SafeAccountV0_3_0` class in AbstractionKit, support **EntryPoint v0.7**. ## Import[​](#import "Direct link to Import") ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; ``` ## How to Use[​](#how-to-use "Direct link to How to Use") Initialize a new Safe Account and calculate its address: ``` const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // Safe owner pub address const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const accountAddress = smartAccount.accountAddress; ``` ## Methods[​](#methods "Direct link to Methods") The **Essentials** methods provide all necessary functionalities with support for overrides, offering a streamlined approach. ### initializeNewAccount[​](#initializenewaccount "Direct link to initializeNewAccount") Initializes a new SafeAccount class given a list of owners' public addresses. Only needs to be called on the first transaction when the account has not been deployed yet. #### Usage[​](#usage "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Account address (sender): " + smartAccount.accountAddress); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :------------------ | :------------------ | :------------------------------------------------------------------- | | `SafeAccount class` | `SafeAccountV0_3_0` | An instance of the Safe V3 Account and the initialization parameters | Example Response ``` Account address(sender) : 0x1a02592A3484c2077d2E5D24482497F85e1980C6 ``` #### Source code[​](#source-code "Direct link to Source code") [initializeNewAccount](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L97) ### isDeployed[​](#isdeployed "Direct link to isDeployed") Static method that checks whether a Safe account is already deployed on-chain. Use it to decide between connecting to an existing account (`new SafeAccountV0_3_0(address)`) and initializing a counterfactual one (`SafeAccountV0_3_0.initializeNewAccount(owners)`). Once an account is deployed, the factory data carried by `initializeNewAccount` is no longer needed and including it would waste gas. This only checks for non-empty bytecode at `accountAddress`. It does not verify that the deployed code is a Safe or that its on-chain configuration matches a given owner set. #### Usage[​](#usage-1 "Direct link to Usage") * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const account = (await SafeAccount.isDeployed(accountAddress, nodeRpcUrl)) ? new SafeAccount(accountAddress) : SafeAccount.initializeNewAccount([ownerPublicAddress]); ``` | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `accountAddress` | `string` | The Safe account address to check. | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | key | type | description | | :----------- | :----------------- | :------------------------------------------------------------------------------------- | | `isDeployed` | `Promise` | Resolves to true if non-empty bytecode is deployed at accountAddress, false otherwise. | #### Source code[​](#source-code-1 "Direct link to Source code") [isDeployed](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L263) ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") This method determines the nonce, fetches the gas prices, estimates gas limits, and returns a UserOperation to be signed. You can override any of these values using the overrides parameter. #### Usage[​](#usage-2 "Direct link to Usage") This example mints the same NFT twice in a single UserOperation. * example.ts * Param Types * Return Type example.ts ``` import { MetaTransaction } from "abstractionkit"; const jsonRpcNodeProvider = "https://rpc2.sepolia.org"; const bundlerUrl = "https://api.candide.dev/public/v3/11155111"; const transaction: MetaTransaction = { to: "0xD9de104e3386d9A45a61BcE269c43E48B534e4E7", // NFT contract address value: 0n, data: "0x1249c58b", // mint() } let userOperation = await smartAccount.createUserOperation( [transaction, transaction], // batch transactions to mint 2 NFTs jsonRpcNodeProvider, bundlerUrl, ) console.log(userOperation); ``` | Param Name | Param Type | Description | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------- | | Transactions | MetaTransaction\[]key type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation | | Provider RPC | string | The node URL. It is used to fetch the current nonce and fetch gas prices | | Bundler URL | string | The Bundler URL. It is used to fetch the gas limits | | Overrides | CreateUserOperationOverrides, optional objectkey type descriptionnonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) callGasLimitPercentageMultiplier number Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler verificationGasLimitPercentageMultiplier number Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler preVerificationGasPercentageMultiplier number Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler maxFeePerGasPercentageMultiplier number Set the maxFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node maxPriorityFeePerGasPercentageMultiplier number Set the maxPriorityFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node state\_override\_set? object Pass state overrides for gas estimation state\_override\_set?.\[address: string] object Overrides for a specific address state\_override\_set?.\[address: string].balance bigint Override the balance of the address state\_override\_set?.\[address: string].nonce bigint Override the nonce of the address state\_override\_set?.\[address: string].code string Override the code of the address state\_override\_set?.\[address: string].state Dictionary\ Override the storage slots of the address state\_override\_set?.\[address: string].stateDiff Dictionary\ Apply state differences to the storage slots of the address dummySignerSignaturePairs\[] object Provide dummy signatures for the operation dummySignerSignaturePairs\[].SignerSignaturePair\[] object Signature Pair dummySignerSignaturePairs\[].SignerSignaturePair\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key dummySignerSignaturePairs\[].SignerSignaturePair\[].signature string signature dummySignerSignaturePairs\[].SignerSignaturePair\[].isContractSignature boolean isContractSignature expectedSigners\[] object A array of expected signers that will sign over the transaction. This improves the gas estimates. expectedSigners\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key webAuthnSharedSigner string Specify the WebAuthn shared signer webAuthnSignerFactory string Specify the WebAuthn signer factory webAuthnSignerSingleton string Specify the WebAuthn signer singleton eip7212WebAuthnPrecompileVerifier string Specify the EIP-7212 WebAuthn precompile verifier eip7212WebAuthnContractVerifier string Specify the EIP-7212 WebAuthn contract verifier safeModuleExecutorFunctionSelector SafeModuleExecutorFunctionSelector Specify the function selector for the safe module executor multisendContractAddress string Specify the multisend contract address gasLevel object Multiplier to determine the gas price for the user operation gasLevel.Slow 1 Default Slow Gas gasLevel.Medium 1.2 Medium Gas with a 20% increase gasLevel.Fast 1.5 Medium Gas with a 50% increase polygonGasStation object To specify the polygon network polygonGasStation.Mainnet v2 Polygon PoS Mainnet polygonGasStation.ZkMainnet zkevm Polygon zkEVM Mainnet polygonGasStation.Amoy amoy Polygon Amoy PoS Testnet polygonGasStation.Cardona cardona Polygon Cardona zkEVM Testnet isMultiChainSignature? boolean Whether this is a multi-chain signature using Merkle proofs parallelPaymasterInitValues? ParallelPaymasterInitValues Paymaster fields for parallel signing (EntryPoint v0.9) skipGasEstimation? boolean When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls that require a placeholder signature. factory string Set the factory address instead of using the calculated value factoryData string Set the factory data instead of using the calculated value | CreateUserOperationOverrides allows you to override any of the default parameters for the user operation | | Param Name | Param Type | Description | | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | UserOperation | Promise\UserOperation objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorizationJsonRpcError objectkey type descriptioncode number JSON RPC error code message string JSON RPC error message descriptionBundlerJsonRpcError objectkey type descriptioncode number Bundler RPC error code message string Bundler RPC error message description | Returns a UserOperation on success, or the RPC/bundler error on failure. | Example Response ``` { sender: '0xd785bb8a95a6a08ace0aa2e54aee5cf04694b1db', nonce: 19n, callData: '0x541d63c80000000000000000000000009a7af758ae5d7b6aae84fe4c5ba67c041dfe533600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000246a627842000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db00000000000000000000000000000000000000000000000000000000', callGasLimit: 58588n, verificationGasLimit: 94374n, preVerificationGas: 45628n, maxFeePerGas: 148180551200n, maxPriorityFeePerGas: 161327244n, signature: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000041ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', factory: null, factoryData: null, paymaster: null, paymasterVerificationGasLimit: null, paymasterPostOpGasLimit: null, paymasterData: null } ``` #### Source code[​](#source-code-2 "Direct link to Source code") [createUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L289) ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") This method takes a userOperation, the private keys of the owner of the account, and the chainId and returns the signature field. * example.ts * Param Types * Return Type example.ts ``` const chainId = BigInt("11155111"); // sepolia chain ID const privateKey = "0x4cad764980d84fc6684ca839cae2c78be5432e292fa98416e11687ceb9096a03"; const userOperation = {..} const signature = smartAccount.signUserOperation( userOperation, [privateKey], chainId, ); userOperation.signature = signature; console.log(signature); ``` | key | type | description | | :----------------------- | :---------------- | :------------------------------------------ | | `userOperation` | `UserOperationV7` | userOperation to sign | | `privateKeys` | `string[]` | private keys of owners/signers | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | | key | type | description | | :---------------- | :------- | :-------------------------------------------------------------------------------------------------------------- | | `Signature field` | `string` | UserOperation Signature with the data passed into the account along with the nonce during the verification step | Example Response ``` 0x00000000000000000000000041c6297bd9573e8d979a272db4f6576a98f639a7e6874055a627769401dc46d01143551ccaa473364ace4340ec395c546dccb725e1eac2639ecef443d229f0071b ``` #### Source code[​](#source-code-3 "Direct link to Source code") [signUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L356) ### signUserOperationWithSigners[​](#signuseroperationwithsigners "Direct link to signUserOperationWithSigners") Signs a UserOperation using one or more [`ExternalSigner`s](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of raw private keys. Integrates viem, ethers, hardware wallets, HSMs, MPC services, and WebAuthn through the same API. example.ts ``` import { fromViem } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const viemAccount = privateKeyToAccount("0x..."); userOperation.signature = await smartAccount.signUserOperationWithSigners( userOperation, [fromViem(viemAccount)], chainId, ); ``` See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters and custom-signer integrations. #### Source code[​](#source-code-4 "Direct link to Source code") [signUserOperationWithSigners](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L395) ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") This method sends the UserOperation to the bundler to be executed on-chain. It returns a promise `SendUseroperationResponse` object to confirm the on-chain inclusion of the UserOperation. * example.ts * Param Types * Return Type example.ts ``` const sendUserOperationResponse = await smartAccount.sendUserOperation(userOperation, bundlerUrl) console.log("sendUserOperationResponse: ", sendUserOperationResponse); console.log("UserOperation sent. Waiting to be included..."); const receipt = await sendUserOperationResponse.included() console.log("receipt: ", receipt); ``` | key | type | description | | :-------------- | :---------------- | :-------------------------------- | | `userOperation` | `UserOperationV7` | userOperation to send | | `bundlerRpc` | `string` | bundler rpc to send userOperation | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | | key | type | description | | :------------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `bundler` | `Bundler` | The Bundler class | | `entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | BundlerJsonRpcError | key | type | description | | :-------- | :------- | :------------------------------------ | | `code` | `number` | Bundler RPC error code | | `message` | `string` | Bundler RPC error message description | UserOperationReceiptResult | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | Example Response ``` sendUserOperationResponse: { userOperationHash: '0x61b3e2c57ad7ad1ae788f0ac84c79b28aab8aeaf872be173cadc72ab8b3d4418', bundler: { rpcUrl: 'https://api.candide.dev/public/v3/11155111' }, entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' } UserOperation sent. Waiting to be included... receipt: { userOpHash: '0x395ddb51c0b76fd72796878d1008c8c9af897e944092bbc741b0a8def1a29984', entryPoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', sender: '0xd785bb8a95a6a08ace0aa2e54aee5cf04694b1db', nonce: 19n, paymaster: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', actualGasCost: 16706333689468320n, actualGasUsed: 185340n, success: true, logs: '[{"address":"0x0000000071727de22e5e9d8baf0edac6f37da032","topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x395ddb51c0b76fd72796878d1008c8c9af897e944092bbc741b0a8def1a29984","0x000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db","0x0000000000000000000000008b1f6cb5d062aa2ce8d581942bbb960420d875ba"],"data":"0x00000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000003b5a526d0f55a0000000000000000000000000000000000000000000000000000000000002d3fc","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x79","removed":false}]', receipt: { blockHash: '0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f', blockNumber: 6677635n, from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: 10239342n, gasUsed: 175887n, logs: '[{"address":"0x0000000071727de22e5e9d8baf0edac6f37da032","topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"data":"0x","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x74","removed":false},{"address":"0xd785bb8a95a6a08ace0aa2e54aee5cf04694b1db","topics":["0xb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e"],"data":"0x00000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000009a7af758ae5d7b6aae84fe4c5ba67c041dfe5336000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000246a627842000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db00000000000000000000000000000000000000000000000000000000","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x75","removed":false},{"address":"0x9a7af758ae5d7b6aae84fe4c5ba67c041dfe5336","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db","0x0000000000000000000000000000000000000000000000000000000000000508"],"data":"0x","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x76","removed":false},{"address":"0xd785bb8a95a6a08ace0aa2e54aee5cf04694b1db","topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x00000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c226"],"data":"0x","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x77","removed":false},{"address":"0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba","topics":["0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471","0x395ddb51c0b76fd72796878d1008c8c9af897e944092bbc741b0a8def1a29984","0x000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db","0x0000000000000000000000000000000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x78","removed":false},{"address":"0x0000000071727de22e5e9d8baf0edac6f37da032","topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x395ddb51c0b76fd72796878d1008c8c9af897e944092bbc741b0a8def1a29984","0x000000000000000000000000d785bb8a95a6a08ace0aa2e54aee5cf04694b1db","0x0000000000000000000000008b1f6cb5d062aa2ce8d581942bbb960420d875ba"],"data":"0x00000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000003b5a526d0f55a0000000000000000000000000000000000000000000000000000000000002d3fc","blockNumber":"0x65e483","transactionHash":"0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e","transactionIndex":"0x5d","blockHash":"0x0f306649579021da8b4447bdfcf24b050bacc162ddb756c1d003ab2dd7a0ad9f","logIndex":"0x79","removed":false}]', logsBloom: '0x0000000000111000000010000000000000200000000000000000000002000000000800040000020000000001000040000000000000000000800002000000000020000000020000000000000c000000000040000010000000000000000000000000000000020800000000000000000800000000000000200000000010000000000000000020100000000000000800000020000000000000008000000004000000000040100000000800400080000000000200000040000000000002000000000000000002000000400001000000000000000000000020000000100000000020000000200000000000000200000000000200000000000000000000000010000000', transactionHash: '0xdf4f976eb8148d14ded74c3f1b21666250be9ae836aa42fb97a3e5ba23f6bf8e', transactionIndex: 93n, effectiveGasPrice: 50000149713n } } ``` #### Source code[​](#source-code-5 "Direct link to Source code") [sendUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L1078) ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") The **Advanced** methods offer fine control and customization, catering to developers who require detailed configurations for their specific requirements. ### createAccountAddress[​](#createaccountaddress "Direct link to createAccountAddress") Calculates the Account address from the initial owners #### Usage[​](#usage-3 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const safeAddress = SafeAccount.createAccountAddress( [ownerPublicAddress], ); console.log("Account address (sender): " + safeAddress); ``` | key | type | description | | :-------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides` | `object` | Override values to change the initialization default values | | `initCodeOverrides.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :---------------------- | :------- | :-------------------- | | `Smart Account Address` | `string` | Smart Account Address | Example Response ``` Account address(sender) : 0x1a02592A3484c2077d2E5D24482497F85e1980C6 ``` #### Source code[​](#source-code-6 "Direct link to Source code") [createAccountAddress](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L74) ### createFactoryAddressAndData[​](#createfactoryaddressanddata "Direct link to createFactoryAddressAndData") Create an account factory address and factory data #### Usage[​](#usage-4 "Direct link to Usage") In this example, we initiate a single owner account. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; let [factoryAddress, factoryData] = SafeAccount.createFactoryAddressAndData( [ownerPublicAddress], ); console.log("factoryAddress: " + factoryAddress); console.log("factoryData: ", factoryData); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :---------------- | :------- | :----------------------- | | `factory address` | `string` | The Safe factory address | | `factory data` | `string` | The factory data | Example Response ``` factoryAddress: 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67 factoryData: 0x1688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d3100000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-7 "Direct link to Source code") [createFactoryAddressAndData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L259) ### createInitializerCallData[​](#createinitializercalldata "Direct link to createInitializerCallData") Creates the initializer calldata #### Usage[​](#usage-5 "Direct link to Usage") * Example * Param Types * Return Types ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const initializeCallData = SafeAccount.createInitializerCallData( [ownerPublicAddress], // owners 1, //threshold ); console.log("initializeCallData: " + initializeCallData); ``` | key | type | description | | :------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `overrides?` | `object` | Override values to change the initialization default values | | `overrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `overrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `overrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `overrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `overrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `overrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :--------- | :------- | :----------------------- | | `calldata` | `string` | The initializer calldata | Example Response ``` initializeCallData: 0xb63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d3100000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c22600000000000000000000000000000000000000000000000000000000 ``` #### Source[​](#source "Direct link to Source") [createInitializerCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L223) ### createAccountCallDataSingleTransaction[​](#createaccountcalldatasingletransaction "Direct link to createAccountCallDataSingleTransaction") Encode calldata for a single MetaTransaction to be executed by Safe account #### Usage[​](#usage-6 "Direct link to Usage") In this example, we make a transfer of 1 wei to a random address. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const callData = smartAccount.createAccountCallDataSingleTransaction({ to: "0x1a02592A3484c2077d2E5D24482497F85e1980C6", value: 1, data: "0x", }); console.log("callData: " + callData); ``` | key | type | description | | :---------------------------------------------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `metaTransaction` | `object` | The MetaTransaction to create calldata for | | `metaTransaction.MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `metaTransaction.MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `metaTransaction.MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `metaTransaction.MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `metaTransaction.MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | | `safeModuleExecutorFunctionSelector` | `object` | Safe has two executor functions executeUserOpWithErrorString and executeUserOp | | `safeModuleExecutorFunctionSelector.executeUserOpWithErrorString` | `0x541d63c8` | The MetaTransaction to create calldata for | | `safeModuleExecutorFunctionSelector.executeUserOp` | `0x7bb37428` | The MetaTransaction to create calldata for | | key | type | description | | :--------- | :------- | :------------------------------------------------------------------------- | | `callData` | `string` | CallData to be includes in the user operation to send a single transaction | Example Response ``` callData : 0xf34308ef000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-8 "Direct link to Source code") [createAccountCallDataSingleTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L280) ### createAccountCallDataBatchTransactions[​](#createaccountcalldatabatchtransactions "Direct link to createAccountCallDataBatchTransactions") Encode calldata for a list of MetaTransactions to be executed by Safe account #### Usage[​](#usage-7 "Direct link to Usage") In this example, we make a transfer to 2 different random addresses, 1 wei each. * example.ts * Param Types * Return Type example.ts ``` import { SafeAccountV0_3_0 as SafeAccount, MetaTransaction, } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const tx1: MetaTransaction = { to: "0x1a02592A3484c2077d2E5D24482497F85e1980C6", value: 1, data: "0x", }; const tx2: MetaTransaction = { to: "0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020", value: 1, data: "0x", }; const callData = SafeAccount.createAccountCallDataBatchTransactions([tx1, tx2]); console.log("callData: " + callData); ``` | key | type | description | | :---------------------------------------------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `metaTransaction[]` | `object` | The MetaTransaction to create calldata for | | `metaTransaction[].MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `metaTransaction[].MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `metaTransaction[].MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `metaTransaction[].MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `metaTransaction[].MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | | `safeModuleExecutorFunctionSelector` | `object` | Safe has two executor functions executeUserOpWithErrorString and executeUserOp | | `safeModuleExecutorFunctionSelector.executeUserOpWithErrorString` | `0x541d63c8` | The MetaTransaction to create calldata for | | `safeModuleExecutorFunctionSelector.executeUserOp` | `0x7bb37428` | The MetaTransaction to create calldata for | | key | type | description | | :--------- | :------- | :------------------------------------------------------------------------- | | `callData` | `string` | CallData to be includes in the user operation to send a single transaction | Example Response ``` callData : 0xf34308ef000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` #### Source code[​](#source-code-9 "Direct link to Source code") [createAccountCallDataBatchTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L315) ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Estimate gas limits for a userOperation #### Usage[​](#usage-8 "Direct link to Usage") * example.ts * Param Types * Response Type ``` import { SafeAccountV0_3_0 as SafeAccount, UserOperationV7 } from "abstractionkit"; const bundlerRPC = "https://api.candide.dev/public/v3/11155111"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) // Use createUserOperation() to help you construct the userOp below const userOperation:UserOperationV7 = {..} const [preVerificationGas, verificationGasLimit, callGasLimit] = await smartAccount.estimateUserOperationGas(userOperation, bundlerRPC); ``` | key | type | description | | :-------------------------------------------------------------------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV7` | userOperation to send | | `bundlerRpc` | `string` | bundler rpc to send userOperation | | `overrides?` | `object` | overrides for the default values | | `overrides?.stateOverrideSet?` | `object` | Pass state overrides for gas estimation | | `overrides?.stateOverrideSet?.[address: string]` | `object` | Overrides for a specific address | | `overrides?.stateOverrideSet?.[address: string].balance` | `bigint` | Override the balance of the address | | `overrides?.stateOverrideSet?.[address: string].nonce` | `bigint` | Override the nonce of the address | | `overrides?.stateOverrideSet?.[address: string].code` | `string` | Override the code of the address | | `overrides?.stateOverrideSet?.[address: string].state` | `Dictionary` | Override the storage slots of the address | | `overrides?.stateOverrideSet?.[address: string].stateDiff` | `Dictionary` | Apply state differences to the storage slots of the address | | `overrides?.dummySignerSignaturePairs?` | `object` | Provide dummy signatures for the operation | | `overrides?.dummySignerSignaturePairs?.SignerSignaturePair[]` | `object` | Signature Pair | | `overrides?.dummySignerSignaturePairs?.SignerSignaturePair[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `overrides?.dummySignerSignaturePairs?.SignerSignaturePair[].signature` | `string` | signature | | `overrides?.dummySignerSignaturePairs?.SignerSignaturePair[].isContractSignature` | `boolean` | isContractSignature | | `overrides?.expectedSigners?` | `object` | The expected signers that will sign over the transaction. This improves the gas estimates. | | `overrides?.expectedSigners?.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `overrides?.webAuthnSharedSigner?` | `string` | Specify the WebAuthn shared signer | | `overrides?.webAuthnSignerFactory?` | `string` | Specify the WebAuthn signer factory | | `overrides?.webAuthnSignerSingleton?` | `string` | Specify the WebAuthn signer singleton | | `overrides?.webAuthnSignerProxyCreationCode?` | `string` | Specify the WebAuthn signer proxy creation code | | `overrides?.eip7212WebAuthnPrecompileVerifier?` | `string` | Specify the EIP-7212 WebAuthn precompile verifier | | `overrides?.eip7212WebAuthnContractVerifier?` | `string` | Specify the EIP-7212 WebAuthn contract verifier | UserOperationV7 | key | type | description | | :------------------------------ | :--------------- | :------------------------------------------------------------------------------ | | `sender` | `string` | The account making the operation | | `nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `factory` | `string` | account factory, only for new accounts | | `factoryData` | `string` | data for account factory (only if account factory exists) | | `callData` | `string` | The data to pass to the sender during the main execution call | | `callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `signature` | `string` | Data passed into the account to verify authorization | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | Parameter | Type | Description | | :------------ | :--------------------------------- | :---------------------------------------------------------------------------------- | | gas estimates | Promise<\[bigint, bigint, bigint]> | Returns the gas estimates of preVerificationGas, verificationGasLimit, callGasLimit | Example Response ``` [ 46840n, 64545n, 102761n ] ``` #### Source code[​](#source-code-10 "Direct link to Source code") [estimateUserOperationGas](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L325) ### getUserOperationEip712Hash[​](#getuseroperationeip712hash "Direct link to getUserOperationEip712Hash") Create a userOperation eip712 hash #### Usage[​](#usage-9 "Direct link to Usage") * example * Param Types * Return Types example ``` import { SafeAccountV0_3_0 as SafeAccount, UserOperationV7 } from "abstractionkit"; const userOperation: UserOperationV7 = smartAccount.createUserOperation(..) const safeUserOpHash = SafeAccount.getUserOperationEip712Hash(userOperation, chainId); console.log(safeUserOpHash); ``` | key | type | description | | :-------------------------------------------- | :--------------- | :------------------------------------------------------------------------------ | | `userOperation` | `object` | UserOperation to hash | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `userOperation.factory` | `string` | account factory, only for new accounts | | `userOperation.factoryData` | `string` | data for account factory (only if account factory exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `userOperation.paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | Overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.entrypointAddress?` | `string` | Target entrypoint. Defaults to EP v0.7 | | `overrides?.safe4337ModuleAddress?` | `string` | Defaults to official 4337 safe module address | | key | type | description | | :-------------- | :------- | :----------------- | | `userOperation` | `string` | userOperation hash | Example Response ``` 0xec030c825b12b398c10f1b552004e43ec753fdf001e1c1daa1ceffe4f7ff5056 ``` #### Source[​](#source-1 "Direct link to Source") [getUserOperationEip712Hash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L151) ### formatEip712SignaturesToUseroperationSignature[​](#formateip712signaturestouseroperationsignature "Direct link to formatEip712SignaturesToUseroperationSignature") A static method to format a list of eip712 signatures to a userOperation signature. #### Usage[​](#usage-10 "Direct link to Usage") * ethers example * viem example * Param Types * Response Type ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; import { Wallet } from "ethers"; const ownerPrivateKey = process.env.PRIVATE_KEY as string; const signer = new Wallet(ownerPrivateKey); const ownerPublicAddress = signer.address; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); let userOperation = ... // smartAccount.createUserOperation(..) const safeUserOpHash = SafeAccount.getUserOperationEip712Hash(userOperation, chainId); const signature = signer.signingKey.sign(safeUserOpHash).serialized; const formatedSig = SafeAccount.formatEip712SignaturesToUseroperationSignature([ownerPublicAddress], [signature]); userOperation.signature = formatedSig; ``` ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; import { privateKeyToAccount } from "viem"; const ownerPrivateKey = process.env.PRIVATE_KEY as string; const signer = privateKeyToAccount(process.env.PRIVATE_KEY1 as `0x${string}`); const ownerPublicAddress = signer.address; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); let userOperation = ... // smartAccount.createUserOperation(..) const chainId = BigInt(process.env.CHAIN_ID as string); const safeUserOpHash = SafeAccount.getUserOperationEip712Hash( userOperation, chainId ) as `0x${string}`; const signature = await signer.sign({ hash: safeUserOpHash }); const formatedSig = SafeAccount.formatEip712SignaturesToUseroperationSignature([ownerPublicAddress], [signature]); userOperation.signature = formatedSig; ``` | key | type | description | | :---------------------------------- | :--------- | :---------------------------------------------------------- | | `signersAddresses` | `string[]` | Provide dummy signatures for the operation | | `signatures` | `string[]` | Provide dummy signatures for the operation | | `overrides?` | `object` | overrides for the default values | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.isMultiChainSignature?` | `boolean` | Whether this is a multi-chain signature using Merkle proofs | | `overrides?.merkleProof?` | `string` | Merkle proof for multi-chain signature verification | | key | type | description | | :---------- | :------- | :-------------------- | | `signature` | `string` | The EIP-712 Signature | Example Response ``` 0x0000000000000000000000006da39f6f7b0d2c0035084d3c313350697b3167ff591a84bf0b4bb4741224b5d226682ec306544c091e2b6535042c900b459282edfe98e393d552963ca8db11731c ``` #### Source code[​](#source-code-11 "Direct link to Source code") [formatEip712SignaturesToUseroperationSignature](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L521) ### isModuleEnabled[​](#ismoduleenabled "Direct link to isModuleEnabled") Checks if a specified module is enabled for a given Safe account. This function returns a boolean indicating the module's status. * Param Types * Return Types | key | type | description | | :-------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain. | | `moduleAddress` | `string` | The module address to check if enabled. | | key | type | description | | :---------- | :-------- | :------------------------------------------------- | | `isEnabled` | `boolean` | Indicates whether the specified module is enabled. | ### createAddOwnerWithThresholdMetaTransactions[​](#createaddownerwiththresholdmetatransactions "Direct link to createAddOwnerWithThresholdMetaTransactions") Creates a meta-transaction to add a new owner to the Safe account with a specified threshold. This function returns an object containing the meta-transaction details, including the target address, encoded data, and transaction value. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------------- | | `newOwner` | `object` | The public address of the new owner to be added | | `newOwner.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | The new threshold value for owner confirmations. | | `overrides` | `object` | Optional Overrides for the default values used in the transaction. | | `overrides.nodeRpcUrl` | `string?` | The JSON-RPC API url for the target chain, to check if the new webauthn owner is deployed or not | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string?` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string?` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string?` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string?` | Address for the WebAuthn signer singleton. | | key | type | description | | :--------------------------------- | :------- | :------------------------------------------------------------------------------- | | `Promise` | `object` | The meta-transaction object for adding a new owner with the specified threshold. | | `Promise.to` | `string` | The target address for the meta-transaction (Safe account address). | | `Promise.data` | `string` | The encoded function call data for adding an owner. | | `Promise.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### createSwapOwnerMetaTransactions[​](#createswapownermetatransactions "Direct link to createSwapOwnerMetaTransactions") Creates a meta-transaction to swap an owner in the Safe account. If a new owner verifier is not already deployed, it will deploy one and fetch the previous owner automatically. This function returns a promise that resolves to a list of meta-transactions containing the necessary details for the swap and deployment. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :------------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous owner. | | `newOwner` | `object` | The public address of the new owner to be added. | | `newOwner.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `oldOwner` | `Signer` | The public address of the owner to be replaced. | | `overrides` | `object` | Overrides for the default values used in the transaction. | | `overrides.prevOwner` | `string` | If set, it will be used as the previous owner instead of fetching it. | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string` | Address for the WebAuthn signer singleton. | | key | type | description | | :------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction[]` | `object` | A promise that resolves to a list of meta-transactions for swapping the specified owner and deploying the verifier if necessary. | | `MetaTransaction[].to` | `string` | The target address for each meta-transaction (Safe account address). | | `MetaTransaction[].data` | `string` | The encoded function call data for the swap owner transaction. | | `MetaTransaction[].value` | `bigint` | The value to be sent with the meta-transaction, set to zero for these functions. | ### createRemoveOwnerMetaTransaction[​](#createremoveownermetatransaction "Direct link to createRemoveOwnerMetaTransaction") Creates a meta-transaction to remove an owner from the Safe account, fetching the previous owner if not provided. This function returns a promise that resolves to a meta-transaction object containing the necessary details for the removal. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :------------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous owner. | | `ownerToDelete` | `object` | The public address of the owner to be deleted. | | `ownerToDelete.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | The new threshold value for owner confirmations after removal. | | `overrides` | `object` | Optional Overrides for the default values used in the transaction. | | `overrides.prevOwner` | `string` | If set, it will be used as the previous owner instead of fetching it. | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string` | Address for the WebAuthn signer singleton. | | key | type | description | | :---------------------- | :------- | :--------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | A promise that resolves to the meta-transaction object for removing the specified owner. | | `MetaTransaction.to` | `string` | The target address for the meta-transaction (Safe account address). | | `MetaTransaction.data` | `string` | The encoded function call data for removing the owner. | | `MetaTransaction.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### createDisableModuleMetaTransaction[​](#createdisablemodulemetatransaction "Direct link to createDisableModuleMetaTransaction") Creates a meta-transaction to disable a module from the Safe account. The function automatically fetches the previous module address in the linked list if not provided in the overrides. This function returns a promise that resolves to a meta-transaction object containing the necessary details for disabling the module. * Param Types * Return Types | key | type | description | | :---------------------------- | :------- | :------------------------------------------------------------------------------ | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain to fetch the previous module address. | | `moduleToDisableAddress` | `string` | The address of the module to be disabled. | | `accountAddress` | `string` | The Safe account address for which to disable the module. | | `overrides` | `object` | Optional overrides for the default values used in the transaction. | | `overrides.prevModuleAddress` | `string` | If set, it will be used as the previous module address instead of fetching it. | | `overrides.modulesStart` | `string` | Starting address for the module pagination query. | | `overrides.modulesPageSize` | `bigint` | Page size for the module pagination query. | | key | type | description | | :---------------------- | :------- | :----------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | A promise that resolves to the meta-transaction object for disabling the specified module. | | `MetaTransaction.to` | `string` | The target address for the meta-transaction (Safe account address). | | `MetaTransaction.data` | `string` | The encoded function call data for disabling the module. | | `MetaTransaction.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | ### verifyWebAuthnSignatureForMessageHash[​](#verifywebauthnsignatureformessagehash "Direct link to verifyWebAuthnSignatureForMessageHash") A static method that verifies a webAuthn signed hash message for Passkeys, and can be also used even if the safe account hasn't been deployed yet. * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :------- | :------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain. | | `webAuthnPublicKey` | `object` | The x and y coordinates of the webAuthn public key | | `webAuthnPublicKey.x` | `bigint` | The x coordinate of the webAuthn public key | | `webAuthnPublicKey.y` | `bigint` | The y coordinate of the webAuthn public key | | `message` | `string` | The hashed message to verify | | `signature` | `string` | The signed message | | `overrides` | `object` | Optional overrides for webAuthn verify signature message | | `overrides.webAuthnSignerSingleton` | `string` | Specify the WebAuthn signer singleton | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string` | Specify the EIP-7212 WebAuthn precompile verifier | | `overrides.eip7212WebAuthnContractVerifier` | `string` | Specify the EIP-7212 WebAuthn contract verifier | | key | type | description | | :-------- | :----------------- | :--------------------------------------------- | | `isValid` | `Promise` | Returns true if the message is valid signature | ### getUserOperationEip712Data[​](#getuseroperationeip712data "Direct link to getUserOperationEip712Data") A static method that returns the EIP-712 domain data for a userOp * Param Types * Return Types | key | type | description | | :-------------------------------------------- | :--------------- | :------------------------------------------------------------------------------ | | `userOperation` | `object` | UserOp to hash | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `userOperation.factory` | `string` | account factory, only for new accounts | | `userOperation.factoryData` | `string` | data for account factory (only if account factory exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | Extra gas to pay the bunder | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code | | `userOperation.paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization | | `chainId` | `bigint` | target chain id | | `overrides?` | `object` | optional overrides | | `overrides?.validAfter?` | `bigint` | Timestamp the signature will be valid after | | `overrides?.validUntil?` | `bigint` | Timestamp the signature will be valid until | | `overrides?.entrypointAddress?` | `string` | Address of the entrypoint contract | | `overrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | key | type | description | | :---------------------------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `domain` | `object` | Safe userOperation typed data domain | | `domain.chainId` | `number` | target chain id | | `domain.verifyingContract` | `string` | safe 4337 module contract on traget entrypoint | | `types` | `Record` | The Safe Typed structured data to be signed | | `messageValue` | `object` | Safe userOperation typed message value | | `messageValue.safe` | `string` | the userop sender address of the safe account | | `messageValue.nonce` | `string` | Anti-replay parameter (see “Semi-abstracted Nonce Support” ) | | `messageValue.initCode` | `string` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `messageValue.callData` | `string` | The data to pass to the sender during the main execution call | | `messageValue.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `messageValue.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `messageValue.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `messageValue.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `messageValue.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `messageValue.paymasterAndData` | `string` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas | | `messageValue.signature` | `string` | The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | ### createMigrateToSafeMultiChainSigAccountV1MetaTransactions[​](#createmigratetosafemultichainsigaccountv1metatransactions "Direct link to createMigrateToSafeMultiChainSigAccountV1MetaTransactions") Creates an array of meta-transactions that migrate a deployed Safe from the EntryPoint v0.7 module to the EntryPoint v0.9 `Safe4337MultiChainSignatureModule`. The migration disables the v0.7 module, enables the v0.9 module, and updates the fallback handler. Both modules are stateless, so no storage clearing is required. Unless `{ skipPreflight: true }` is passed, it first verifies on-chain that the account is a Safe (version `>= 1.4.1`) running the old module, where the module is both enabled and the current fallback handler. This turns a would-be cryptic on-chain `AA23` / `AA24` into a clear up-front error. Full example: [`migrate-safe-v07-to-v09.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/migrate-safe-v07-to-v09/migrate-safe-v07-to-v09.ts). #### Usage[​](#usage-11 "Direct link to Usage") ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const bundlerUrl = "https://api.candide.dev/public/v3/11155111"; const smartAccount = new SafeAccount(accountAddress); const migrationMetaTransactions = await smartAccount.createMigrateToSafeMultiChainSigAccountV1MetaTransactions(nodeRpcUrl); // Include these meta-transactions in a UserOperation to execute the migration const userOperation = await smartAccount.createUserOperation( migrationMetaTransactions, nodeRpcUrl, bundlerUrl, ); ``` * Param Types * Return Types | key | type | description | | :--------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | | `nodeRpcUrl` | `string \| Transport \| JsonRpcNode` | The JSON-RPC API URL for the target chain. | | `overrides?` | `object` | Optional overrides for contract addresses, pagination, and the preflight check. | | `overrides?.safeV07ModuleAddress?` | `string` | Override the EntryPoint v0.7 module address to disable. | | `overrides?.safeV09ModuleAddress?` | `string` | Override the EntryPoint v0.9 module address to enable. | | `overrides?.prevModuleAddress?` | `string` | Override the linked-list predecessor of the module being disabled. Defaults to an on-chain lookup. | | `overrides?.modulesStart?` | `string` | Starting address for the module pagination query. | | `overrides?.modulesPageSize?` | `bigint` | Page size for the module pagination query. | | `overrides?.skipPreflight?` | `boolean` | Skip the on-chain check that verifies the account is a Safe (>= 1.4.1) running the old module before building the batch. Defaults to false. | | key | type | description | | :------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction[]` | `object` | Array of meta-transactions that disable the v0.7 module, enable the v0.9 Safe4337MultiChainSignatureModule, and update the fallback handler. | | `MetaTransaction[].to` | `string` | The target address for the meta-transaction. | | `MetaTransaction[].data` | `string` | The encoded function call data. | | `MetaTransaction[].value` | `bigint` | The value to send with the meta-transaction (0n for these operations). | #### Source code[​](#source-code-12 "Direct link to Source code") [createMigrateToSafeMultiChainSigAccountV1MetaTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccountV0_3_0.ts#L429) ### getFallbackHandler[​](#getfallbackhandler "Direct link to getFallbackHandler") Reads the active ERC-4337 fallback handler (the 4337 module) currently set on the Safe. #### Usage[​](#usage-12 "Direct link to Usage") ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const smartAccount = new SafeAccount(accountAddress); const fallbackHandler = await smartAccount.getFallbackHandler(nodeRpcUrl); ``` * Param Types * Return Types | key | type | description | | :----------- | :----------------------------------- | :----------------------------------------- | | `nodeRpcUrl` | `string \| Transport \| JsonRpcNode` | The JSON-RPC API URL for the target chain. | | key | type | description | | :---------------- | :---------------- | :---------------------------------------------------------------------------------------- | | `fallbackHandler` | `Promise` | Resolves to the address of the active ERC-4337 fallback handler (module) set on the Safe. | #### Source code[​](#source-code-13 "Direct link to Source code") [getFallbackHandler](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L2837) ### getSafeVersion[​](#getsafeversion "Direct link to getSafeVersion") Reads the Safe singleton version string via `VERSION()`, for example `"1.4.1"`. #### Usage[​](#usage-13 "Direct link to Usage") ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; const accountAddress = "0x1a02592A3484c2077d2E5D24482497F85e1980C6"; const nodeRpcUrl = "https://rpc2.sepolia.org"; const smartAccount = new SafeAccount(accountAddress); const version = await smartAccount.getSafeVersion(nodeRpcUrl); ``` * Param Types * Return Types | key | type | description | | :----------- | :----------------------------------- | :----------------------------------------- | | `nodeRpcUrl` | `string \| Transport \| JsonRpcNode` | The JSON-RPC API URL for the target chain. | | key | type | description | | :-------- | :---------------- | :------------------------------------------------------------------------------- | | `version` | `Promise` | Resolves to the Safe singleton version string read from VERSION(), e.g. "1.4.1". | #### Source code[​](#source-code-14 "Direct link to Source code") [getSafeVersion](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L2853) ## Audits[​](#audits "Direct link to Audits") * [Audits by Ackee](https://github.com/safe-global/safe-modules/blob/main/modules/4337/docs/v0.3.0/audit.md) * [Contracts developed by the Safe Protocol Team](https://github.com/safe-global/safe-modules/tree/main/modules/4337) --- # Safe Account The `SafeAccount` uses the original Safe Singleton and adds ERC-4337 functionality using a module/fallback handler. For new integrations, use [Safe Unified Account](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) (`SafeMultiChainSigAccountV1`) on EntryPoint v0.9; use the V2/V3 classes below for legacy EntryPoint v0.6/v0.7 compatibility. * The V2 contracts, or `SafeAccountV0_2_0` in AbstractionKit, support EntryPoint v0.6. * The V3 contracts, or `SafeAccountV0_3_0` in AbstractionKit, support EntryPoint v0.7. The contracts were developed by the Safe Team and audited by OpenZeppelin & Ackee Blockchain. ## Audits[​](#audits "Direct link to Audits") * [Version 0.2.0 by Ackee and OpenZeppelin](https://github.com/safe-global/safe-modules/blob/main/modules/4337/docs/v0.2.0/audit.md) * [Version 0.3.0 by Ackee](https://github.com/safe-global/safe-modules/blob/main/modules/4337/docs/v0.3.0/audit.md) To learn more about the contracts, visit the [repository of Safe ERC-4337 Module](https://github.com/safe-global/safe-modules/tree/main/modules/4337). ## Import[​](#import "Direct link to Import") ### Recommended for EntryPoint v0.9[​](#recommended-for-entrypoint-v09 "Direct link to Recommended for EntryPoint v0.9") ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; ``` ### V3 for EntryPoint v0.7[​](#v3-for-entrypoint-v07 "Direct link to V3 for EntryPoint v0.7") ``` import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit"; ``` ### V2 for EntryPoint v0.6[​](#v2-for-entrypoint-v06 "Direct link to V2 for EntryPoint v0.6") ``` import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit"; ``` ## How to Use[​](#how-to-use "Direct link to How to Use") AbstractionKit classes are designed to support the same methods across all versions, ensuring compatibility within the essential classes. This allows developers to move between Safe V2, Safe V3 and the Safe Unified Account seamlessly without breaking functionality or requiring significant code changes. Targeting a different EntryPoint is a change of account class rather than a separate integration — see [Compatibility with Safe Account V3](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#compatibility-with-safe-account-v3). Each version derives a different counterfactual address for the same owner, so a new account is a different account under each class. An already-deployed Safe keeps the address it has; it needs only its module and fallback handler migrated before another version's class can operate on it. To initialize a new Safe Account and calculate its address: ``` const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // Safe owner pub address const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const accountAddress = smartAccount.accountAddress; ``` ### [Safe Unified Account](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) [Recommended Safe account for EntryPoint v0.9, single-chain and multichain](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) [Safe Account V3](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md) [Reference functions for Safe Account V3 with EntryPoint V7](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md) [Safe Account V2](https://docs.candide.dev/wallet/abstractionkit/safe-account-v2.md) [Reference functions for Safe Account V2 with EntryPoint V6](https://docs.candide.dev/wallet/abstractionkit/safe-account-v2.md) --- # Safe Unified Account **SafeMultiChainSigAccountV1** is the recommended Safe account class for new AbstractionKit examples. It works for normal single-chain Safe transactions and enables chain abstraction for multichain Safe Accounts. It extends the standard Safe Account with multichain signature support via Merkle trees, allowing you to sign UserOperations for multiple chains with a single signature. Uses EntryPoint v0.9 and EIP-712 typed data with Merkle proofs. ## Audits[​](#audits "Direct link to Audits") * [Audit Report by Nethermind (NM-0874)](https://github.com/candidelabs/safe-4337-multi-chain-signature-module/blob/main/audit/NM_0874_Candide_safe.pdf) To learn more about the contracts, visit the [Safe 4337 Multi-Chain Signature Module repository](https://github.com/candidelabs/safe-4337-multi-chain-signature-module). ## Import[​](#import "Direct link to Import") ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; ``` ## How to Use[​](#how-to-use "Direct link to How to Use") ``` const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Account address:", smartAccount.accountAddress); ``` This class inherits all methods from [Safe Account](https://docs.candide.dev/wallet/abstractionkit/safe-account.md). The methods below are specific to multichain operations or override the base class with multichain defaults. ## Compatibility with Safe Account V3[​](#compatibility-with-safe-account-v3 "Direct link to Compatibility with Safe Account V3") `SafeMultiChainSigAccountV1` is API-compatible with [`SafeAccountV0_3_0`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md). Both extend the same `SafeAccount` base and expose the same methods with the same signatures, so moving to EntryPoint v0.9 is a change of account class — not a separate integration: | Method | `SafeAccountV0_3_0` | `SafeMultiChainSigAccountV1` | | ----------------------------------------------------------------- | ------------------- | ---------------------------- | | `createAccountAddress(owners, overrides)` | ✅ | ✅ | | `initializeNewAccount(owners, overrides)` | ✅ | ✅ | | `isDeployed(address, rpc)` | ✅ | ✅ | | `createUserOperation(txs, nodeRpc, bundlerRpc, overrides)` | ✅ | ✅ | | `estimateUserOperationGas(userOp, bundlerRpc, overrides)` | ✅ | ✅ | | `signUserOperation(userOp, privateKeys, chainId, options)` | ✅ | ✅ | | `signUserOperationWithSigners(userOp, signers, chainId, options)` | ✅ | ✅ | | `sendUserOperation(userOp, bundlerRpc)` | ✅ | ✅ | Both classes take the same `InitCodeOverrides` shape, so an integration that already threads `entrypointAddress`, `safe4337ModuleAddress`, `safeModuleSetupAddress`, `safeAccountSingleton`, `safeAccountFactoryAddress` and `c2Nonce` through its config needs no new plumbing. The multichain APIs — `signUserOperations`, `signUserOperationsWithSigners`, the Merkle-root EIP-712 helpers and the WebAuthn verifier helpers — are additional methods. Opt into them for chain abstraction, or ignore them and the account behaves as a single-chain Safe. A few differences to be aware of when migrating: * **New accounts get a different address.** A different Safe module and EntryPoint derive a different counterfactual address for the same owner, so `SafeMultiChainSigAccountV1` and `SafeAccountV0_3_0` never refer to the same undeployed account. * **Deployed accounts must be migrated first.** Attaching `SafeMultiChainSigAccountV1` to a Safe that was deployed through `SafeAccountV0_3_0` is not sufficient. Two pieces of Safe configuration have to change first: the v0.9 module must be enabled on the Safe, and the Safe's fallback handler must be updated to point at it. Until both land, the account cannot be used on EntryPoint v0.9 and UserOperations sent to it will fail. Build the batch with [`createMigrateToSafeMultiChainSigAccountV1MetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createmigratetosafemultichainsigaccountv1metatransactions) and send it as a UserOperation from the v0.7 account itself; once it lands, attach the same address to `SafeMultiChainSigAccountV1`. * **The UserOperation type is `UserOperationV9`.** It adds a nullable `eip7702Auth` field to the v0.7 shape; every other field, including the gas fields and the paymaster and factory fields, is unchanged. * **Signatures always use the multichain encoding.** `isMultiChainSignature` is set for you, including on single-chain UserOperations — the single-op path signs the leaf SafeOp hash directly rather than a Merkle root. This is handled inside AbstractionKit; you only need it when [formatting a signature yourself](#manual-eip-712-signing-for-a-single-useroperation). * **Your bundler and paymaster must serve EntryPoint v0.9.** Check that the v0.9 EntryPoint and the Safe multichain signature module are deployed on your target chains before predicting addresses for them. ## Methods[​](#methods "Direct link to Methods") ### initializeNewAccount[​](#initializenewaccount "Direct link to initializeNewAccount") Initializes a new SafeMultiChainSigAccountV1 from a list of owners. Only needed on the first transaction when the account has not been deployed yet. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Account address (sender): " + smartAccount.accountAddress); ``` | key | type | description | | :--------------------------------------------------------------------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | | `owners[]` | `object` | Pass the owner(s) address(es) of the account. It can be a single owner account, a multi-sig, or a WebAuthn | | `owners[].Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `initCodeOverrides?` | `object` | Override values to change the initialization default values | | `initCodeOverrides?.threshold?` | `number` | Signature threshold, defines how many signatures are required. Default is 1. | | `initCodeOverrides?.c2Nonce?` | `bigint` | Create2 nonce used to generate different sender addresses from the same owners. Default is 0. | | `initCodeOverrides?.entrypointAddress?` | `string` | Address of the entry point for transactions or contracts. | | `initCodeOverrides?.safe4337ModuleAddress?` | `string` | Address of the Safe 4337 module. | | `initCodeOverrides?.safeModuleSetupAddress?` | `string` | Address used for setting up the Safe module. | | `initCodeOverrides?.safeAccountSingleton?` | `SafeAccountSingleton` | Safe contract singleton address. Default is "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762". | | `initCodeOverrides?.safeAccountFactoryAddress?` | `string` | Address of the Safe Factory. Default is "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67". | | `initCodeOverrides?.multisendContractAddress?` | `string` | Address of the Safe 4337 multisend contract. Default is "0xa581c4A4DB7175302464fF3C06380BC3270b4037". | | `initCodeOverrides?.webAuthnSharedSigner?` | `string` | Shared signer used for WebAuthn-based authentication. | | `initCodeOverrides?.eip7212WebAuthnPrecompileVerifierForSharedSigner?` | `string` | Verifier contract for WebAuthn precompile, related to the shared signer. | | `initCodeOverrides?.eip7212WebAuthnContractVerifierForSharedSigner?` | `string` | Contract verifier for WebAuthn-based shared signer, compliant with EIP-7212. | | `initCodeOverrides?.onChainIdentifierParams?` | `OnChainIdentifierParamsType` | Parameters for on-chain identifier tracking. | | `initCodeOverrides?.onChainIdentifier?` | `string` | Pre-computed on-chain identifier string. | | key | type | description | | :--------------- | :------- | :-------------------------------------- | | `ECDSASignature` | `string` | ECDSA signature represented as a string | WebauthnPublicKey | key | type | description | | :------------------ | :----------------- | :------------------------------------------------------------------------ | | `authenticatorData` | `ArrayBuffer` | Binary data returned by the authenticator during the Webauthn process | | `clientDataFields` | `string` | Fields associated with the client's Webauthn request data | | `rs` | `[bigint, bigint]` | Array of two bigints representing the 'r' and 's' values of the signature | | key | type | description | | :------------------ | :------------------ | :------------------------------------------------------------------- | | `SafeAccount class` | `SafeAccountV0_3_0` | An instance of the Safe V3 Account and the initialization parameters | #### Source code[​](#source-code "Direct link to Source code") [initializeNewAccount](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L155) ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") Creates a UserOperation for multichain signing. This overrides the base SafeAccount method to always set `isMultiChainSignature: true` and use EntryPoint v0.9. Determines the nonce, fetches gas prices, estimates gas limits, and returns a UserOperation to be signed. You can override any of these values using the overrides parameter. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const transaction: MetaTransaction = { to: "0xTargetAddress", value: 0n, data: "0x", }; const userOperation = await smartAccount.createUserOperation( [transaction], "https://rpc-node-url", "https://bundler-url", ); ``` | Param Name | Param Type | Description | | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Transactions | MetaTransaction\[]key type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation | | Provider RPC | string | The node URL. It is used to fetch the current nonce and fetch gas prices | | Bundler URL | string | The Bundler URL. It is used to fetch the gas limits | | Overrides | CreateUserOperationV9Overrides, optional objectkey type descriptionnonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) callGasLimitPercentageMultiplier number Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler verificationGasLimitPercentageMultiplier number Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler preVerificationGasPercentageMultiplier number Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler maxFeePerGasPercentageMultiplier number Set the maxFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node maxPriorityFeePerGasPercentageMultiplier number Set the maxPriorityFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node state\_override\_set? object Pass state overrides for gas estimation state\_override\_set?.\[address: string] object Overrides for a specific address state\_override\_set?.\[address: string].balance bigint Override the balance of the address state\_override\_set?.\[address: string].nonce bigint Override the nonce of the address state\_override\_set?.\[address: string].code string Override the code of the address state\_override\_set?.\[address: string].state Dictionary\ Override the storage slots of the address state\_override\_set?.\[address: string].stateDiff Dictionary\ Apply state differences to the storage slots of the address dummySignerSignaturePairs\[] object Provide dummy signatures for the operation dummySignerSignaturePairs\[].SignerSignaturePair\[] object Signature Pair dummySignerSignaturePairs\[].SignerSignaturePair\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key dummySignerSignaturePairs\[].SignerSignaturePair\[].signature string signature dummySignerSignaturePairs\[].SignerSignaturePair\[].isContractSignature boolean isContractSignature expectedSigners\[] object A array of expected signers that will sign over the transaction. This improves the gas estimates. expectedSigners\[].Signer ECDSAPublicAddress \| WebauthnPublicKey Signer type which can be either an ECDSA public address or a Webauthn public key webAuthnSharedSigner string Specify the WebAuthn shared signer webAuthnSignerFactory string Specify the WebAuthn signer factory webAuthnSignerSingleton string Specify the WebAuthn signer singleton eip7212WebAuthnPrecompileVerifier string Specify the EIP-7212 WebAuthn precompile verifier eip7212WebAuthnContractVerifier string Specify the EIP-7212 WebAuthn contract verifier safeModuleExecutorFunctionSelector SafeModuleExecutorFunctionSelector Specify the function selector for the safe module executor multisendContractAddress string Specify the multisend contract address gasLevel object Multiplier to determine the gas price for the user operation gasLevel.Slow 1 Default Slow Gas gasLevel.Medium 1.2 Medium Gas with a 20% increase gasLevel.Fast 1.5 Medium Gas with a 50% increase polygonGasStation object To specify the polygon network polygonGasStation.Mainnet v2 Polygon PoS Mainnet polygonGasStation.ZkMainnet zkevm Polygon zkEVM Mainnet polygonGasStation.Amoy amoy Polygon Amoy PoS Testnet polygonGasStation.Cardona cardona Polygon Cardona zkEVM Testnet isMultiChainSignature? boolean Whether this is a multi-chain signature using Merkle proofs parallelPaymasterInitValues? ParallelPaymasterInitValues Paymaster fields for parallel signing (EntryPoint v0.9) skipGasEstimation? boolean When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls that require a placeholder signature. factory string Set the factory address instead of using the calculated value factoryData string Set the factory data instead of using the calculated value | CreateUserOperationV9Overrides allows you to override any of the default parameters for the user operation. It has the same fields as CreateUserOperationV7Overrides. | | Param Name | Param Type | Description | | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------- | | UserOperation | Promise\key type descriptionsender string The account making the operation nonce bigint Anti-replay parameter (see Semi-abstracted Nonce Support) factory string \| null Account factory address, only for new accounts (null if account already exists) factoryData string \| null Data for account factory (null if account already exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string \| null Address of paymaster contract (null if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step (null if no paymaster) paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) paymasterData string \| null Data for paymaster (null if no paymaster) eip7702Auth Authorization7702Hex \| null EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) signature string Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | Returns an unsigned UserOperation for EntryPoint v0.9. | #### Source code[​](#source-code-1 "Direct link to Source code") [createUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L385) ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Estimates gas limits for a multichain-signature UserOperation against a bundler, matching the estimation surface available on the other account classes. Returns a tuple of `[preVerificationGas, verificationGasLimit, callGasLimit]`. #### Usage[​](#usage "Direct link to Usage") ``` import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, } from "abstractionkit"; const bundlerRpc = "https://api.candide.dev/public/v3/11155111"; const nodeRpcUrl = "https://rpc-node-url"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const transaction: MetaTransaction = { to: "0xTargetAddress", value: 0n, data: "0x", }; const userOperation = await smartAccount.createUserOperation( [transaction], nodeRpcUrl, bundlerRpc, ); const [preVerificationGas, verificationGasLimit, callGasLimit] = await smartAccount.estimateUserOperationGas(userOperation, bundlerRpc); ``` * Param Types * Return Types | key | type | description | | :-------------------------------------- | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `UserOperationV9` | The UserOperation to estimate gas for. | | `bundlerRpc` | `string \| Transport \| Bundler` | The bundler RPC URL used to run eth\_estimateUserOperationGas. | | `overrides?` | `object` | Optional overrides for the estimation, such as bundler state overrides and the dummy signatures used while estimating. | | `overrides?.stateOverrideSet?` | `StateOverrideSet` | State overrides passed to the bundler during estimation. | | `overrides?.dummySignerSignaturePairs?` | `SignerSignaturePair[]` | Dummy signer/signature pairs used as placeholders during estimation. | | `overrides?.expectedSigners?` | `Signer[]` | The signers whose signatures will be produced at sign time. Used to build dummy signatures when dummySignerSignaturePairs is not provided. | | key | type | description | | :--------------------------------------------------------- | :---------------------------------- | :----------------------------------------------------------------------------------------------- | | `[preVerificationGas, verificationGasLimit, callGasLimit]` | `Promise<[bigint, bigint, bigint]>` | Resolves to a tuple of the estimated preVerificationGas, verificationGasLimit, and callGasLimit. | #### Source code[​](#source-code-2 "Direct link to Source code") [estimateUserOperationGas](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L461) ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") Signs a single UserOperation with one or more private keys. This overrides the base method to always encode the signature with the multichain scheme (`isMultiChainSignature: true`), which is how this account validates even single-chain UserOperations. ``` signUserOperation( userOperation: UserOperationV9, privateKeys: string[], chainId: bigint, options?: SafeSignatureOptions, ): string ``` Pass one private key per owner required by the threshold. `options` accepts the usual `SafeSignatureOptions` timing fields (`validAfter`, `validUntil`) and module address override; `multiChainMerkleProof` is rejected here, use [`signUserOperations`](#signuseroperations) for multi-op Merkle signatures. example.ts ``` userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], 11155111n, // chainId ); ``` #### Source code[​](#source-code-3 "Direct link to Source code") [signUserOperation](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L585) ### signUserOperations[​](#signuseroperations "Direct link to signUserOperations") Signs multiple UserOperations across different chains with a single signing session. Builds a Merkle tree from the UserOperation hashes, signs the Merkle root, and returns an array of signatures (one per UserOperation) each containing the Merkle proof for its chain. If only one UserOperation is provided, falls back to single-chain signing. In AbstractionKit `v0.3.5`, `UserOperationToSignWithOverrides` split the previous catch-all `overrides` field into `options` for normal signing options and `webAuthnSignatureOverrides` for WebAuthn-specific signature encoding values. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); // Create UserOperations for each chain const userOp1 = await smartAccount.createUserOperation([tx], nodeUrl1, bundlerUrl1); const userOp2 = await smartAccount.createUserOperation([tx], nodeUrl2, bundlerUrl2); // Sign all UserOperations with one signing session const signatures = smartAccount.signUserOperations( [ { userOperation: userOp1, chainId: 11155111n }, { userOperation: userOp2, chainId: 11155420n }, ], [ownerPrivateKey], ); // Attach signatures userOp1.signature = signatures[0]; userOp2.signature = signatures[1]; ``` | key | type | description | | :--------------------- | :---------------------- | :-------------------------------------------------------------------------------- | | `userOperationsToSign` | `UserOperationToSign[]` | Array of UserOperations with their target chain IDs and optional validity windows | | `privateKeys` | `string[]` | Private keys of the Safe owners to sign with | UserOperationToSign | key | type | description | | :---------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `bigint` | Target chain ID for this UserOperation | | `userOperation` | `UserOperationV9` | The UserOperation to sign | | `validAfter?` | `bigint` | Timestamp the signature will be valid after | | `validUntil?` | `bigint` | Timestamp the signature will be valid until | | `options?` | `MultiChainSigningOptions` | Per-operation signing options such as the Safe 4337 module address. In v0.3.5 this replaced the generic overrides field for non-WebAuthn options. | | `webAuthnSignatureOverrides?` | `WebAuthnSignatureOverrides` | Per-operation WebAuthn signature encoding overrides, such as isInit or WebAuthn verifier addresses. In v0.3.5 this replaced the WebAuthn-specific values previously carried in overrides. | | key | type | description | | :----------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `signatures` | `string[]` | Array of signatures, one per UserOperation. Each contains the Merkle proof for its respective chain. If only one UserOperation is provided, falls back to single-chain signing. | #### Source code[​](#source-code-4 "Direct link to Source code") [signUserOperations](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L672) ### signUserOperationsWithSigners[​](#signuseroperationswithsigners "Direct link to signUserOperationsWithSigners") Multi-op variant that signs a Merkle-rooted bundle of UserOperations using one or more [`ExternalSigner`s](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of raw private keys. One signing session covers every chain in the bundle. Since `v0.3.8`, this path accepts typed-data-only signers such as `fromViemWalletClient` as well as raw-hash signers. Pass `chainId` inside each `UserOperationToSign` item so the SDK can build the per-chain typed-data payloads. ``` signUserOperationsWithSigners( userOperationsToSign: UserOperationToSign[], signers: ExternalSigner[], ): Promise ``` * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromViem } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const viemAccount = privateKeyToAccount("0x..."); const signatures = await smartAccount.signUserOperationsWithSigners( [ { userOperation: userOp1, chainId: 11155111n }, { userOperation: userOp2, chainId: 11155420n }, ], [fromViem(viemAccount)], ); userOp1.signature = signatures[0]; userOp2.signature = signatures[1]; ``` | Param Name | Param Type | Description | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | userOperationsToSign | UserOperationToSign\[]key type descriptionchainId bigint Target chain ID for this UserOperation userOperation UserOperationV9 The UserOperation to sign validAfter? bigint Timestamp the signature will be valid after validUntil? bigint Timestamp the signature will be valid until options? MultiChainSigningOptions Per-operation signing options such as the Safe 4337 module address. In v0.3.5 this replaced the generic overrides field for non-WebAuthn options. webAuthnSignatureOverrides? WebAuthnSignatureOverrides Per-operation WebAuthn signature encoding overrides, such as isInit or WebAuthn verifier addresses. In v0.3.5 this replaced the WebAuthn-specific values previously carried in overrides. | UserOperations with their chain IDs and optional validity windows. | | signers | ExternalSigner\[] | One signer per owner required by the threshold, in any order. The built-in adapters `fromPrivateKey`, `fromViem`, `fromEthersWallet`, and `fromViemWalletClient` all work here. | | Param Name | Param Type | Description | | :--------- | :------------------ | :------------------------------------------------------------------ | | signatures | Promise\ | One formatted signature per input UserOperation, in the same order. | See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters, and the [multi-chain add-owner example](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner-with-external-signer.ts) for a full end-to-end flow. #### Source code[​](#source-code-5 "Direct link to Source code") [signUserOperationsWithSigners](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L768) ### getMultiChainSingleSignatureUserOperationsEip712Hash[​](#getmultichainsinglesignatureuseroperationseip712hash "Direct link to getMultiChainSingleSignatureUserOperationsEip712Hash") Static method. Computes the EIP-712 hash of the Merkle tree root for a set of UserOperations. This is the hash that wallet signers (browser extensions, hardware wallets) sign to approve multiple cross-chain operations at once. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const hash = SafeAccount.getMultiChainSingleSignatureUserOperationsEip712Hash([ { userOperation: userOp1, chainId: 11155111n }, { userOperation: userOp2, chainId: 11155420n }, ]); ``` | key | type | description | | :--------------------- | :---------------------- | :-------------------------------------------------- | | `userOperationsToSign` | `UserOperationToSign[]` | Array of UserOperations with their target chain IDs | | `overrides?` | `object` | Optional overrides for the Safe 4337 module address | UserOperationToSign | key | type | description | | :---------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `bigint` | Target chain ID for this UserOperation | | `userOperation` | `UserOperationV9` | The UserOperation to sign | | `validAfter?` | `bigint` | Timestamp the signature will be valid after | | `validUntil?` | `bigint` | Timestamp the signature will be valid until | | `options?` | `MultiChainSigningOptions` | Per-operation signing options such as the Safe 4337 module address. In v0.3.5 this replaced the generic overrides field for non-WebAuthn options. | | `webAuthnSignatureOverrides?` | `WebAuthnSignatureOverrides` | Per-operation WebAuthn signature encoding overrides, such as isInit or WebAuthn verifier addresses. In v0.3.5 this replaced the WebAuthn-specific values previously carried in overrides. | | key | type | description | | :----- | :------- | :--------------------------------------------------------------------- | | `hash` | `string` | The EIP-712 hash of the Merkle tree root, ready for signing by wallets | #### Source code[​](#source-code-6 "Direct link to Source code") [getMultiChainSingleSignatureUserOperationsEip712Hash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L902) ### getMultiChainSingleSignatureUserOperationsEip712Data[​](#getmultichainsinglesignatureuseroperationseip712data "Direct link to getMultiChainSingleSignatureUserOperationsEip712Data") Static method. Returns the EIP-712 typed data components (domain, types, message value) for a multi-chain Merkle tree root. Use this for wallet-compatible signing via `eth_signTypedData_v4`. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const { domain, types, messageValue } = SafeAccount.getMultiChainSingleSignatureUserOperationsEip712Data([ { userOperation: userOp1, chainId: 11155111n }, { userOperation: userOp2, chainId: 11155420n }, ]); // Use with a wallet provider const signature = await wallet.signTypedData(domain, types, messageValue); ``` | key | type | description | | :--------------------- | :---------------------- | :----------------------------------------------------- | | `userOperationsToSign` | `UserOperationToSign[]` | Array of UserOperations with their target chain IDs | | `overrides?` | `object` | Optional overrides for module and entrypoint addresses | UserOperationToSign | key | type | description | | :---------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `bigint` | Target chain ID for this UserOperation | | `userOperation` | `UserOperationV9` | The UserOperation to sign | | `validAfter?` | `bigint` | Timestamp the signature will be valid after | | `validUntil?` | `bigint` | Timestamp the signature will be valid until | | `options?` | `MultiChainSigningOptions` | Per-operation signing options such as the Safe 4337 module address. In v0.3.5 this replaced the generic overrides field for non-WebAuthn options. | | `webAuthnSignatureOverrides?` | `WebAuthnSignatureOverrides` | Per-operation WebAuthn signature encoding overrides, such as isInit or WebAuthn verifier addresses. In v0.3.5 this replaced the WebAuthn-specific values previously carried in overrides. | | key | type | description | | :------------- | :------------------------------------------------- | :---------------------------------------------------------------------- | | `domain` | `{ verifyingContract: string }` | The EIP-712 domain with the Safe 4337 module as verifying contract | | `types` | `Record` | The EIP-712 type definitions for multi-chain operations | | `messageValue` | `{ merkleTreeRoot: string }` | The message containing the Merkle tree root of all UserOperation hashes | #### Source code[​](#source-code-7 "Direct link to Source code") [getMultiChainSingleSignatureUserOperationsEip712Data](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L936) ### formatSignaturesToUseroperationsSignatures[​](#formatsignaturestouseroperationssignatures "Direct link to formatSignaturesToUseroperationsSignatures") Static method. Formats EIP-712 signatures (from wallet signing) into UserOperation signatures with Merkle proofs. Use this after signing with `getMultiChainSingleSignatureUserOperationsEip712Hash` or `getMultiChainSingleSignatureUserOperationsEip712Data`. * example.ts * Param Types * Return Type example.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const userOpsToSign = [ { userOperation: userOp1, chainId: 11155111n }, // Since v0.3.5, generic signing options and WebAuthn signature overrides // use separate fields instead of the previous catch-all `overrides` field. { userOperation: userOp2, chainId: 11155420n, options: { safe4337ModuleAddress: "0x..." }, webAuthnSignatureOverrides: { isInit: userOp2.nonce === 0n }, }, ]; const signerSignaturePairs = [ { signer: ownerAddress, signature: eip712Signature }, ]; const signatures = SafeAccount.formatSignaturesToUseroperationsSignatures( userOpsToSign, signerSignaturePairs, ); userOp1.signature = signatures[0]; userOp2.signature = signatures[1]; ``` | key | type | description | | :--------------------- | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | `userOperationsToSign` | `UserOperationToSignWithOverrides[]` | Array of UserOperations with their target chain IDs and optional per-operation options / WebAuthn signature overrides | | `signerSignaturePairs` | `SignerSignaturePair[]` | Array of signer address and signature pairs from EIP-712 signing | UserOperationToSign | key | type | description | | :---------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `bigint` | Target chain ID for this UserOperation | | `userOperation` | `UserOperationV9` | The UserOperation to sign | | `validAfter?` | `bigint` | Timestamp the signature will be valid after | | `validUntil?` | `bigint` | Timestamp the signature will be valid until | | `options?` | `MultiChainSigningOptions` | Per-operation signing options such as the Safe 4337 module address. In v0.3.5 this replaced the generic overrides field for non-WebAuthn options. | | `webAuthnSignatureOverrides?` | `WebAuthnSignatureOverrides` | Per-operation WebAuthn signature encoding overrides, such as isInit or WebAuthn verifier addresses. In v0.3.5 this replaced the WebAuthn-specific values previously carried in overrides. | | key | type | description | | :----------- | :--------- | :---------------------------------------------------------------------- | | `signatures` | `string[]` | Array of formatted signatures with Merkle proofs, one per UserOperation | #### Source code[​](#source-code-8 "Direct link to Source code") [formatSignaturesToUseroperationsSignatures](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeMultiChainSigAccount.ts#L1015) ### Manual EIP-712 signing for a single UserOperation[​](#manual-eip-712-signing-for-a-single-useroperation "Direct link to Manual EIP-712 signing for a single UserOperation") `SafeMultiChainSigAccountV1` also validates single-chain UserOperations through its multichain signature scheme. If you drive `signTypedData` yourself instead of using `signUserOperationWithSigners`, format the raw wallet signature with `isMultiChainSignature: true`. example.ts ``` import { EIP712_SAFE_OPERATION_PRIMARY_TYPE, SafeMultiChainSigAccountV1 as SafeAccount, } from "abstractionkit"; const eip712Data = SafeAccount.getUserOperationEip712Data( userOperation, chainId, ); const signature = await walletClient.signTypedData({ domain: eip712Data.domain, types: eip712Data.types, primaryType: EIP712_SAFE_OPERATION_PRIMARY_TYPE, message: eip712Data.messageValue, }); userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [{ signer: ownerAddress, signature }], { isMultiChainSignature: true }, ); ``` Full example: [`eip-712-signing.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-712-signing/eip-712-signing.ts). ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") Inherited from [Safe Account](https://docs.candide.dev/wallet/abstractionkit/safe-account.md). Sends a signed UserOperation to the bundler. example.ts ``` const account = new SafeAccount(userOperation.sender); const response = await account.sendUserOperation(userOperation, bundlerUrl); const receipt = await response.included(); ``` ## Owner Management[​](#owner-management "Direct link to Owner Management") `SafeMultiChainSigAccountV1` inherits the Safe owner-management helpers from the base `SafeAccount` class. The two add-owner methods below are the ones used in the guides; the remaining helpers (swap owner, remove owner, module management) are documented on [Safe Account V3](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md). ### createAddOwnerWithThresholdMetaTransactions[​](#createaddownerwiththresholdmetatransactions "Direct link to createAddOwnerWithThresholdMetaTransactions") Builds the MetaTransactions needed to add a new owner (an ECDSA address or a WebAuthn public key) and set a new threshold. If the new owner is a WebAuthn signer whose verifier contract is not yet deployed, the returned list also contains a MetaTransaction that deploys the verifier, so it resolves to one or two MetaTransactions. ``` createAddOwnerWithThresholdMetaTransactions( newOwner: Signer, threshold: number, overrides?: { nodeRpcUrl?: string; eip7212WebAuthnPrecompileVerifier?: string; eip7212WebAuthnContractVerifier?: string; webAuthnSignerFactory?: string; webAuthnSignerSingleton?: string; webAuthnSignerProxyCreationCode?: string; }, ): Promise ``` `overrides.nodeRpcUrl` is required when adding a WebAuthn owner, so the SDK can check whether the new owner's verifier is already deployed. * example.ts * Param Types * Return Type example.ts ``` const newOwnerPublicAddress = "0xNewOwnerAddress"; const addOwnerMetaTransactions = await smartAccount.createAddOwnerWithThresholdMetaTransactions( newOwnerPublicAddress, 2, // new threshold ); const userOperation = await smartAccount.createUserOperation( addOwnerMetaTransactions, nodeRpcUrl, bundlerUrl, ); ``` | key | type | description | | :-------------------------------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------------- | | `newOwner` | `object` | The public address of the new owner to be added | | `newOwner.Signer` | `ECDSAPublicAddress \| WebauthnPublicKey` | Signer type which can be either an ECDSA public address or a Webauthn public key | | `threshold` | `number` | The new threshold value for owner confirmations. | | `overrides` | `object` | Optional Overrides for the default values used in the transaction. | | `overrides.nodeRpcUrl` | `string?` | The JSON-RPC API url for the target chain, to check if the new webauthn owner is deployed or not | | `overrides.eip7212WebAuthnPrecompileVerifier` | `string?` | Address for the EIP-7212 WebAuthn precompile verifier. | | `overrides.eip7212WebAuthnContractVerifier` | `string?` | Address for the EIP-7212 WebAuthn contract verifier. | | `overrides.webAuthnSignerFactory` | `string?` | Address for the WebAuthn signer factory. | | `overrides.webAuthnSignerSingleton` | `string?` | Address for the WebAuthn signer singleton. | | key | type | description | | :--------------------------------- | :------- | :------------------------------------------------------------------------------- | | `Promise` | `object` | The meta-transaction object for adding a new owner with the specified threshold. | | `Promise.to` | `string` | The target address for the meta-transaction (Safe account address). | | `Promise.data` | `string` | The encoded function call data for adding an owner. | | `Promise.value` | `bigint` | The value to be sent with the meta-transaction, set to zero for this function. | #### Source code[​](#source-code-9 "Direct link to Source code") [createAddOwnerWithThresholdMetaTransactions](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L2475) ### createStandardAddOwnerWithThresholdMetaTransaction[​](#createstandardaddownerwiththresholdmetatransaction "Direct link to createStandardAddOwnerWithThresholdMetaTransaction") Builds the single standard `addOwnerWithThreshold` MetaTransaction for an owner public address. Unlike the plural variant above, it takes a plain address, performs no WebAuthn verifier handling, and returns synchronously. ``` createStandardAddOwnerWithThresholdMetaTransaction( newOwner: string, threshold: number, ): MetaTransaction ``` example.ts ``` const addOwnerMetaTransaction = smartAccount.createStandardAddOwnerWithThresholdMetaTransaction( "0xNewOwnerAddress", 2, // new threshold ); ``` #### Source code[​](#source-code-10 "Direct link to Source code") [createStandardAddOwnerWithThresholdMetaTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L2536) ## Gas Sponsorship[​](#gas-sponsorship "Direct link to Gas Sponsorship") Use `CandidePaymaster` for gas sponsorship with multichain operations. The paymaster uses a two-phase flow: **commit** (before signing) and **finalize** (after signing). example.ts ``` import { CandidePaymaster } from "abstractionkit"; // One paymaster instance per chain const paymaster1 = new CandidePaymaster("https://api.candide.dev/public/v3/11155111"); const paymaster2 = new CandidePaymaster("https://api.candide.dev/public/v3/11155420"); // Phase 1: Commit (before signing) const commitContext = { signingPhase: "commit" as const }; const commitOverrides = { preVerificationGasPercentageMultiplier: 20 }; const [{ userOperation: commitOp1 }, { userOperation: commitOp2 }] = await Promise.all([ paymaster1.createSponsorPaymasterUserOperation( smartAccount, userOperation1, bundlerUrl1, undefined, commitContext, commitOverrides ), paymaster2.createSponsorPaymasterUserOperation( smartAccount, userOperation2, bundlerUrl2, undefined, commitContext, commitOverrides ), ]); userOperation1 = commitOp1; userOperation2 = commitOp2; // Sign here with signUserOperations... // Phase 2: Finalize (after signing) const finalizeContext = { signingPhase: "finalize" as const }; const [{ userOperation: finalOp1 }, { userOperation: finalOp2 }] = await Promise.all([ paymaster1.createSponsorPaymasterUserOperation( smartAccount, userOperation1, bundlerUrl1, undefined, finalizeContext ), paymaster2.createSponsorPaymasterUserOperation( smartAccount, userOperation2, bundlerUrl2, undefined, finalizeContext ), ]); userOperation1 = finalOp1; userOperation2 = finalOp2; ``` --- # Simple 7702 Account (EntryPoint v0.8) The `Simple7702Account` targets **EntryPoint v0.8** and supports the full feature set including gas sponsorship and ERC-20 gas payments through the Candide paymaster. Moving to EntryPoint v0.9? See the [Simple 7702 Account (EntryPoint v0.9)](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v09.md) reference and the [`07-migrate-v08-to-v09.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/07-migrate-v08-to-v09.ts) example for a complete v0.8 to v0.9 migration. [EIP-7702 QuickStart](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md) [Learn how to upgrade an EOA to a Smart Account with batch transactions and gas sponsorship capabilities](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md) ## Smart Contracts and Audits[​](#smart-contracts-and-audits "Direct link to Smart Contracts and Audits") The contracts were developed by the Ethereum Foundation Account Abstraction Team and audited by Spearbit. * [Smart Contracts](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/Simple7702Account.sol) * [Release](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.8.0) * [Audit](https://github.com/eth-infinitism/account-abstraction/blob/develop/audits/SpearBit%20Account%20Abstraction%20Security%20Review%20-%20Mar%202025.pdf) ## How to Use[​](#how-to-use "Direct link to How to Use") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using `Simple7702Account`, you must have: * **Node.js**: Version 18.0 or higher. * **EIP-7702 Compatible Network**: Ethereum mainnet, Sepolia, Optimism, Base, or other EIP-7702 enabled chains. * **Private Key Access**: Required for signing authorizations and user operations. ### Installation[​](#installation "Direct link to Installation") ``` npm install abstractionkit ``` ### Usage[​](#usage "Direct link to Usage") ``` import { Simple7702Account } from "abstractionkit"; const delegatorPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // EOA public key const smartAccount = new Simple7702Account(delegatorPublicAddress); ``` **Constructor defaults:** * `entrypointAddress`: `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108` (EntryPoint v0.8) * `delegateeAddress`: `0xe6Cae83BdE06E4c305530e199D7217f42808555B` Both can be overridden by passing an `overrides` object as the second constructor argument. ``` const simple7702Account = new Simple7702Account(eoaAddress, { entrypointAddress: "0x...", // optional delegateeAddress: "0x...", // optional }); ``` ## Essential Methods[​](#essential-methods "Direct link to Essential Methods") ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") Creates a UserOperation for EIP-7702 accounts that can be sent to bundlers for execution. * example.ts * Param Types * Return Type example.ts ``` import { Simple7702Account } from "abstractionkit"; const delegatorPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = new Simple7702Account(delegatorPublicAddress); const chainId = 11155111n; // Sepolia const transactions = [ { to: "0x...", value: 0n, data: "0x...", }, ]; const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // provider RPC "https://api.candide.dev/public/v3/11155111", // bundler RPC { eip7702Auth: { chainId, // chainId at which the account will be authorized }, // Optional overrides maxFeePerGas: 20000000000n, maxPriorityFeePerGas: 2000000000n, } ); ``` | key | type | description | | :----------------------------------------------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactions` | `SimpleMetaTransaction[]` | Array of transactions to include in the user operation | | `providerRpc?` | `string` | Optional JSON-RPC provider URL for blockchain queries | | `bundlerRpc?` | `string` | Optional bundler RPC URL for gas estimation | | `overrides?` | `object` | Optional overrides for user operation creation | | `overrides?.eip7702Auth?` | `object` | EIP-7702 authorization parameters for EOA delegation | | `overrides?.eip7702Auth?.chainId` | `string` | Chain ID in hexadecimal format where the authorization is valid | | `overrides?.eip7702Auth?.address` | `string` | Address to authorize for the EOA delegation | | `overrides?.eip7702Auth?.nonce` | `string` | Authorization nonce in hexadecimal format | | `overrides?.eip7702Auth?.yParity` | `string` | Y parity of the authorization signature | | `overrides?.eip7702Auth?.r` | `string` | R component of the authorization signature | | `overrides?.eip7702Auth?.s` | `string` | S component of the authorization signature | | `overrides?.nonce?` | `string` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `overrides?.callData?` | `string` | The data to pass to the sender during the main execution call | | `overrides?.callGasLimit?` | `bigint` | The amount of gas to allocate the main execution call | | `overrides?.verificationGasLimit?` | `bigint` | The amount of gas to allocate for the verification step | | `overrides?.preVerificationGas?` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `overrides?.maxFeePerGas?` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `overrides?.maxPriorityFeePerGas?` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `overrides?.callGasLimitPercentageMultiplier?` | `number` | Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.verificationGasLimitPercentageMultiplier?` | `number` | Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.preVerificationGasPercentageMultiplier?` | `number` | Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.maxFeePerGasPercentageMultiplier?` | `number` | Set the maxFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node | | `overrides?.maxPriorityFeePerGasPercentageMultiplier?` | `number` | Set the maxPriorityFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node | | `overrides?.state_override_set?` | `StateOverrideSet` | Pass state override set for simulation | | `overrides?.dummySignature?` | `string` | Dummy signature for gas estimation | | `overrides?.skipGasEstimation?` | `boolean` | When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls. | SimpleMetaTransaction | key | type | description | | :---------------------------- | :------- | :---------------------------------------------------------------------------- | | `SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Simple7702Account. | | `SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `SimpleMetaTransaction.data` | `string` | The call data for the transaction | | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The constructed user operation for EIP-7702 | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") Signs a UserOperation with the provided private key for the EIP-7702 account. * example.ts * Param Types * Return Type example.ts ``` const signature = smartAccount.signUserOperation( userOperation, "0x...private-key", 11155111n // chain ID ); userOperation.signature = signature; ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to sign | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `privateKey` | `string` | Private key to sign the user operation with | | `chainId` | `bigint` | Chain ID for the target blockchain | | key | type | description | | :---------- | :------- | :----------------------------------- | | `signature` | `string` | The signature for the user operation | ### signUserOperationWithSigner[​](#signuseroperationwithsigner "Direct link to signUserOperationWithSigner") Signs a UserOperation using an [`ExternalSigner`](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of a raw private key. Integrates viem, ethers, browser wallets, hardware wallets, HSMs, and MPC services through the same API. Since AbstractionKit `v0.3.5`, `Simple7702Account` accepts signers that implement either `signTypedData` or `signHash`. The SDK prefers `signTypedData` when available, so JSON-RPC wallets and viem `WalletClient` instances can sign EntryPoint v0.8 UserOperations without raw-hash signing support. Raw-hash signers continue to work. example.ts ``` import { fromViemWalletClient } from "abstractionkit"; import { createWalletClient, custom } from "viem"; const walletClient = createWalletClient({ account: delegatorPublicAddress, chain, transport: custom(window.ethereum), }); userOperation.signature = await smartAccount.signUserOperationWithSigner( userOperation, fromViemWalletClient(walletClient), 11155111n, // chain ID ); ``` See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters and custom-signer integrations. ### getUserOperationEip712Data[​](#getuseroperationeip712data "Direct link to getUserOperationEip712Data") Builds the EIP-712 typed data payload for a UserOperation under the EntryPoint v0.8 domain. Use this static helper when you need to inspect the wallet prompt payload or drive a custom `signTypedData` primitive directly. example.ts ``` import { Simple7702Account } from "abstractionkit"; const typedData = Simple7702Account.getUserOperationEip712Data( userOperation, 11155111n, ); const signature = await walletClient.signTypedData(typedData); userOperation.signature = signature; ``` `getUserOperationEip712TypedData` was renamed in `v0.3.8` and moved from an instance method to a static helper. Use `getUserOperationEip712Hash(userOperation, chainId, overrides?)` when you only need the digest. ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") Sends a signed UserOperation to the bundler for execution on-chain. * example.ts * Param Types * Return Type example.ts ``` const response = await smartAccount.sendUserOperation( userOperation, "https://api.candide.dev/public/v3/11155111" // bundler URL ); console.log("UserOperation hash:", response.userOperationHash); // Wait for the transaction to be included const receipt = await response.included(); console.log("Transaction receipt:", receipt); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The signed user operation to send | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `bundlerRpc` | `string` | Bundler RPC URL to send the user operation to | | key | type | description | | :--------------------------- | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `response` | `object` | Response containing user operation hash and bundler details | | `response.userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `response.bundler` | `Bundler` | The Bundler class | | `response.entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `response.included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | SendUseroperationResponse | key | type | description | | :------------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `bundler` | `Bundler` | The Bundler class | | `entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | BundlerJsonRpcError | key | type | description | | :-------- | :------- | :------------------------------------ | | `code` | `number` | Bundler RPC error code | | `message` | `string` | Bundler RPC error message description | UserOperationReceiptResult | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") ### createAccountCallData[​](#createaccountcalldata "Direct link to createAccountCallData") Creates call data for a basic transaction with specified target, value, and data. * example.ts * Param Types * Return Type example.ts ``` const callData = Simple7702Account.createAccountCallData( "0x...", // to address 1000000000000000000n, // value in wei "0x..." // transaction data ); ``` | key | type | description | | :------ | :------- | :----------------------------------- | | `to` | `string` | Target address for the transaction | | `value` | `bigint` | Value to transfer in the transaction | | `data` | `string` | Call data for the transaction | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### createAccountCallDataSingleTransaction[​](#createaccountcalldatasingletransaction "Direct link to createAccountCallDataSingleTransaction") Creates call data for a single SimpleMetaTransaction. * example.ts * Param Types * Return Type example.ts ``` const metaTransaction = { to: "0x...", value: 0n, data: "0x...", }; const callData = Simple7702Account.createAccountCallDataSingleTransaction(metaTransaction); ``` | key | type | description | | :-------------------------------------------- | :------- | :---------------------------------------------------------------------------- | | `metaTransaction` | `object` | The SimpleMetaTransaction to create call data for | | `metaTransaction.SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Simple7702Account. | | `metaTransaction.SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `metaTransaction.SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `metaTransaction.SimpleMetaTransaction.data` | `string` | The call data for the transaction | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### createAccountCallDataBatchTransactions[​](#createaccountcalldatabatchtransactions "Direct link to createAccountCallDataBatchTransactions") Creates call data for batching multiple SimpleMetaTransactions together. * example.ts * Param Types * Return Type example.ts ``` const transactions = [ { to: "0x...", value: 0n, data: "0x..." }, { to: "0x...", value: 0n, data: "0x..." }, ]; const callData = Simple7702Account.createAccountCallDataBatchTransactions(transactions); ``` | key | type | description | | :------------- | :------------------------ | :------------------------------------------------ | | `transactions` | `SimpleMetaTransaction[]` | Array of SimpleMetaTransactions to batch together | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### prependTokenPaymasterApproveToCallDataStatic[​](#prependtokenpaymasterapprovetocalldatastatic "Direct link to prependTokenPaymasterApproveToCallDataStatic") Prepends a token approval transaction to existing call data for use with token paymasters. * example.ts * Param Types * Return Type example.ts ``` const callDataWithApproval = Simple7702Account.prependTokenPaymasterApproveToCallDataStatic( "0x...", // existing call data "0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address "0x...", // paymaster address 1000000n // approve amount (1 USDC) ); ``` | key | type | description | | :----------------- | :------- | :-------------------------------------------- | | `callData` | `string` | Existing call data to prepend the approval to | | `tokenAddress` | `string` | Address of the ERC-20 token to approve | | `paymasterAddress` | `string` | Address of the paymaster contract | | `approveAmount` | `bigint` | Amount of tokens to approve for the paymaster | | key | type | description | | :--------- | :------- | :-------------------------------------- | | `callData` | `string` | Call data with token approval prepended | ### prependTokenPaymasterApproveToCallData[​](#prependtokenpaymasterapprovetocalldata "Direct link to prependTokenPaymasterApproveToCallData") Instance method to prepend token approval to call data for paymaster usage. * example.ts * Param Types * Return Type example.ts ``` const callDataWithApproval = smartAccount.prependTokenPaymasterApproveToCallData( "0x...", // existing call data "0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address "0x...", // paymaster address 1000000n // approve amount ); ``` | key | type | description | | :----------------- | :------- | :-------------------------------------------- | | `callData` | `string` | Existing call data to prepend the approval to | | `tokenAddress` | `string` | Address of the ERC-20 token to approve | | `paymasterAddress` | `string` | Address of the paymaster contract | | `approveAmount` | `bigint` | Amount of tokens to approve for the paymaster | | key | type | description | | :--------- | :------- | :-------------------------------------- | | `callData` | `string` | Call data with token approval prepended | ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Estimates gas limits for a UserOperation using the bundler. * example.ts * Param Types * Return Type example.ts ``` const [preVerificationGas, verificationGasLimit, callGasLimit] = await smartAccount.estimateUserOperationGas( userOperation, "https://api.candide.dev/public/v3/11155111", { // Optional overrides stateOverrideSet: {...}, dummySignature: "0x...", } ); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to estimate gas for | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `bundlerRpc` | `string` | Bundler RPC URL for gas estimation | | `overrides?` | `object` | Optional overrides for gas estimation | | `overrides?.state_override_set?` | `StateOverrideSet` | State override set for simulation | | `overrides?.dummySignature?` | `string` | Dummy signature for gas estimation | | key | type | description | | :---------- | :------------------------- | :----------------------------------------------------------------- | | `gasLimits` | `[bigint, bigint, bigint]` | Tuple of \[preVerificationGas, verificationGasLimit, callGasLimit] | ## Delegation Methods[​](#delegation-methods "Direct link to Delegation Methods") ### isDelegatedToThisAccount[​](#isdelegatedtothisaccount "Direct link to isDelegatedToThisAccount") Checks if the EOA is currently delegated to the expected smart account address via EIP-7702. Returns `true` only when delegated to the account's `delegateeAddress`. * example.ts * Param Types * Return Type example.ts ``` const isDelegated = await smartAccount.isDelegatedToThisAccount( "https://ethereum-sepolia-rpc.publicnode.com" ); if (isDelegated) { console.log("EOA is delegated to this smart account"); } else { console.log("EOA is not delegated"); } ``` | key | type | description | | :------------ | :------- | :------------------------- | | `providerRpc` | `string` | Ethereum JSON-RPC node URL | | key | type | description | | :------------ | :-------- | :-------------------------------------------------------------------- | | `isDelegated` | `boolean` | true if the EOA is delegated to the expected address, false otherwise | ### createRevokeDelegationTransaction[​](#createrevokedelegationtransaction "Direct link to createRevokeDelegationTransaction") Creates a signed EIP-7702 transaction that revokes the delegation, restoring the EOA to a regular account. The transaction delegates to `address(0)`, removing the smart account code from the EOA. This is a regular Ethereum transaction (type `0x04`), not a UserOperation. The EOA needs native tokens to pay for gas. note Revocation cannot be done via a UserOperation because the authorization list is processed before execution, which would remove the account's code mid-transaction. * example.ts * Param Types * Return Type example.ts ``` const signedTransaction = await smartAccount.createRevokeDelegationTransaction( "0x...private-key", "https://ethereum-sepolia-rpc.publicnode.com", ); // Send using your preferred method (e.g., viem, ethers) // const txHash = await client.request({ // method: 'eth_sendRawTransaction', // params: [signedTransaction], // }); ``` | key | type | description | | :--------------------------------- | :------- | :----------------------------------------------------------------------- | | `eoaPrivateKey` | `string` | The EOA's private key (signs both the authorization and the transaction) | | `providerRpc` | `string` | JSON-RPC endpoint for nonce, gas price, and chain ID queries | | `overrides?` | `object` | Optional overrides for transaction fields | | `overrides?.nonce?` | `bigint` | Transaction nonce override | | `overrides?.authorizationNonce?` | `bigint` | EIP-7702 authorization nonce override | | `overrides?.maxFeePerGas?` | `bigint` | Maximum fee per gas (EIP-1559) | | `overrides?.maxPriorityFeePerGas?` | `bigint` | Maximum priority fee per gas (EIP-1559) | | `overrides?.gasLimit?` | `bigint` | Gas limit for the transaction | | `overrides?.chainId?` | `bigint` | Chain ID override | | key | type | description | | :------------------ | :------- | :------------------------------------------------------------ | | `signedTransaction` | `string` | Signed raw transaction hex, ready for eth\_sendRawTransaction | ## Error Handling & Common Issues[​](#error-handling--common-issues "Direct link to Error Handling & Common Issues") ### Common Errors[​](#common-errors "Direct link to Common Errors") * Authorization Errors * Gas Estimation Issues * Network Mismatches ``` // Missing eip7702Auth on the first UserOperation authorization const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc // Missing eip7702Auth causes delegation failure ); // Correct usage const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc, { eip7702Auth: { chainId: 11155111n }, // Required for EIP-7702 } ); ``` ``` // Insufficient gas prices const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc, { eip7702Auth: { chainId: 11155111n }, maxFeePerGas: 1000000000n, // Too low for current network conditions maxPriorityFeePerGas: 100000000n, // Too low for current network conditions } ); // Add multipliers to the node const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc, { eip7702Auth: { chainId: 11155111n }, // Use higher gas prices maxFeePerGasPercentageMultiplier: 120, // 20% higher than current network conditions maxPriorityFeePerGasPercentageMultiplier: 150, // 50% higher than current network conditions } ); ``` ``` // ChainId mismatch const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // Sepolia RPC "https://api.candide.dev/public/v3/11155111", // Sepolia bundler { eip7702Auth: { chainId: 1n }, // Mainnet chainId } ); // Matching network configuration const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // Sepolia RPC "https://api.candide.dev/public/v3/11155111", // Sepolia bundler { eip7702Auth: { chainId: 11155111n }, // Sepolia chainId } ); ``` ### Error Recovery[​](#error-recovery "Direct link to Error Recovery") ``` async function robustTransactionExecution() { try { const response = await smartAccount.sendUserOperation(userOperation, bundlerRpc); const receipt = await response.included(); if (!receipt.success) { console.error("Transaction failed:", receipt.logs); // Handle transaction failure } return receipt; } catch (error) { if (error.code === -32500) { // Transaction rejected by entrypoint simulation console.error("Simulation failed, check transaction data"); } else if (error.code === -32501) { // Paymaster rejection console.error("Paymaster rejected transaction"); } throw error; } } ``` --- # Simple 7702 Account (EntryPoint v0.9) The `Simple7702AccountV09` is a minimalist smart contract account for EIP-7702, targeting **EntryPoint v0.9** (`0x433709009B8330FDa32311DF1C2AFA402eD8D009`). It shares the same audited contract implementation and API surface as `Simple7702Account`, with type specialization for `UserOperationV9`. EntryPoint v0.9 maintains ABI compatibility with v0.8 while adding: * **Parallelizable Paymaster Signing**: New `paymasterSignature` field allows passing data to Paymasters after UserOperation signing. * **Block Number-Based Validity Ranges**: `validAfter` and `validUntil` can now specify block numbers instead of timestamps. * **Flexible InitCode Handling**: `initCode` is silently ignored if the Account already exists, enabling two-dimensional nonce usage. * **UserOp Hash Query**: New `getCurrentUserOpHash` function exposes the current UserOperation hash during execution. The following ERCs are supported: * ERC-165 * ERC-721 * ERC-1155 * ERC-1271 * ERC-4337 v0.9 Full Example A complete working example is available on GitHub: [01-upgrade-eoa.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/01-upgrade-eoa.ts) ## Smart Contracts and Audits[​](#smart-contracts-and-audits "Direct link to Smart Contracts and Audits") The contracts were developed by the Ethereum Foundation Account Abstraction Team and audited by Cantina. * [Smart Contracts](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/Simple7702Account.sol) * [Audit](https://github.com/eth-infinitism/account-abstraction/blob/develop/audits/ERC-4337%20Account%20Abstraction%20v0.9%20Security%20Review%20-%20Cantina.pdf) ## How to Use[​](#how-to-use "Direct link to How to Use") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using `Simple7702AccountV09`, you must have: * **Node.js**: Version 18.0 or higher. * **EIP-7702 Compatible Network**: Ethereum mainnet, Sepolia, or other EIP-7702 enabled chains with EP v0.9 support. * **Private Key Access**: Required for signing authorizations and user operations. ### Installation[​](#installation "Direct link to Installation") ``` npm install abstractionkit ``` ### Usage[​](#usage "Direct link to Usage") ``` import { Simple7702AccountV09 } from "abstractionkit"; const delegatorPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // EOA public key const smartAccount = new Simple7702AccountV09(delegatorPublicAddress); ``` **Constructor defaults:** * `entrypointAddress`: `0x433709009B8330FDa32311DF1C2AFA402eD8D009` (EntryPoint v0.9) * `delegateeAddress`: `0xa46cc63eBF4Bd77888AA327837d20b23A63a56B5` Both can be overridden by passing an `overrides` object as the second constructor argument. ## Essential Methods[​](#essential-methods "Direct link to Essential Methods") ### createUserOperation[​](#createuseroperation "Direct link to createUserOperation") Creates a UserOperation for EIP-7702 accounts that can be sent to bundlers for execution. * example.ts * Param Types * Return Type example.ts ``` import { Simple7702AccountV09 } from "abstractionkit"; const delegatorPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; const smartAccount = new Simple7702AccountV09(delegatorPublicAddress); const chainId = 11155111n; // Sepolia const transactions = [ { to: "0x...", value: 0n, data: "0x...", }, ]; const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // provider RPC "https://your-ep-v09-bundler-rpc", // bundler RPC (must support EP v0.9) { eip7702Auth: { chainId, // chainId at which the account will be authorized }, // Optional overrides maxFeePerGas: 20000000000n, maxPriorityFeePerGas: 2000000000n, } ); ``` | key | type | description | | :----------------------------------------------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactions` | `SimpleMetaTransaction[]` | Array of transactions to include in the user operation | | `providerRpc?` | `string` | Optional JSON-RPC provider URL for blockchain queries | | `bundlerRpc?` | `string` | Optional bundler RPC URL for gas estimation | | `overrides?` | `object` | Optional overrides for user operation creation | | `overrides?.eip7702Auth?` | `object` | EIP-7702 authorization parameters for EOA delegation | | `overrides?.eip7702Auth?.chainId` | `string` | Chain ID in hexadecimal format where the authorization is valid | | `overrides?.eip7702Auth?.address` | `string` | Address to authorize for the EOA delegation | | `overrides?.eip7702Auth?.nonce` | `string` | Authorization nonce in hexadecimal format | | `overrides?.eip7702Auth?.yParity` | `string` | Y parity of the authorization signature | | `overrides?.eip7702Auth?.r` | `string` | R component of the authorization signature | | `overrides?.eip7702Auth?.s` | `string` | S component of the authorization signature | | `overrides?.nonce?` | `string` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `overrides?.callData?` | `string` | The data to pass to the sender during the main execution call | | `overrides?.callGasLimit?` | `bigint` | The amount of gas to allocate the main execution call | | `overrides?.verificationGasLimit?` | `bigint` | The amount of gas to allocate for the verification step | | `overrides?.preVerificationGas?` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `overrides?.maxFeePerGas?` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `overrides?.maxPriorityFeePerGas?` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `overrides?.callGasLimitPercentageMultiplier?` | `number` | Set the callGasLimitPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.verificationGasLimitPercentageMultiplier?` | `number` | Set the verificationGasLimitPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.preVerificationGasPercentageMultiplier?` | `number` | Set the preVerificationGasPercentageMultiplier instead of estimating gas using the bundler | | `overrides?.maxFeePerGasPercentageMultiplier?` | `number` | Set the maxFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node | | `overrides?.maxPriorityFeePerGasPercentageMultiplier?` | `number` | Set the maxPriorityFeePerGasPercentageMultiplier instead of querying the current gas price from the RPC node | | `overrides?.state_override_set?` | `StateOverrideSet` | Pass state override set for simulation | | `overrides?.dummySignature?` | `string` | Dummy signature for gas estimation | | `overrides?.skipGasEstimation?` | `boolean` | When true, skip the bundler's eth\_estimateUserOperationGas call. Gas limits fall back to any overrides or 0n. The returned UserOperation is always populated with a dummy signature so it stays valid for downstream paymaster sponsorship calls. | SimpleMetaTransaction | key | type | description | | :---------------------------- | :------- | :---------------------------------------------------------------------------- | | `SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Simple7702Account. | | `SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `SimpleMetaTransaction.data` | `string` | The call data for the transaction | | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The constructed user operation for EIP-7702 | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | ### signUserOperation[​](#signuseroperation "Direct link to signUserOperation") Signs a UserOperation with the provided private key for the EIP-7702 account. * example.ts * Param Types * Return Type example.ts ``` const signature = smartAccount.signUserOperation( userOperation, "0x...private-key", 11155111n // chain ID ); userOperation.signature = signature; ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to sign | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `privateKey` | `string` | Private key to sign the user operation with | | `chainId` | `bigint` | Chain ID for the target blockchain | | key | type | description | | :---------- | :------- | :----------------------------------- | | `signature` | `string` | The signature for the user operation | ### signUserOperationWithSigner[​](#signuseroperationwithsigner "Direct link to signUserOperationWithSigner") Signs a UserOperation using an [`ExternalSigner`](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) instead of a raw private key. Integrates viem, ethers, browser wallets, hardware wallets, HSMs, and MPC services through the same API. Since AbstractionKit `v0.3.5`, `Simple7702AccountV09` accepts signers that implement either `signTypedData` or `signHash`. The SDK prefers `signTypedData` when available, so JSON-RPC wallets and viem `WalletClient` instances can sign EntryPoint v0.9 UserOperations without raw-hash signing support. Raw-hash signers continue to work. example.ts ``` import { fromViemWalletClient } from "abstractionkit"; import { createWalletClient, custom } from "viem"; import { sepolia } from "viem/chains"; const chain = sepolia; const walletClient = createWalletClient({ account: delegatorPublicAddress, chain, transport: custom(window.ethereum), }); userOperation.signature = await smartAccount.signUserOperationWithSigner( userOperation, fromViemWalletClient(walletClient), 11155111n, // chain ID ); ``` See [External Signers](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the full list of adapters and custom-signer integrations. EntryPoint v0.9 also supports parallel paymaster signing via the two-phase `signingPhase` context — see the [parallel signing example](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/03-parallel-signing.ts). ### getUserOperationEip712Data[​](#getuseroperationeip712data "Direct link to getUserOperationEip712Data") Builds the EIP-712 typed data payload for a UserOperation under the EntryPoint v0.9 domain. Use this static helper when you need to inspect the wallet prompt payload or drive a custom `signTypedData` primitive directly. Full example: [`06-typed-data-builder.ts`](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/06-typed-data-builder.ts). example.ts ``` import { Simple7702AccountV09 } from "abstractionkit"; const typedData = Simple7702AccountV09.getUserOperationEip712Data( userOperation, 11155111n, ); const signature = await walletClient.signTypedData(typedData); userOperation.signature = signature; ``` `getUserOperationEip712TypedData` was renamed in `v0.3.8` and moved from an instance method to a static helper. Use `getUserOperationEip712Hash(userOperation, chainId, overrides?)` when you only need the digest. ### sendUserOperation[​](#senduseroperation "Direct link to sendUserOperation") Sends a signed UserOperation to the bundler for execution on-chain. * example.ts * Param Types * Return Type example.ts ``` const response = await smartAccount.sendUserOperation( userOperation, "https://your-ep-v09-bundler-rpc" // bundler URL (must support EP v0.9) ); console.log("UserOperation hash:", response.userOperationHash); // Wait for the transaction to be included const receipt = await response.included(); console.log("Transaction receipt:", receipt); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The signed user operation to send | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `bundlerRpc` | `string` | Bundler RPC URL to send the user operation to | | key | type | description | | :--------------------------- | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `response` | `object` | Response containing user operation hash and bundler details | | `response.userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `response.bundler` | `Bundler` | The Bundler class | | `response.entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `response.included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | SendUseroperationResponse | key | type | description | | :------------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `userOperationHash` | `string` | The hash over the userOp (except signature), entryPoint and chainId | | `bundler` | `Bundler` | The Bundler class | | `entrypointAddress` | `string` | The entrypoint address where the useroperation got executed | | `included()` | `Promise` | Waits for the user operation to be included onchain and returns the user operation receipt on success, or the bundler error on failture | BundlerJsonRpcError | key | type | description | | :-------- | :------- | :------------------------------------ | | `code` | `number` | Bundler RPC error code | | `message` | `string` | Bundler RPC error message description | UserOperationReceiptResult | key | type | description | | :-------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------- | | `userOpHash` | `string` | The hash of the user operation. | | `entryPoint` | `string` | The address of the entry point contract that processed the operation. | | `sender` | `string` | The address of the sender of the user operation. | | `nonce` | `bigint` | The nonce of the user operation. | | `paymaster` | `string` | The address of the paymaster that paid for the gas of the user operation. | | `actualGasCost` | `bigint` | The actual gas cost incurred for executing the user operation. | | `actualGasUsed` | `bigint` | The actual amount of gas used for the user operation. | | `success` | `boolean` | Indicates whether the user operation was successful. | | `logs` | `string` | The logs produced during the execution of the user operation. | | `receipt` | `object` | The detailed receipt of the user operation. | | `receipt.blockHash` | `string` | The hash of the block in which the transaction was included. | | `receipt.blockNumber` | `bigint` | The number of the block in which the transaction was included. | | `receipt.from` | `string` | The address that initiated the transaction. | | `receipt.cumulativeGasUsed` | `bigint` | The total amount of gas used in the block up to and including this transaction. | | `receipt.gasUsed` | `bigint` | The amount of gas used by this transaction. | | `receipt.logs` | `string` | Logs generated by the transaction. | | `receipt.logsBloom` | `string` | The bloom filter for the logs generated by the transaction. | | `receipt.transactionHash` | `string` | The unique hash of the transaction. | | `receipt.transactionIndex` | `bigint` | The index of the transaction within the block. | | `receipt.effectiveGasPrice` | `bigint` | The effective gas price for the transaction. This field is optional and may not be present in all receipts. | ### prependTokenPaymasterApproveToCallDataStatic[​](#prependtokenpaymasterapprovetocalldatastatic "Direct link to prependTokenPaymasterApproveToCallDataStatic") Prepends a token approval transaction to existing call data for use with token paymasters. * example.ts * Param Types * Return Type example.ts ``` const callDataWithApproval = Simple7702AccountV09.prependTokenPaymasterApproveToCallDataStatic( "0x...", // existing call data "0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address "0x...", // paymaster address 1000000n // approve amount (1 USDC) ); ``` | key | type | description | | :----------------- | :------- | :-------------------------------------------- | | `callData` | `string` | Existing call data to prepend the approval to | | `tokenAddress` | `string` | Address of the ERC-20 token to approve | | `paymasterAddress` | `string` | Address of the paymaster contract | | `approveAmount` | `bigint` | Amount of tokens to approve for the paymaster | | key | type | description | | :--------- | :------- | :-------------------------------------- | | `callData` | `string` | Call data with token approval prepended | ### prependTokenPaymasterApproveToCallData[​](#prependtokenpaymasterapprovetocalldata "Direct link to prependTokenPaymasterApproveToCallData") Instance method to prepend token approval to call data for paymaster usage. * example.ts * Param Types * Return Type example.ts ``` const callDataWithApproval = smartAccount.prependTokenPaymasterApproveToCallData( "0x...", // existing call data "0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address "0x...", // paymaster address 1000000n // approve amount ); ``` | key | type | description | | :----------------- | :------- | :-------------------------------------------- | | `callData` | `string` | Existing call data to prepend the approval to | | `tokenAddress` | `string` | Address of the ERC-20 token to approve | | `paymasterAddress` | `string` | Address of the paymaster contract | | `approveAmount` | `bigint` | Amount of tokens to approve for the paymaster | | key | type | description | | :--------- | :------- | :-------------------------------------- | | `callData` | `string` | Call data with token approval prepended | ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") ### createAccountCallData[​](#createaccountcalldata "Direct link to createAccountCallData") Creates call data for a basic transaction with specified target, value, and data. * example.ts * Param Types * Return Type example.ts ``` const callData = Simple7702AccountV09.createAccountCallData( "0x...", // to address 1000000000000000000n, // value in wei "0x..." // transaction data ); ``` | key | type | description | | :------ | :------- | :----------------------------------- | | `to` | `string` | Target address for the transaction | | `value` | `bigint` | Value to transfer in the transaction | | `data` | `string` | Call data for the transaction | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### createAccountCallDataSingleTransaction[​](#createaccountcalldatasingletransaction "Direct link to createAccountCallDataSingleTransaction") Creates call data for a single SimpleMetaTransaction. * example.ts * Param Types * Return Type example.ts ``` const metaTransaction = { to: "0x...", value: 0n, data: "0x...", }; const callData = Simple7702AccountV09.createAccountCallDataSingleTransaction(metaTransaction); ``` | key | type | description | | :-------------------------------------------- | :------- | :---------------------------------------------------------------------------- | | `metaTransaction` | `object` | The SimpleMetaTransaction to create call data for | | `metaTransaction.SimpleMetaTransaction` | `object` | SimpleMetaTransaction is the type of transaction used with Simple7702Account. | | `metaTransaction.SimpleMetaTransaction.to` | `string` | Target contract address for the transaction | | `metaTransaction.SimpleMetaTransaction.value` | `bigint` | Value transferred in the transaction (usually 0n for contract interactions) | | `metaTransaction.SimpleMetaTransaction.data` | `string` | The call data for the transaction | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### createAccountCallDataBatchTransactions[​](#createaccountcalldatabatchtransactions "Direct link to createAccountCallDataBatchTransactions") Creates call data for batching multiple SimpleMetaTransactions together. * example.ts * Param Types * Return Type example.ts ``` const transactions = [ { to: "0x...", value: 0n, data: "0x..." }, { to: "0x...", value: 0n, data: "0x..." }, ]; const callData = Simple7702AccountV09.createAccountCallDataBatchTransactions(transactions); ``` | key | type | description | | :------------- | :------------------------ | :------------------------------------------------ | | `transactions` | `SimpleMetaTransaction[]` | Array of SimpleMetaTransactions to batch together | | key | type | description | | :--------- | :------- | :-------------------------------------------- | | `callData` | `string` | Encoded call data for the account transaction | ### estimateUserOperationGas[​](#estimateuseroperationgas "Direct link to estimateUserOperationGas") Estimates gas limits for a UserOperation using the bundler. * example.ts * Param Types * Return Type example.ts ``` const [preVerificationGas, verificationGasLimit, callGasLimit] = await smartAccount.estimateUserOperationGas( userOperation, "https://your-ep-v09-bundler-rpc", { // Optional overrides stateOverrideSet: {...}, dummySignature: "0x...", } ); ``` | key | type | description | | :-------------------------------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `userOperation` | `object` | The user operation to estimate gas for | | `userOperation.sender` | `string` | The account making the operation | | `userOperation.nonce` | `bigint` | Anti-replay parameter (see Semi-abstracted Nonce Support) | | `userOperation.factory` | `string \| null` | Account factory address, only for new accounts (null if account already exists) | | `userOperation.factoryData` | `string \| null` | Data for account factory (null if account already exists) | | `userOperation.callData` | `string` | The data to pass to the sender during the main execution call | | `userOperation.callGasLimit` | `bigint` | The amount of gas to allocate the main execution call | | `userOperation.verificationGasLimit` | `bigint` | The amount of gas to allocate for the verification step | | `userOperation.preVerificationGas` | `bigint` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `userOperation.maxFeePerGas` | `bigint` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `userOperation.maxPriorityFeePerGas` | `bigint` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `userOperation.paymaster` | `string \| null` | Address of paymaster contract (null if account pays for itself) | | `userOperation.paymasterVerificationGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster verification step (null if no paymaster) | | `userOperation.paymasterPostOpGasLimit` | `bigint \| null` | The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) | | `userOperation.paymasterData` | `string \| null` | Data for paymaster (null if no paymaster) | | `userOperation.eip7702Auth` | `Authorization7702Hex \| null` | EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) | | `userOperation.signature` | `string` | Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | `bundlerRpc` | `string` | Bundler RPC URL for gas estimation | | `overrides?` | `object` | Optional overrides for gas estimation | | `overrides?.state_override_set?` | `StateOverrideSet` | State override set for simulation | | `overrides?.dummySignature?` | `string` | Dummy signature for gas estimation | | key | type | description | | :---------- | :------------------------- | :----------------------------------------------------------------- | | `gasLimits` | `[bigint, bigint, bigint]` | Tuple of \[preVerificationGas, verificationGasLimit, callGasLimit] | ## Delegation Methods[​](#delegation-methods "Direct link to Delegation Methods") ### isDelegatedToThisAccount[​](#isdelegatedtothisaccount "Direct link to isDelegatedToThisAccount") Checks if the EOA is currently delegated to the expected smart account address via EIP-7702. Returns `true` only when delegated to the account's `delegateeAddress`. * example.ts * Param Types * Return Type example.ts ``` const isDelegated = await smartAccount.isDelegatedToThisAccount( "https://ethereum-sepolia-rpc.publicnode.com" ); if (isDelegated) { console.log("EOA is delegated to this smart account"); } else { console.log("EOA is not delegated"); } ``` | key | type | description | | :------------ | :------- | :------------------------- | | `providerRpc` | `string` | Ethereum JSON-RPC node URL | | key | type | description | | :------------ | :-------- | :-------------------------------------------------------------------- | | `isDelegated` | `boolean` | true if the EOA is delegated to the expected address, false otherwise | ### createRevokeDelegationTransaction[​](#createrevokedelegationtransaction "Direct link to createRevokeDelegationTransaction") Creates a signed EIP-7702 transaction that revokes the delegation, restoring the EOA to a regular account. The transaction delegates to `address(0)`, removing the smart account code from the EOA. This is a regular Ethereum transaction (type `0x04`), not a UserOperation. The EOA needs native tokens to pay for gas. note Revocation cannot be done via a UserOperation because the authorization list is processed before execution, which would remove the account's code mid-transaction. * example.ts * Param Types * Return Type example.ts ``` const signedTransaction = await smartAccount.createRevokeDelegationTransaction( "0x...private-key", "https://ethereum-sepolia-rpc.publicnode.com", ); // Send using your preferred method (e.g., viem, ethers) // const txHash = await client.request({ // method: 'eth_sendRawTransaction', // params: [signedTransaction], // }); ``` | key | type | description | | :--------------------------------- | :------- | :----------------------------------------------------------------------- | | `eoaPrivateKey` | `string` | The EOA's private key (signs both the authorization and the transaction) | | `providerRpc` | `string` | JSON-RPC endpoint for nonce, gas price, and chain ID queries | | `overrides?` | `object` | Optional overrides for transaction fields | | `overrides?.nonce?` | `bigint` | Transaction nonce override | | `overrides?.authorizationNonce?` | `bigint` | EIP-7702 authorization nonce override | | `overrides?.maxFeePerGas?` | `bigint` | Maximum fee per gas (EIP-1559) | | `overrides?.maxPriorityFeePerGas?` | `bigint` | Maximum priority fee per gas (EIP-1559) | | `overrides?.gasLimit?` | `bigint` | Gas limit for the transaction | | `overrides?.chainId?` | `bigint` | Chain ID override | | key | type | description | | :------------------ | :------- | :------------------------------------------------------------ | | `signedTransaction` | `string` | Signed raw transaction hex, ready for eth\_sendRawTransaction | ## Error Handling & Common Issues[​](#error-handling--common-issues "Direct link to Error Handling & Common Issues") For runnable examples that classify and recover from the most common UserOperation failures (underfunded paymaster, insufficient token balance, sponsor denial, on-chain reverts, and gas-too-low retries), see the [error-handling examples](https://github.com/candidelabs/abstractionkit-examples/tree/main/error-handling). * Authorization Errors * Gas Estimation Issues * Network Mismatches ``` // Missing eip7702Auth on the first UserOperation authorization const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc, { eip7702Auth: { chainId: 11155111n }, // Required for EIP-7702 } ); ``` ``` // Use multipliers if gas prices are volatile const userOperation = await smartAccount.createUserOperation( transactions, providerRpc, bundlerRpc, { eip7702Auth: { chainId: 11155111n }, maxFeePerGasPercentageMultiplier: 120, // 20% above current network conditions maxPriorityFeePerGasPercentageMultiplier: 150, // 50% above current network conditions } ); ``` ``` // Ensure chainId, RPC, and bundler all target the same network const userOperation = await smartAccount.createUserOperation( transactions, "https://ethereum-sepolia-rpc.publicnode.com", // Sepolia RPC "https://your-ep-v09-bundler-rpc", // Sepolia EP v0.9 bundler { eip7702Auth: { chainId: 11155111n }, // Sepolia chainId } ); ``` --- # Simple 7702 Account The `Simple7702Account` is a fully audited minimalist smart contract account that can be safely authorized by any EOA. It adds full support for major smart account features like batching and gas sponsorship. * `Simple7702AccountV09` in AbstractionKit supports EntryPoint v0.9 and is recommended for new integrations. * `Simple7702Account` in AbstractionKit supports EntryPoint v0.8 for legacy integrations and migration flows. The following ERCs are supported: * ERC-165 * ERC-721 * ERC-1155 * ERC-1271 * ERC-4337 ## When to Use Simple 7702 Account[​](#when-to-use-simple-7702-account "Direct link to When to Use Simple 7702 Account") * You need a straightforward EOA upgrade with minimal overhead * Single signer (EOA private key) is sufficient * You don't need key management, passkey support, or per-key hooks * Lightweight: fewer methods, simpler API surface * Audited by Spearbit (v0.8) and Cantina (v0.9) For multi-key support, passkeys, or per-key hooks, see [Calibur Account](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md). ## Import[​](#import "Direct link to Import") ### EntryPoint v0.9 recommended[​](#entrypoint-v09-recommended "Direct link to EntryPoint v0.9 recommended") ``` import { Simple7702AccountV09 } from "abstractionkit"; ``` ### EntryPoint v0.8 legacy[​](#entrypoint-v08-legacy "Direct link to EntryPoint v0.8 legacy") ``` import { Simple7702Account } from "abstractionkit"; ``` ## How to Use[​](#how-to-use "Direct link to How to Use") AbstractionKit classes are designed to support the same methods across both versions, ensuring compatibility within the essential classes. This allows developers to upgrade seamlessly without breaking functionality or requiring significant code changes. ``` const delegatorPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // EOA public key const smartAccount = new Simple7702AccountV09(delegatorPublicAddress); ``` ### [Simple 7702 Account for EP v0.8](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v08.md) [Reference functions for Simple 7702 Account with EntryPoint v0.8. Supports gas sponsorship and ERC-20 gas payments.](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v08.md) [Simple 7702 Account for EP v0.9](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v09.md) [Reference functions for Simple 7702 Account with EntryPoint v0.9. Supports gas sponsorship and ERC-20 gas payments.](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v09.md) [EIP-7702 QuickStart](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md) [Learn how to upgrade an EOA to a Smart Account with batch transactions and gas sponsorship capabilities](https://docs.candide.dev/wallet/guides/getting-started-eip-7702.md) --- # Utilities A set of utilities for ERC-4337 Account Abstraction to help you with your development. ## UserOperation utils[​](#useroperation-utils "Direct link to UserOperation utils") ### createUserOperationHash[​](#createuseroperationhash "Direct link to createUserOperationHash") Computes the hash of a UserOperation, which is used as the unique identifier for the operation on-chain. * example.ts * Param Types * Return Type example.ts ``` import { createUserOperationHash } from "abstractionkit"; const userOpHash = createUserOperationHash( userOperation, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", 11155111n ); ``` | key | type | description | | :------------------ | :------------------------------------------------------------------------- | :------------------------------------- | | `useroperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | The UserOperation to hash | | `entrypointAddress` | `string` | The address of the EntryPoint contract | | `chainId` | `bigint` | The chain ID of the target network | | key | type | description | | :------------------ | :------- | :--------------------------------------------- | | `userOperationHash` | `string` | The keccak256 hash of the packed UserOperation | #### Source code[​](#source-code "Direct link to Source code") [createUserOperationHash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L49) ### createPackedUserOperationV6[​](#createpackeduseroperationv6 "Direct link to createPackedUserOperationV6") Encodes a UserOperationV6 into its packed ABI representation, suitable for hashing or on-chain verification. * example.ts * Param Types * Return Type example.ts ``` import { createPackedUserOperationV6 } from "abstractionkit"; const packed = createPackedUserOperationV6(userOperationV6); ``` | key | type | description | | :-------------- | :---------------- | :--------------------------------------------------- | | `useroperation` | `UserOperationV6` | A UserOperation following the EntryPoint v0.6 format | | key | type | description | | :-------------------- | :------- | :----------------------------------------------------- | | `packedUserOperation` | `string` | ABI-encoded packed representation of the UserOperation | #### Source code[​](#source-code-1 "Direct link to Source code") [createPackedUserOperationV6](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L310) ### createPackedUserOperationV7[​](#createpackeduseroperationv7 "Direct link to createPackedUserOperationV7") Encodes a UserOperationV7 into its packed ABI representation, suitable for hashing or on-chain verification. * example.ts * Param Types * Return Type example.ts ``` import { createPackedUserOperationV7 } from "abstractionkit"; const packed = createPackedUserOperationV7(userOperationV7); ``` | key | type | description | | :-------------- | :---------------- | :--------------------------------------------------- | | `useroperation` | `UserOperationV7` | A UserOperation following the EntryPoint v0.7 format | | key | type | description | | :-------------------- | :------- | :----------------------------------------------------- | | `packedUserOperation` | `string` | ABI-encoded packed representation of the UserOperation | #### Source code[​](#source-code-2 "Direct link to Source code") [createPackedUserOperationV7](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L349) ### fetchAccountNonce[​](#fetchaccountnonce "Direct link to fetchAccountNonce") Fetches the current nonce for a smart account from the EntryPoint. Accepts an optional `key` parameter (default 0) that enables parallel nonce channels, allowing multiple independent UserOperations to be submitted concurrently. * example.ts * Param Types * Return Type example.ts ``` import { fetchAccountNonce } from "abstractionkit"; const nonce = await fetchAccountNonce( "https://ethereum-sepolia-rpc.publicnode.com", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", "0xYourSmartAccountAddress" ); ``` | key | type | description | | :----------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | `rpcUrl` | `string` | The JSON-RPC URL of an Ethereum node | | `entryPoint` | `string` | The address of the EntryPoint contract | | `account` | `string` | The smart account address to query the nonce for | | `key` | `number` | Nonce key for parallel nonce channels (default: 0). Using different keys allows multiple independent UserOperations to be submitted concurrently. | | key | type | description | | :------ | :---------------- | :------------------------------------------------- | | `nonce` | `Promise` | The current nonce of the account in the EntryPoint | #### Source code[​](#source-code-3 "Direct link to Source code") [fetchAccountNonce](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L577) ### calculateUserOperationMaxGasCost[​](#calculateuseroperationmaxgascost "Direct link to calculateUserOperationMaxGasCost") Calculates the maximum possible gas cost (in wei) for a UserOperation based on its gas limits and fee parameters. * example.ts * Param Types * Return Type example.ts ``` import { calculateUserOperationMaxGasCost } from "abstractionkit"; const maxCost = calculateUserOperationMaxGasCost(userOperation); ``` | key | type | description | | :-------------- | :----------------------------------- | :------------------------------------------------------ | | `useroperation` | `UserOperationV6 \| UserOperationV7` | The UserOperation to calculate the maximum gas cost for | | key | type | description | | :----------- | :------- | :--------------------------------------------------------------- | | `maxGasCost` | `bigint` | The maximum gas cost in wei that the UserOperation could consume | #### Source code[​](#source-code-4 "Direct link to Source code") [calculateUserOperationMaxGasCost](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L636) ### JsonRpcNode[​](#jsonrpcnode "Direct link to JsonRpcNode") `JsonRpcNode` wraps common Ethereum node calls behind the same `Transport` abstraction used by Bundler and Paymaster services. Use it for node reads instead of the removed top-level URL helpers `fetchGasPrice`, `getBalanceOf`, `getDepositInfo`, and `getDelegatedAddress`. * example.ts * Transport example.ts ``` import { GasOption, JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const [maxFeePerGas, maxPriorityFeePerGas] = await node.getFeeData(GasOption.Medium); const deposit = await node.getEntryPointDeposit( "0xYourAccountAddress", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", ); const depositInfo = await node.getEntryPointDepositInfo( "0xYourAccountAddress", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", ); const delegatee = await node.getDelegatedAddress("0xYourEOAAddress"); ``` transport.ts ``` import { JsonRpcNode, type Transport } from "abstractionkit"; const transport: Transport = { request: async ({ method, params }, options) => { return myRpcClient.request({ method, params }, { signal: options?.signal }); }, }; const node = new JsonRpcNode(transport); const code = await node.getCode("0xYourAccountAddress"); ``` #### Methods[​](#methods "Direct link to Methods") * `chainId()` * `blockNumber()` * `getCode(address)` * `call(transaction)` * `getTransactionCount(address)` * `getFeeData(gasOption?)` * `getDelegatedAddress(eoaAddress)` * `getEntryPointNonce(entryPoint, account, key?)` * `getEntryPointDeposit(address, entryPoint)` * `getEntryPointDepositInfo(address, entryPoint)` * `request(args, options?)` #### Source code[​](#source-code-5 "Direct link to Source code") [JsonRpcNode](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/transport/JsonRpcNode.ts#L50) ### getBalanceOf[​](#getbalanceof "Direct link to getBalanceOf") Deprecated in `v0.3.8`. Use `JsonRpcNode#getEntryPointDeposit` instead. migration.ts ``` import { JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const balance = await node.getEntryPointDeposit( "0xYourAccountAddress", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", ); ``` ### getDepositInfo[​](#getdepositinfo "Direct link to getDepositInfo") Deprecated in `v0.3.8`. Use `JsonRpcNode#getEntryPointDepositInfo` instead. migration.ts ``` import { JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const info = await node.getEntryPointDepositInfo( "0xYourAccountAddress", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", ); ``` ### getStorageAt[​](#getstorageat "Direct link to getStorageAt") Reads the 32-byte storage word at a given slot via `eth_getStorageAt`. * example.ts * Param Types * Return Types example.ts ``` import { JsonRpcNode, SAFE_FALLBACK_HANDLER_STORAGE_SLOT } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const word = await node.getStorageAt( "0xYourAccountAddress", SAFE_FALLBACK_HANDLER_STORAGE_SLOT, ); ``` | key | type | description | | :---------- | :----------------- | :---------------------------------------------------- | | `address` | `string` | The contract address to read storage from. | | `slot` | `string` | The 32-byte storage slot, as a hex string. | | `blockTag?` | `string \| bigint` | Block tag or number to read at. Defaults to "latest". | | key | type | description | | :----- | :---------------- | :----------------------------------------------------------------------- | | `word` | `Promise` | Resolves to the 32-byte storage word at the given slot, as a hex string. | #### Source code[​](#source-code-6 "Direct link to Source code") [getStorageAt](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/transport/JsonRpcNode.ts#L162) ## Simulation utils[​](#simulation-utils "Direct link to Simulation utils") ### simulateUserOperationWithTenderlyAndCreateShareLink[​](#simulateuseroperationwithtenderlyandcreatesharelink "Direct link to simulateUserOperationWithTenderlyAndCreateShareLink") Simulates a UserOperation via the EntryPoint's `handleOps` on Tenderly and returns a shareable dashboard link. * example.ts * Param Types * Return Type example.ts ``` import { simulateUserOperationWithTenderlyAndCreateShareLink } from "abstractionkit"; const { simulation, simulationShareLink } = await simulateUserOperationWithTenderlyAndCreateShareLink( "my-account", "my-project", "my-access-key", 11155111n, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", userOperation ); ``` | key | type | description | | :-------------------- | :------------------------------------------------------------------------- | :---------------------------------------------------------- | | `tenderlyAccountSlug` | `string` | Your Tenderly account slug | | `tenderlyProjectSlug` | `string` | Your Tenderly project slug | | `tenderlyAccessKey` | `string` | Your Tenderly API access key | | `chainId` | `bigint` | The chain ID of the target network | | `entrypointAddress` | `string` | The address of the EntryPoint contract | | `userOperation` | `UserOperationV6 \| UserOperationV7 \| UserOperationV8 \| UserOperationV9` | The UserOperation to simulate | | `blockNumber` | `number \| null` | Block number to simulate against (default: null for latest) | | `stateOverrides?` | `OverrideType \| null` | Optional state overrides applied during simulation | | key | type | description | | :-------------------- | :------------------------------------------ | :----------------------------------------------------- | | `simulation` | `SingleTransactionTenderlySimulationResult` | The simulation result from Tenderly | | `simulationShareLink` | `string` | A shareable Tenderly dashboard link for the simulation | #### Source code[​](#source-code-7 "Direct link to Source code") [simulateUserOperationWithTenderlyAndCreateShareLink](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utilsTenderly.ts#L82) ### simulateUserOperationCallDataWithTenderlyAndCreateShareLink[​](#simulateuseroperationcalldatawithtenderlyandcreatesharelink "Direct link to simulateUserOperationCallDataWithTenderlyAndCreateShareLink") Simulates UserOperation callData with Tenderly and returns shareable links. Unlike `simulateUserOperationWithTenderlyAndCreateShareLink`, this simulates the inner call data rather than the full EntryPoint `handleOps` flow. * example.ts * Param Types * Return Type example.ts ``` import { simulateUserOperationCallDataWithTenderlyAndCreateShareLink } from "abstractionkit"; const { simulation, callDataSimulationShareLink } = await simulateUserOperationCallDataWithTenderlyAndCreateShareLink( "my-account", "my-project", "my-access-key", 11155111n, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", userOperation ); ``` | key | type | description | | :-------------------- | :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | `tenderlyAccountSlug` | `string` | Your Tenderly account slug | | `tenderlyProjectSlug` | `string` | Your Tenderly project slug | | `tenderlyAccessKey` | `string` | Your Tenderly API access key | | `chainId` | `bigint` | The chain ID of the target network | | `entrypointAddress` | `string` | The address of the EntryPoint contract | | `userOperation` | `UserOperationV6ToSimulate \| UserOperationV7ToSimulate \| UserOperationV8ToSimulate \| UserOperationV9ToSimulate` | A partial UserOperation with at least sender, nonce, and callData populated | | `blockNumber` | `number \| null` | Block number to simulate against (default: null for latest) | | `stateOverrides?` | `OverrideType \| null` | Optional state overrides applied during simulation | | key | type | description | | :-------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------- | | `simulation` | `TenderlySimulationResult` | The simulation result from Tenderly | | `callDataSimulationShareLink` | `string` | A shareable Tenderly dashboard link for the callData simulation | | `accountDeploymentSimulationShareLink?` | `string` | A shareable link for the account deployment simulation, if a factory was provided | #### Source code[​](#source-code-8 "Direct link to Source code") [simulateUserOperationCallDataWithTenderlyAndCreateShareLink](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utilsTenderly.ts#L377) ### simulateSenderCallDataWithTenderlyAndCreateShareLink[​](#simulatesendercalldatawithtenderlyandcreatesharelink "Direct link to simulateSenderCallDataWithTenderlyAndCreateShareLink") Simulates sender callData directly with Tenderly and returns shareable links. Useful for simulating the execution of callData from a specific sender address without wrapping it in a full UserOperation. * example.ts * Param Types * Return Type example.ts ``` import { simulateSenderCallDataWithTenderlyAndCreateShareLink } from "abstractionkit"; const { simulation, callDataSimulationShareLink } = await simulateSenderCallDataWithTenderlyAndCreateShareLink( "my-account", "my-project", "my-access-key", 11155111n, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", "0xSenderAddress", "0xCallData" ); ``` | key | type | description | | :-------------------- | :--------------------- | :-------------------------------------------------------------- | | `tenderlyAccountSlug` | `string` | Your Tenderly account slug | | `tenderlyProjectSlug` | `string` | Your Tenderly project slug | | `tenderlyAccessKey` | `string` | Your Tenderly API access key | | `chainId` | `bigint` | The chain ID of the target network | | `entrypointAddress` | `string` | The address of the EntryPoint contract | | `sender` | `string` | The sender address to simulate the call from | | `callData` | `string` | The call data to simulate | | `factory` | `string \| null` | Factory address if the account needs deployment (default: null) | | `factoryData` | `string \| null` | Factory init data for account deployment (default: null) | | `blockNumber` | `number \| null` | Block number to simulate against (default: null for latest) | | `stateOverrides?` | `OverrideType \| null` | Optional state overrides applied during simulation | | key | type | description | | :-------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------- | | `simulation` | `TenderlySimulationResult` | The simulation result from Tenderly | | `callDataSimulationShareLink` | `string` | A shareable Tenderly dashboard link for the callData simulation | | `accountDeploymentSimulationShareLink?` | `string` | A shareable link for the account deployment simulation, if a factory was provided | #### Source code[​](#source-code-9 "Direct link to Source code") [simulateSenderCallDataWithTenderlyAndCreateShareLink](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utilsTenderly.ts#L597) ## Multicall utils[​](#multicall-utils "Direct link to Multicall utils") ### encodeMultiSendCallData[​](#encodemultisendcalldata "Direct link to encodeMultiSendCallData") Encodes an array of MetaTransactions into a single callData payload for the MultiSend contract. * example.ts * Param Types * Return Type example.ts ``` import { encodeMultiSendCallData } from "abstractionkit"; const callData = encodeMultiSendCallData([ { to: "0xTokenAddress", value: 0n, data: "0xTransferCallData", operation: 0, }, { to: "0xAnotherContract", value: 0n, data: "0xAnotherCallData", operation: 0, }, ]); ``` | key | type | description | | :----------------- | :------------------ | :--------------------------------------------------------------- | | `metaTransactions` | `MetaTransaction[]` | Array of MetaTransactions to encode into a single MultiSend call | MetaTransaction | key | type | description | | :---------- | :---------- | :--------------------------------------------- | | `to` | `string` | Target contract address | | `value` | `bigint` | Value to send with the transaction (in wei) | | `data` | `string` | Call data for the transaction | | `operation` | `Operation` | Operation type: 0 for Call, 1 for DelegateCall | | key | type | description | | :--------- | :------- | :---------------------------------------------- | | `callData` | `string` | ABI-encoded callData for the MultiSend contract | #### Source code[​](#source-code-10 "Direct link to Source code") [encodeMultiSendCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/multisend.ts#L26) ### decodeMultiSendCallData[​](#decodemultisendcalldata "Direct link to decodeMultiSendCallData") Decodes encoded MultiSend callData back into a readable representation. * example.ts * Param Types * Return Type example.ts ``` import { decodeMultiSendCallData } from "abstractionkit"; const decoded = decodeMultiSendCallData(encodedCallData); ``` | key | type | description | | :--------- | :------- | :--------------------------------------- | | `callData` | `string` | The encoded MultiSend callData to decode | | key | type | description | | :-------- | :------- | :--------------------------------------------------- | | `decoded` | `string` | Decoded representation of the MultiSend transactions | #### Source code[​](#source-code-11 "Direct link to Source code") [decodeMultiSendCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/multisend.ts#L36) ## Generic Ethereum utils[​](#generic-ethereum-utils "Direct link to Generic Ethereum utils") ### fetchGasPrice[​](#fetchgasprice "Direct link to fetchGasPrice") Deprecated in `v0.3.8`. Use `JsonRpcNode#getFeeData` instead. migration.ts ``` import { GasOption, JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const [maxFeePerGas, maxPriorityFeePerGas] = await node.getFeeData(GasOption.Medium); ``` ### createCallData[​](#createcalldata "Direct link to createCallData") Encodes a function call into ABI-encoded call data by combining a function selector with its encoded parameters. * example.ts * Param Types * Return Type example.ts ``` import { createCallData } from "abstractionkit"; const callData = createCallData( "0xa9059cbb", // transfer(address,uint256) selector ["address", "uint256"], ["0xRecipientAddress", 1000000n] ); ``` | key | type | description | | :------------------------ | :---------------- | :----------------------------------------------------------------------------------- | | `functionSelector` | `string` | The 4-byte function selector (e.g. from getFunctionSelector) | | `functionInputAbi` | `string[]` | Array of ABI type strings for the function parameters (e.g. \["address", "uint256"]) | | `functionInputParameters` | `AbiInputValue[]` | Array of values matching the ABI types | | key | type | description | | :--------- | :------- | :---------------------------------------------------------------------- | | `callData` | `string` | The ABI-encoded call data combining the selector and encoded parameters | #### Source code[​](#source-code-12 "Direct link to Source code") [createCallData](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L466) ### getFunctionSelector[​](#getfunctionselector "Direct link to getFunctionSelector") Computes the 4-byte function selector from a Solidity function signature string. * example.ts * Param Types * Return Type example.ts ``` import { getFunctionSelector } from "abstractionkit"; const selector = getFunctionSelector("transfer(address,uint256)"); // Returns "0xa9059cbb" ``` | key | type | description | | :------------------ | :------- | :--------------------------------------------------------------- | | `functionSignature` | `string` | The function signature string (e.g. "transfer(address,uint256)") | | key | type | description | | :--------- | :------- | :----------------------------------------------------------------------------------- | | `selector` | `string` | The first 4 bytes (8 hex characters) of the keccak256 hash of the function signature | #### Source code[​](#source-code-13 "Direct link to Source code") [getFunctionSelector](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L558) ### sendJsonRpcRequest[​](#sendjsonrpcrequest "Direct link to sendJsonRpcRequest") Sends a JSON-RPC request to the given URL. Accepts optional `headers` (defaults to `{"Content-Type": "application/json"}`) and an optional `paramsKeyName` for non-standard JSON-RPC endpoints. * example.ts * Param Types * Return Type example.ts ``` import { sendJsonRpcRequest } from "abstractionkit"; const result = await sendJsonRpcRequest( "https://ethereum-sepolia-rpc.publicnode.com", "eth_blockNumber", [] ); ``` | key | type | description | | :-------------- | :----------------------- | :------------------------------------------------------------------------------------------------------------------ | | `rpcUrl` | `string` | The JSON-RPC endpoint URL | | `method` | `string` | The JSON-RPC method name | | `params` | `JsonRpcParam` | The parameters to pass to the JSON-RPC method | | `headers` | `Record` | HTTP headers for the request (default: {"Content-Type": "application/json"}) | | `paramsKeyName` | `string` | Key name for the params field in the JSON-RPC body (default: "params"). Useful for non-standard JSON-RPC endpoints. | | key | type | description | | :------- | :----------------------- | :----------------------------------------------------- | | `result` | `Promise` | The JSON-RPC result, or an error if the request failed | #### Source code[​](#source-code-14 "Direct link to Source code") [sendJsonRpcRequest](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils.ts#L505) ### sendEthCallRequest[​](#sendethcallrequest "Direct link to sendEthCallRequest") Deprecated in `v0.3.8`. Use `JsonRpcNode#call` instead. example.ts ``` import { JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const result = await node.call({ to: "0xContractAddress", data: "0xCallData", }); ``` #### Source code[​](#source-code-15 "Direct link to Source code") [JsonRpcNode.call](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/transport/JsonRpcNode.ts#L189) ### sendEthGetCodeRequest[​](#sendethgetcoderequest "Direct link to sendEthGetCodeRequest") Deprecated in `v0.3.8`. Use `JsonRpcNode#getCode` instead. example.ts ``` import { JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const code = await node.getCode("0xContractAddress"); ``` #### Source code[​](#source-code-16 "Direct link to Source code") [JsonRpcNode.getCode](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/transport/JsonRpcNode.ts#L137) ### getDelegatedAddress[​](#getdelegatedaddress "Direct link to getDelegatedAddress") Deprecated in `v0.3.8`. Use `JsonRpcNode#getDelegatedAddress` instead. migration.ts ``` import { JsonRpcNode } from "abstractionkit"; const node = new JsonRpcNode("https://ethereum-sepolia-rpc.publicnode.com"); const delegatee = await node.getDelegatedAddress("0xYourEOAAddress"); if (delegatee) { console.log("Delegated to:", delegatee); } else { console.log("Not delegated"); } ``` ## Error and revert decoding[​](#error-and-revert-decoding "Direct link to Error and revert decoding") ### decodeUserOperationRevertReason[​](#decodeuseroperationrevertreason "Direct link to decodeUserOperationRevertReason") Reads the EntryPoint's `UserOperationRevertReason` log directly from a mined receipt and returns the decoded reason, with no extra RPC call. The result is matched to the receipt's `userOpHash`, so a multi-op bundle returns the right entry. The reason is an `Error` string, a `Panic` code, or empty when the inner call left no revert data (usually out-of-gas). * example.ts * Param Types * Return Types example.ts ``` import { decodeUserOperationRevertReason } from "abstractionkit"; const receipt = await response.included(); const revert = decodeUserOperationRevertReason(receipt); if (revert.reverted) { console.log(revert.errorMessage ?? revert.panicCode ?? "out of gas"); } ``` | key | type | description | | :-------- | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | `receipt` | `UserOperationReceiptResult` | A mined UserOperation receipt. The function reads the EntryPoint's UserOperationRevertReason log from it directly, with no extra RPC call. | | key | type | description | | :---------------------------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | `UserOperationRevert` | `object` | The decoded revert reason, matched to the receipt's userOpHash so multi-op bundles return the right entry. | | `UserOperationRevert.reverted` | `boolean` | True when the receipt's success is false. | | `UserOperationRevert.outOfGas` | `boolean` | True when the inner call left no revert data. Usually out-of-gas, though a bare revert()/assert or a call to a non-contract also produce empty data. | | `UserOperationRevert.errorMessage?` | `string` | Decoded Error("...") string, when the call reverted with a reason. | | `UserOperationRevert.panicCode?` | `number` | Decoded Panic(uint256) code (0x11 overflow, 0x12 divide-by-zero, ...). | | `UserOperationRevert.revertData` | `string` | The raw revert data bytes, for custom errors or further decoding. | #### Source code[​](#source-code-17 "Direct link to Source code") [decodeUserOperationRevertReason](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/userOperationRevert.ts#L45) ### parseAaCode[​](#parseaacode "Direct link to parseAaCode") Parses an EntryPoint `AAxx` revert code (e.g. `AA21`) out of an error message, so you can branch on a stable contract-defined code instead of matching message text. When a UserOperation fails, AbstractionKit also surfaces this code directly on `AbstractionKitError.aaCode`, so most callers can read `error.aaCode` without calling `parseAaCode` themselves. * example.ts * Param Types * Return Types example.ts ``` import { parseAaCode } from "abstractionkit"; try { await smartAccount.sendUserOperation(userOperation, bundlerUrl); } catch (error) { // Read the code straight off the error... if (error.aaCode === "AA21") { // account did not pay the prefund } // ...or parse it from any message string const code = parseAaCode(error.message); } ``` | key | type | description | | :-------- | :------- | :--------------------------------------------------- | | `message` | `string` | An error message string from a failed UserOperation. | | key | type | description | | :------- | :-------------------- | :------------------------------------------------------------------------------------------------------- | | `aaCode` | `string \| undefined` | The parsed EntryPoint AAxx revert code (e.g. "AA21"), or undefined when the message contains no AA code. | #### Source code[​](#source-code-18 "Direct link to Source code") [parseAaCode](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/errors.ts#L146) ## EIP-7702 Utilities[​](#eip-7702-utilities "Direct link to EIP-7702 Utilities") ### createAndSignEip7702DelegationAuthorization[​](#createandsigneip7702delegationauthorization "Direct link to createAndSignEip7702DelegationAuthorization") Creates and signs an EIP-7702 delegation authorization, allowing an EOA to delegate its code to a specified contract address. Accepts either a hex-encoded private key string for synchronous signing, or an async signer callback `(hash: string) => Promise` for use with viem, ethers Signers, hardware wallets, or MPC signers. * example.ts * Param Types * Return Type example.ts ``` import { createAndSignEip7702DelegationAuthorization } from "abstractionkit"; // With a private key (synchronous) const authorization = createAndSignEip7702DelegationAuthorization( 11155111n, "0xDelegateeContractAddress", 0n, "0xYourPrivateKey" ); // With an async signer callback const authorization = await createAndSignEip7702DelegationAuthorization( 11155111n, "0xDelegateeContractAddress", 0n, async (hash) => { // Sign the hash using your preferred method return await wallet.signMessage(hash); } ); ``` | key | type | description | | :-------- | :---------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `bigint` | The chain ID for the delegation authorization | | `address` | `string` | The contract address to delegate to | | `nonce` | `bigint` | The authorization nonce of the EOA | | `signer` | `string \| ((hash: string) => Promise)` | Either a hex-encoded private key for synchronous signing, or an async callback that receives the authorization hash and returns a signature. The callback form supports viem wallets, ethers Signers, hardware wallets, and MPC signers. | | key | type | description | | :-------------- | :--------------------- | :------------------------------------------- | | `authorization` | `Authorization7702Hex` | The signed EIP-7702 delegation authorization | Authorization7702Hex | key | type | description | | :-------- | :------- | :--------------------------------------- | | `chainId` | `string` | Hex-encoded chain ID | | `address` | `string` | The delegatee contract address | | `nonce` | `string` | Hex-encoded authorization nonce | | `yParity` | `string` | Hex-encoded y-parity of the signature | | `r` | `string` | Hex-encoded r component of the signature | | `s` | `string` | Hex-encoded s component of the signature | #### Source code[​](#source-code-19 "Direct link to Source code") [createAndSignEip7702DelegationAuthorization](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils7702.ts#L142) ### createRevokeDelegationAuthorization[​](#createrevokedelegationauthorization "Direct link to createRevokeDelegationAuthorization") Creates a signed authorization that revokes an existing EIP-7702 delegation by setting the delegatee address to the zero address, restoring the EOA to a normal account. * example.ts * Param Types * Return Type example.ts ``` import { createRevokeDelegationAuthorization } from "abstractionkit"; const revokeAuth = createRevokeDelegationAuthorization( 11155111n, 0n, "0xYourPrivateKey" ); ``` | key | type | description | | :-------------- | :------- | :-------------------------------------------- | | `chainId` | `bigint` | The chain ID for the revocation authorization | | `nonce` | `bigint` | The authorization nonce of the EOA | | `eoaPrivateKey` | `string` | The hex-encoded private key of the EOA | | key | type | description | | :-------------- | :--------------------- | :------------------------------------------------------------------------------------ | | `authorization` | `Authorization7702Hex` | A signed authorization that delegates to address(0), revoking the existing delegation | Authorization7702Hex | key | type | description | | :-------- | :------- | :--------------------------------------- | | `chainId` | `string` | Hex-encoded chain ID | | `address` | `string` | The delegatee contract address | | `nonce` | `string` | Hex-encoded authorization nonce | | `yParity` | `string` | Hex-encoded y-parity of the signature | | `r` | `string` | Hex-encoded r component of the signature | | `s` | `string` | Hex-encoded s component of the signature | #### Source code[​](#source-code-20 "Direct link to Source code") [createRevokeDelegationAuthorization](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils7702.ts#L197) ### createEip7702DelegationAuthorizationHash[​](#createeip7702delegationauthorizationhash "Direct link to createEip7702DelegationAuthorizationHash") Computes the keccak256 hash of an EIP-7702 delegation authorization using the MAGIC prefix (0x05) as defined in the EIP-7702 spec. Useful when signing the authorization externally. * example.ts * Param Types * Return Type example.ts ``` import { createEip7702DelegationAuthorizationHash } from "abstractionkit"; const hash = createEip7702DelegationAuthorizationHash( 11155111n, "0xDelegateeContractAddress", 0n ); ``` | key | type | description | | :-------- | :------- | :--------------------------------- | | `chainId` | `bigint` | The chain ID for the authorization | | `address` | `string` | The delegatee contract address | | `nonce` | `bigint` | The authorization nonce | | key | type | description | | :----- | :------- | :--------------------------------------------------------------------------------------------------------- | | `hash` | `string` | The keccak256 hash of the RLP-encoded authorization with the MAGIC prefix (0x05), as specified by EIP-7702 | #### Source code[​](#source-code-21 "Direct link to Source code") [createEip7702DelegationAuthorizationHash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils7702.ts#L214) ### createAndSignEip7702RawTransaction[​](#createandsigneip7702rawtransaction "Direct link to createAndSignEip7702RawTransaction") Creates and signs a raw EIP-7702 (set-code, type 0x04) transaction with an authorization list. The transaction is RLP-encoded and includes the type prefix, ready for `eth_sendRawTransaction`. * example.ts * Param Types * Return Type example.ts ``` import { createAndSignEip7702RawTransaction } from "abstractionkit"; const rawTx = createAndSignEip7702RawTransaction( 11155111n, // chainId 0n, // nonce 2000000000n, // maxPriorityFeePerGas 20000000000n, // maxFeePerGas 21000n, // gasLimit "0xDestination", // destination 0n, // value "0x", // data [], // accessList [authorization], // authorizationList "0xYourPrivateKey" ); ``` | key | type | description | | :------------------------- | :--------------------- | :------------------------------------------------------------- | | `chainId` | `bigint` | The chain ID for the transaction | | `nonce` | `bigint` | The transaction nonce | | `max_priority_fee_per_gas` | `bigint` | Maximum priority fee per gas (EIP-1559) | | `max_fee_per_gas` | `bigint` | Maximum fee per gas (EIP-1559) | | `gas_limit` | `bigint` | Gas limit for the transaction | | `destination` | `string` | The target address for the transaction | | `value` | `bigint` | Value to send in wei | | `data` | `string` | The call data for the transaction | | `access_list` | `[string, string[]][]` | EIP-2930 access list: array of \[address, storageKeys] tuples | | `authorization_list` | `Authorization7702[]` | Array of EIP-7702 authorizations to include in the transaction | | `eoaPrivateKey` | `string` | The hex-encoded private key to sign the transaction with | | key | type | description | | :--------------- | :------- | :-------------------------------------------------------------------------------------------------------- | | `rawTransaction` | `string` | The signed, RLP-encoded EIP-7702 transaction with the 0x04 type prefix, ready for eth\_sendRawTransaction | #### Source code[​](#source-code-22 "Direct link to Source code") [createAndSignEip7702RawTransaction](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils7702.ts#L259) ### createEip7702TransactionHash[​](#createeip7702transactionhash "Direct link to createEip7702TransactionHash") Computes the keccak256 hash of an EIP-7702 transaction for signing purposes, using the type 0x04 prefix and RLP encoding. * example.ts * Param Types * Return Type example.ts ``` import { createEip7702TransactionHash } from "abstractionkit"; const txHash = createEip7702TransactionHash( 11155111n, // chainId 0n, // nonce 2000000000n, // maxPriorityFeePerGas 20000000000n, // maxFeePerGas 21000n, // gasLimit "0xDestination", // destination 0n, // value "0x", // data [], // accessList [authorization] // authorizationList ); ``` | key | type | description | | :------------------------- | :--------------------- | :------------------------------------------------------------- | | `chainId` | `bigint` | The chain ID for the transaction | | `nonce` | `bigint` | The transaction nonce | | `max_priority_fee_per_gas` | `bigint` | Maximum priority fee per gas (EIP-1559) | | `max_fee_per_gas` | `bigint` | Maximum fee per gas (EIP-1559) | | `gas_limit` | `bigint` | Gas limit for the transaction | | `destination` | `string` | The target address for the transaction | | `value` | `bigint` | Value to send in wei | | `data` | `string` | The call data for the transaction | | `access_list` | `[string, string[]][]` | EIP-2930 access list: array of \[address, storageKeys] tuples | | `authorization_list` | `Authorization7702[]` | Array of EIP-7702 authorizations to include in the transaction | | key | type | description | | :----- | :------- | :----------------------------------------------------------------------------------------------------- | | `hash` | `string` | The keccak256 hash of the RLP-encoded EIP-7702 transaction with the 0x04 type prefix, used for signing | #### Source code[​](#source-code-23 "Direct link to Source code") [createEip7702TransactionHash](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/utils7702.ts#L323) --- # API Keys & Endpoints Production integrations use authenticated endpoints with an API key. Get yours from [Candide's Dashboard](https://dashboard.candide.dev). No sales call required. API keys and management keys are different This page covers runtime Bundler and Paymaster API keys. The [Platform API](https://docs.candide.dev/platform/overview.md) uses a separate team management key in an `Authorization: Bearer` header to create and maintain gas policies. Never expose a management key in client-side code. ## Endpoint URL Format[​](#endpoint-url-format "Direct link to Endpoint URL Format") One URL serves both the **Bundler** and **Paymaster** APIs. Add the chain ID and your API key: ``` https://api.candide.dev/api/v3/{CHAIN_ID}/{API_KEY} ``` **Example (Sepolia Testnet - Chain ID 11155111):** ``` https://api.candide.dev/api/v3/11155111/YOUR_API_KEY ``` The API key is a path segment: there are no separate auth headers. Treat the full URL as a secret. caution The older per-service URL formats (`/bundler/v3/` and `/paymaster/v3/` with chain names) are deprecated. Use the unified format above. ## Example Request[​](#example-request "Direct link to Example Request") ``` curl https://api.candide.dev/api/v3/11155111/YOUR_API_KEY \ -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "eth_supportedEntryPoints", "params": []}' ``` ``` { "jsonrpc": "2.0", "id": 1, "result": [ "0x433709009B8330FDa32311DF1C2AFA402eD8D009", "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ] } ``` ## Using with AbstractionKit[​](#using-with-abstractionkit "Direct link to Using with AbstractionKit") Pass the same URL wherever the SDK expects a bundler or paymaster endpoint: ``` import { Bundler, CandidePaymaster } from "abstractionkit"; const endpoint = `https://api.candide.dev/api/v3/${chainId}/${apiKey}`; const bundler = new Bundler(endpoint); const paymaster = new CandidePaymaster(endpoint); ``` ## Supported Methods & Networks[​](#supported-methods--networks "Direct link to Supported Methods & Networks") The full [Bundler API](https://docs.candide.dev/wallet/bundler/rpc-methods.md) and [Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md) are available on every [supported network](https://docs.candide.dev/wallet/api/supported-networks.md). ## Rate Limits[​](#rate-limits "Direct link to Rate Limits") Authenticated endpoints have higher rate limits than the [public endpoints](https://docs.candide.dev/wallet/api/public-endpoints.md), scaled to your plan. UserOperation allotments per tier are listed on the [pricing page](https://docs.candide.dev/wallet/pricing.md). If you hit a `Too Many Requests` error in production, [reach out](https://t.me/heymarcopolo) and we will look at your usage pattern together. ## Key Safety[​](#key-safety "Direct link to Key Safety") * Keep keys out of client-side code where possible. For frontend paymaster flows, scope what a key can sponsor with [gas policies](https://docs.candide.dev/instagas/gas-policies.md) so a leaked key cannot drain your gas tank. * The repository holding your key config should never commit the full endpoint URL. * If a key leaks, issue a new one from the [dashboard](https://dashboard.candide.dev), update your deployments, and remove the old key. --- # Public Endpoints Candide provides public endpoints for our **Bundler** and **Paymaster**. No API required. Available on mainnets and testnets. ## Endpoint URL Format[​](#endpoint-url-format "Direct link to Endpoint URL Format") All our public endpoints use the same simple URL format. Just add the **chain ID** for the network you want to use. **Base URL:** ``` https://api.candide.dev/public/v3/ ``` **Example (Sepolia Testnet - Chain ID 11155111):** ``` https://api.candide.dev/public/v3/11155111 ``` ## Supported Networks[​](#supported-networks "Direct link to Supported Networks") See the list of [supported networks](https://docs.candide.dev/wallet/api/supported-networks.md). ## Supported Methods[​](#supported-methods "Direct link to Supported Methods") See the list of the [Bundler API](https://docs.candide.dev/wallet/bundler/rpc-methods.md) and [Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md). ## Rate Limit[​](#rate-limit "Direct link to Rate Limit") To keep things running smoothly for everyone, we limit the number of requests you can make from a single IP address. If you send too many requests, you'll get a `Too Many Requests` error. info Need Higher Limits? For production use or higher rate limits, signup on [Candide's Dashboard](https://dashboard.candide.dev) to get your own Bundler and Paymaster URLs. See [API Keys & Endpoints](https://docs.candide.dev/wallet/api/authenticated-endpoints.md) for the URL format and setup. --- # Reliability & Monitoring Smart wallet infrastructure has one job: be there when a transaction needs to go through. A stuck transaction isn't an inconvenience, it's a stuck user, a failed payment, a support ticket you can't explain. ## How we verify liveness[​](#how-we-verify-liveness "Direct link to How we verify liveness") Every minute, on every chain we support, we execute a real signed transaction end to end. This confirms the full path works: RPC connectivity, bundler availability, and finality. A node health check only confirms a server responded, it says nothing about whether a transaction actually landed. ## RPC redundancy[​](#rpc-redundancy "Direct link to RPC redundancy") Every chain we support runs behind multiple independent RPC providers. If one degrades or goes down, traffic fails over automatically, without operator intervention and without a visible interruption to requests in flight. ## Monitoring built for chains[​](#monitoring-built-for-chains "Direct link to Monitoring built for chains") Chain failures look different from server failures: stuck mempools, reorgs, gas spikes, nonce gaps. We built monitoring for these specifically. An issue triggers an alert before it reaches a production wallet. ## SLA by plan[​](#sla-by-plan "Direct link to SLA by plan") | Plan | SLA | | ----------- | ----------------------------------------- | | Starter | No SLA | | Launch Prod | No SLA | | Grow | 99% uptime | | Enterprise | 99.9% uptime, or as specified in contract | Full terms, downtime definition, and service credits are in our [Terms of Service](https://www.candide.dev/legal/tos). ## Live status[​](#live-status "Direct link to Live status") Current and historical uptime is public at [status.candide.dev](https://status.candide.dev). --- # Supported Networks ### EVM Mainnet[​](#evm-mainnet "Direct link to EVM Mainnet") | Network | Chain ID | | :------------------------------------------------------------- | :------- | | ![Arbitrum One](/img/networks/42161.png)Arbitrum One | 42161 | | ![Avalanche C-Chain](/img/networks/43114.png)Avalanche C-Chain | 43114 | | ![Base](/img/networks/8453.png)Base | 8453 | | ![BnB Smart Chain](/img/networks/56.png)BnB Smart Chain | 56 | | ![Celo](/img/networks/5567.png)Celo | 42220 | | ![Ethereum](/img/networks/1.png)Ethereum | 1 | | ![Gnosis](/img/networks/100.svg)Gnosis | 100 | | ![HyperEVM](/img/networks/999.png)HyperEVM | 999 | | ![Optimism](/img/networks/10.png)Optimism | 10 | | ![Plasma](/img/networks/9745.svg)Plasma | 9745 | | ![Polygon PoS](/img/networks/137.png)Polygon PoS | 137 | | ![Somnia](/img/networks/5031.png)Somnia | 5031 | | ![Tempo](/img/networks/4217.png)Tempo | 4217 | | ![Worldchain](/img/networks/480.svg)Worldchain | 480 | ### EVM Testnet[​](#evm-testnet "Direct link to EVM Testnet") | Network | Chain ID | | :----------------------------------------------------------- | :------- | | ![Arbitrum Sepolia](/img/networks/42161.png)Arbitrum Sepolia | 421614 | | ![Base Sepolia](/img/networks/8453.png)Base Sepolia | 84532 | | ![Optimism Sepolia](/img/networks/10.png)Optimism Sepolia | 11155420 | | ![Polygon Amoy](/img/networks/137.png)Polygon Amoy | 80002 | | ![Sepolia](/img/networks/1.png)Sepolia | 11155111 | | ![Somnia Testnet](/img/networks/5031.png)Somnia Testnet | 50312 | | ![Tempo Testnet](/img/networks/4217.png)Tempo Testnet | 42431 | ## Solana[​](#solana "Direct link to Solana") The Solana Paymaster covers transaction fees and collects them in USDT, so accounts never need to hold SOL. It runs [Kora](https://solana.com/docs/tools/kora), the Solana Foundation's open fee-payer standard. See the [guide](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md). | Network | Service | Fee token | | -------------------------------------------- | ----------------------- | --------- | | ![](/img/networks/solana.svg) Solana Mainnet | Solana Paymaster (Kora) | USDT | ## Extended Networks[​](#extended-networks "Direct link to Extended Networks") These networks are available as an add-on by request. Don't see yours? We can evaluate support for any EVM-equivalent chain on demand. [Contact us](https://www.candide.dev/contact) to request access. | Network | Chain ID | | :-------------- | :--------- | | zkSync Era | 324 | | Linea | 59144 | | Ink | 57073 | | Unichain | 130 | | Mantle | 5000 | | Blast | 81457 | | Scroll | 534352 | | Sonic | 146 | | Berachain | 80094 | | Cronos | 25 | | Polygon zkEVM | 1101 | | Taiko | 167000 | | Metis Andromeda | 1088 | | Mode | 34443 | | Abstract | 2741 | | Zora Network | 7777777 | | Moonbeam | 1284 | | Fraxtal | 252 | | Kava EVM | 2222 | | Aurora | 1313161554 | | Morph | 2818 | | Plume | 98866 | | Derive | 957 | | Lisk | 1135 | --- # Introduction To Candide's Voltaire Voltaire is a modular, developer-friendly, and lightning-fast Python Bundler for Ethereum EIP-4337 Account Abstraction. info Looking for a quick bundler instance? Use one of our [public hosted endpoints](https://docs.candide.dev/wallet/api/public-endpoints.md) for development. Voltaire is a fully open-source project designed to operate within the peer-to-peer mempool of UserOperations, enabling faster and more efficient on-chain transaction inclusion. The code is freely available on [GitHub](https://github.com/candidelabs/voltaire) under a permissive license. We encourage the community to fork, contribute documentation, submit issues and pull requests, ask questions, or stress-test the implementation. ## ERC-4337 Account Abstraction[​](#erc-4337-account-abstraction "Direct link to ERC-4337 Account Abstraction") ERC-4337 enables account abstraction without compromising decentralization or censorship resistance. It maintains the same level of decentralization as the underlying chain's block production. Practically, this means providing access to smart contract wallets without relying on centralized relayers controlled by a single entity. Centralized relayers are replaced with a peer-to-peer network of **Bundlers**. ## How Bundlers Work[​](#how-bundlers-work "Direct link to How Bundlers Work") A Bundler operating in a public mempool functions as a block builder or MEV searcher. As Account Abstraction adoption grows, an increasing share of user transactions will flow through bundles, shifting MEV opportunities from blocks to bundles. Block builders that don't partner with or operate bundlers will miss out on this growing MEV share. Bundlers will optimize for MEV extraction to maximize profits. ### Bundler Role[​](#bundler-role "Direct link to Bundler Role") * Users propagate `UserOperations` to a network where any bundler can process them * Bundlers receive and submit `UserOperations` to the EntryPoint contract without modifying them ### UserOperation Flow[​](#useroperation-flow "Direct link to UserOperation Flow") From a Bundler's perspective, the UserOperation flow works as follows: 1. Wallet clients send individual `UserOperations` to Bundlers 2. Bundlers maintain pending UserOps in memory and propagate them to other mempool nodes 3. Bundlers broadcast peer-to-peer messages for each incoming UserOp 4. For efficiency, the P2P protocol supports batching multiple UserOps in a single message—primarily for syncing new bundlers joining the network 5. When creating a bundle, each bundler can freely choose the size and order—from empty to the entire mempool (within transaction and block gas limits). This is where flashbots-style APIs for searchers integrate 6. Larger bundles reduce overhead: the fixed 21,000 gas cost, EntryPoint contract overhead, and variable costs through "warm" address reuse across operations ## Voltaire[​](#voltaire "Direct link to Voltaire") Voltaire currently maintains complete test suite coverage. While the ERC-4337 specification continues to evolve, all updates will maintain full compliance coverage. As open-source developers, we evaluated every Ethereum client implementation and considered various architectures and languages. We built Voltaire from scratch optimizing for: performance, modularity, developer-friendly language, and open licensing. --- # Install & Self-host Candide's Voltaire This guide is intended for developers wishing to spin a instance of Voltaire ERC-4337 Python bundler for handling User Operations. Voltaire is open source and can be found on [github.com/candidelabs/voltaire](https://github.com/candidelabs/voltaire) info Looking for a quick bundler instance? Use one from our [dashboard](https://dashboard.candide.dev) for your developement ## Docker[​](#docker "Direct link to Docker") The quickest way to get started is to use the docker image. * Install [docker](https://docs.docker.com/engine/install) * Find the latest Voltaire [releases](https://github.com/candidelabs/voltaire/releases) * Run the following command to start a geth node and deploy the EntryPoint contract: ``` docker run --net=host --rm -d ghcr.io/candidelabs/voltaire/voltaire-bundler:$VOLTAIRE_VERSION --entrypoint $ENTRYPOINT --bundler_secret $BUNDLER_SECRET --rpc_url $RPC_URL --ethereum_node_url $ETHEREUM_NODE_URL --chain_id $CHAIN_ID --verbose ``` This command will pull the latest docker image and will then start the bundler to listen for UserOperations. Once running, you can then send User Operation to the following if you used the default `$RPC_URL`: `0.0.0.0:3000/rpc` | flags | Comment | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--entrypoint` | Address of the entrypoint contract | | `--bundler_secret` | Private key of the bundler. Use any EOA's to be the secret and make sure it's funded with some ETH | | `--keystore_file_path` | Bundler Keystore file path. Defaults to first file in keystore folder (optional) | | `--keystore_file_password` | Bundler Keystore file password. Defaults to no password (optional) | | `--rpc_url` | URL of the rpc endpoint you will be hosting. Use `0.0.0.0` as default | | `--rpc_port` | RPC serve port number. Defaults to 3000 (optional) | | `--ethereum_node_url` | Pass the node url of your own instance if you are running a full node [locally](#local-full-node), or a link to a full node endpoint from an [RPC provider](#rpc-provider) - defaults to | | `--chain_id` | Chain ID of the network the bundler will operate in | | `--verbose` | To get verbose logs (optional) | | `--debug` | Expose \_debug rpc namespace for testing (optional) | | `--unsafe` | In order to implement the full spec storage access rules and opcode banning, it must run against a go-ethereum or erigon node, which supports `debug_traceCall` with javascript "tracer". Specifically, `hardhat node`, `ganache`, infura and alchemy nodes do NOT support this API. You can still run the bundler with such nodes, but with `--unsafe` so it would skip these security checks (optional) | | `--legacy_mode` | for netwroks that doesn't support EIP-1559 (optional) | | `--bundle_interval` | Set the bundle interval in seconds for the auto bundle mode - set to zero for manual mode (optional) | | `--whitelist_entity_storage_access` | list of entities to whitelist for storage access rules (optional) | | `--metrics` | enable metrics collection using prometheus and grafana (optional) | | `--version` | show program's version number and exit (optional) | | `--p2p_boot_nodes_enr` | To connect the unified shared mempool. Find all ENRs [in this repo](https://github.com/sherifahmed990/shared_bundler_mempool_4337/). (optional) | | `--eip7702` | To enable EIP-7702 support. (optional) | To know about all flags: ``` docker run --rm -it ghcr.io/candidelabs/voltaire/voltaire-bundler:$VERSION -h ``` ### RPC Provider[​](#rpc-provider "Direct link to RPC Provider") If you want to run voltaire locally and have a paid plan with an RPC provider for a full node that supports debug\_traceCall with javascript "tracer", you can simple replace `$ETHEREUM_NODE_URL` with the link to the rpc endpoint. If it doesn't support a javascript tracer, you can run with the `--unsafe` flag for testing purposes. This mode is not meant to be used in production. ### Local Full Node[​](#local-full-node "Direct link to Local Full Node") If you are running your own full node, simply pass your own node url in `--ethereum_node_url`. If you want to run your own node, which comes with superpowers, follow the instruction below to run GETH [using docker](#start-geth). You can also run a different client implentation. Voltaire works well with [go-ethereum](https://geth.ethereum.org/) and [erigon](https://github.com/ledgerwatch/erigon/). Contact us if you would like to see Voltaire tested with a different client. ## Development[​](#development "Direct link to Development") Ubuntu Instructions ### Install Poetry[​](#install-poetry "Direct link to Install Poetry") ``` curl -sSL https://install.python-poetry.org | python3 - ``` #### Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` poetry install ``` #### Make sure you are using the right python version[​](#make-sure-you-are-using-the-right-python-version "Direct link to Make sure you are using the right python version") ``` poetry env use python3.11 ``` ### Install Docker[​](#install-docker "Direct link to Install Docker") Follow the installation guide to install [docker on ubunutu](https://docs.docker.com/engine/install/ubuntu/) #### Post docker installation[​](#post-docker-installation "Direct link to Post docker installation") Follow the instruction for docker's [post linux instalation](https://docs.docker.com/engine/install/linux-postinstall/) ### Start GETH[​](#start-geth "Direct link to Start GETH") ``` docker run --rm -ti --name geth -p 8545:8545 ethereum/client-go:v1.10.26 \ --miner.gaslimit 12000000 \ --http --http.api personal,eth,net,web3,debug \ --http.vhosts '*,localhost,host.docker.internal' --http.addr "0.0.0.0" \ --ignore-legacy-receipts --allow-insecure-unlock --rpc.allow-unprotected-txs \ --dev \ --verbosity 4 \ --nodiscover --maxpeers 0 --mine --miner.threads 1 \ --networkid 1337 ``` #### In another terminal, deploy the EntryPoint and fund the signer[​](#in-another-terminal-deploy-the-entrypoint-and-fund-the-signer "Direct link to In another terminal, deploy the EntryPoint and fund the signer") ``` geth --exec 'loadScript("test/deploy.js")' attach http://0.0.0.0:8545 ``` #### Set the ENTRYPOINT environment variable[​](#set-the-entrypoint-environment-variable "Direct link to Set the ENTRYPOINT environment variable") ``` source test/init-params ``` ### Run the bundler[​](#run-the-bundler "Direct link to Run the bundler") #### In a new terminal[​](#in-a-new-terminal "Direct link to In a new terminal") ``` poetry run python3 main.py --entrypoint $ENTRYPOINT --bundler_secret $BUNDLER_SECRET --chain_id $CHAIN_ID --verbos ``` #### Test the bundler by cloning `eth-infinitism/bundler-spec-tests`[​](#test-the-bundler-by-cloning-eth-infinitismbundler-spec-tests "Direct link to test-the-bundler-by-cloning-eth-infinitismbundler-spec-tests") Follow the instruction in [eth-infinitism/bundler-spec-tests](https://github.com/eth-infinitism/bundler-spec-tests) to install dependencies and run the test --- # Bundler API The Bundler exposes the standard ERC-4337 JSON-RPC API defined by ERC-7769. ## EntryPoint v0.9[​](#entrypoint-v09 "Direct link to EntryPoint v0.9") EntryPoint v0.9 maintains ABI compatibility with v0.8, requiring no changes to existing Accounts and Paymasters unless adopting new features. Key additions include: * **Parallelizable Paymaster Signing**: A new `paymasterSignature` field allows passing data to Paymasters after UserOperation signing, eliminating delays between user action and confirmation. * **Block Number-Based Validity Ranges**: The highest bit of `validAfter` and `validUntil` now indicates block number-based validity instead of timestamps. * **Flexible InitCode Handling**: `initCode` is silently ignored if the Account already exists, enabling two-dimensional nonce usage. * **UserOp Hash Query**: New `getCurrentUserOpHash` function exposes the current UserOperation hash during execution for third-party contracts. ### eth\_sendUserOperation[​](#eth_senduseroperation "Direct link to eth_sendUserOperation") `eth_sendUserOperation` asks the bundler to sign and submit a UserOperation. #### Example Request[​](#example-request "Direct link to Example Request") ``` { jsonrpc: "2.0", id: 1, method: "eth_sendUserOperation", "params": [ { 'sender': '0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31', 'nonce': '0x13', 'callData': '0x34fcd5be...', 'callGasLimit': '0x2c3d', 'verificationGasLimit': '0x15881', 'preVerificationGas': '0xb7f8', 'maxFeePerGas': '0x2c05b3', 'maxPriorityFeePerGas': '0x2c05a2', 'signature': '0x62f505...', 'factory': None, 'factoryData': None, 'paymaster': None, 'paymasterVerificationGasLimit': None, 'paymasterPostOpGasLimit': None, 'paymasterData': None, 'paymasterSignature': None, // new in v0.9: allows parallelizable paymaster signing 'eip7702Auth': { 'chainId': '0x4268', 'address': '0xa46cc63eBF4Bd77888AA327837d20b23A63a56B5', 'nonce': '0x0f', 'yParity': '0x00', 'r': '0xf37fca7f67bc0b336adfeefe42a8856ca1e6dee48b1ce7748a4044acfe283b01', 's': '0x60ab7d22e278dc43b0b37afada54666c0bf9e0beb9f0cd0159de007d127f3406' } }, '0x433709009B8330FDa32311DF1C2AFA402eD8D009', ] } ``` #### Response[​](#response "Direct link to Response") It returns the hash of the User Operation. In case of an error, it returns the error message. * success * example error ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1234...5678" // UserOpHash } ``` ``` { "jsonrpc": "2.0", "id": 1, "error": { "message": "AA21 didn't pay prefund", "code": -32500 } } ``` ### eth\_estimateUserOperationGas[​](#eth_estimateuseroperationgas "Direct link to eth_estimateUserOperationGas") `eth_estimateUserOperationGas` generates and returns an estimate of how much gas is necessary to allow the transaction to complete, given a `UserOperation`. The signature field is ignored by the wallet, so that the operation will not require the user's approval. Still, it might require putting a "semi-valid" signature (e.g. a signature in the right length) #### Example Request[​](#example-request-1 "Direct link to Example Request") ``` { jsonrpc: "2.0", id: 1, method: "eth_estimateUserOperationGas", "params": [ { sender, // address nonce, // uint256 factory, // address factoryData, // bytes callData, // bytes callGasLimit, // uint256 verificationGasLimit, // uint256 preVerificationGas, // uint256 maxFeePerGas, // uint256 maxPriorityFeePerGas, // uint256 paymaster, // address paymasterVerificationGasLimit, // uint256 paymasterPostOpGasLimit, // uint256 paymasterData, // bytes paymasterSignature, // bytes (new in v0.9: parallelizable paymaster signing) signature // bytes 'eip7702Auth': { 'chainId': '0x4268', 'address': '0xa46cc63eBF4Bd77888AA327837d20b23A63a56B5', // must be valid delegation address for simulation 'nonce': '0x0f', 'yParity': '0x00', 'r': '0xf37fca7f67bc0b336adfeefe42a8856ca1e6dee48b1ce7748a4044acfe283b01', // can pass a dummy signature during estimates 's': '0x60ab7d22e278dc43b0b37afada54666c0bf9e0beb9f0cd0159de007d127f3406' // can pass a dummy signature during estimates } }, '0x433709009B8330FDa32311DF1C2AFA402eD8D009' ] } ``` #### Response[​](#response-1 "Direct link to Response") It returns estimates for a UserOperation Gas parameters for: * `preVerificationGas` * `verificationGasLimit` * `paymasterVerificationGasLimit` (if the UserOperation defines a Paymaster address) * `callGasLimit` ``` { "jsonrpc": "2.0", "id": 0, "result": { callGasLimit, // uint256 preVerificationGas, // uint256 paymasterVerificationGasLimit, // uint256 verificationGasLimit, // uint256 }, } ``` ### eth\_getUserOperationByHash[​](#eth_getuseroperationbyhash "Direct link to eth_getUserOperationByHash") `eth_getUserOperationByHash` returns a UserOperation by its hash returned from `eth_sendUserOperation` #### Request[​](#request "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationByHash", "params": ["0x123..456"] // userop hash } ``` #### Response[​](#response-2 "Direct link to Response") If the userOp is included in a block: returns a full userOp, with the addition of `entryPoint`, `blockNumber`, `blockHash` and `transactionHash`. Other status can be one of the following: | **UserOp Status** | **Response** | | ------------------ | -------------------------------------------------------------------------------- | | Pending in mempool | Returns only the `userOp` + `entrypoint` | | Submitted | Returns the `userop` + `entrypoint` + `transactionHash` | | Included | Returns `userOp`+ `entrypoint` + `transactionHash` + `blockNumber` + `blockHash` | | Error | Returns standard error code and message | | Other | Returns `null` | ### eth\_getUserOperationReceipt[​](#eth_getuseroperationreceipt "Direct link to eth_getUserOperationReceipt") `eth_getUserOperationReceipt` returns the receipt of a UserOperation by its hash returned from `eth_sendUserOperation`. Returns `null` in case the UserOperation is not yet included in a block. #### Request[​](#request-1 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationReceipt", "params": [userOpHash] } ``` #### Response[​](#response-3 "Direct link to Response") * success * error ``` { "jsonrpc": "2.0", "id": 0, "result": { userOpHash, sender, nonce, paymaster, actualGasCost, // actual (gas price * gas used) of the user operation actualGasUsed, // actual gas used of the user operation success, // user operation revert status reason, // If reverted, user operation revert reason logs, receipt, // The TransactionReceipt object. Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_chainId[​](#eth_chainid "Direct link to eth_chainId") `eth_chainId` returns the chain ID of the current network #### Request[​](#request-2 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_chainId", "params": [] } ``` #### Response[​](#response-4 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1", // Chain ID } ``` ### eth\_supportedEntryPoints[​](#eth_supportedentrypoints "Direct link to eth_supportedEntryPoints") `eth_supportedEntryPoints` returns an array of the entryPoint addresses supported by the bundler. A Bundler can support multiple entrypoints. #### Request[​](#request-3 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 1, "method": "eth_supportedEntryPoints", "params": [] } ``` #### Response[​](#response-5 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 1, "result": [ "0x433709009B8330FDa32311DF1C2AFA402eD8D009", "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ] } ``` ## EntryPoint v0.8[​](#entrypoint-v08 "Direct link to EntryPoint v0.8") EIP-7702 support is added with EntryPoint v0.8. The main ERC-4337 RPC calls are `eth_estimateUserOperationGas` and `eth_sendUserOperation`, which take a UserOperation structure. In order to support EIP-7702, a new JSON element named `eip7702Auth` is added to hold the EIP-7702 auth tuple items. The Bundler API for EntryPoint v0.8 is fully backward compatible and has no breaking changes from EntryPoint v0.7. ### eth\_sendUserOperation[​](#eth_senduseroperation-1 "Direct link to eth_sendUserOperation") `eth_sendUserOperation` asks the bundler to sign and submit a UserOperation. Highlighted code is relevant to wallets upgrading EOAs to Smart Accounts using EIP-7702. This is an optional field, and teams building smart wallets without EIP-7702 can ignore this field. #### Example Request[​](#example-request-2 "Direct link to Example Request") ``` { jsonrpc: "2.0", id: 1, method: "eth_sendUserOperation", "params": [ { 'sender': '0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31', 'nonce': '0x13', 'callData': '0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000009a7af758ae5d7b6aae84fe4c5ba67c041dfe53360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000246a627842000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d31000000000000000000000000000000000000000000000000000000000000000000000000000000009a7af758ae5d7b6aae84fe4c5ba67c041dfe53360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000246a627842000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d3100000000000000000000000000000000000000000000000000000000', 'callGasLimit': '0x2c3d', 'verificationGasLimit': '0x15881', 'preVerificationGas': '0xb7f8', 'maxFeePerGas': '0x2c05b3', 'maxPriorityFeePerGas': '0x2c05a2', 'signature': '0x62f5051e378470492a7598d33de0b89e51bbf938ac51a863d0444b926df69d872d0a1ef3ee586bba984e6f290b34ab60b6eda7fb21a99aa84248eb4cfa979c891b', 'factory': None, 'factoryData': None, 'paymaster': None, 'paymasterVerificationGasLimit': None, 'paymasterPostOpGasLimit': None, 'paymasterData': None, 'eip7702Auth': { 'chainId': '0x4268', 'address': '0x6C193e88c2C6ACB0897d162E9496156BfFF73C0F', 'nonce': '0x0f', 'yParity': '0x00', 'r': '0xf37fca7f67bc0b336adfeefe42a8856ca1e6dee48b1ce7748a4044acfe283b01', 's': '0x60ab7d22e278dc43b0b37afada54666c0bf9e0beb9f0cd0159de007d127f3406' } }, '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', ] } ``` #### Response[​](#response-6 "Direct link to Response") It returns the hash of the User Operation. In case of an error, it returns the error message. * success * example error ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1234...5678" // UserOpHash } ``` ``` { "jsonrpc": "2.0", "id": 1, "error": { "message": "AA21 didn't pay prefund", "code": -32500 } } ``` ``` { "jsonrpc": "2.0", "id": 1, "error": { "message": "paymaster stake too low", "data": { "paymaster": "0x123456789012345678901234567890123456790", "minimumStake": "0xde0b6b3a7640000", "minimumUnstakeDelay": "0x15180" }, "code": -32504 } } ``` ### eth\_estimateUserOperationGas[​](#eth_estimateuseroperationgas-1 "Direct link to eth_estimateUserOperationGas") `eth_estimateUserOperationGas` generates and returns an estimate of how much gas is necessary to allow the transaction to complete, given a `UserOperation`. The signature field is ignored by the wallet, so that the operation will not require the user’s approval. Still, it might require putting a “semi-valid” signature (e.g. a signature in the right length) #### Example Request[​](#example-request-3 "Direct link to Example Request") ``` { jsonrpc: "2.0", id: 1, method: "eth_estimateUserOperationGas", "params": [ { sender, // address nonce, // uint256 factory, // address factoryData, // bytes callData, // bytes callGasLimit, // uint256 verificationGasLimit, // uint256 preVerificationGas, // uint256 maxFeePerGas, // uint256 maxPriorityFeePerGas, // uint256 paymaster, // address paymasterVerificationGasLimit, // uint256 paymasterPostOpGasLimit, // uint256 paymasterData, // bytes signature // bytes 'eip7702Auth': { 'chainId': '0x4268', 'address': '0x6C193e88c2C6ACB0897d162E9496156BfFF73C0F', // must be valid delegation address for simulation 'nonce': '0x0f', 'yParity': '0x00', 'r': '0xf37fca7f67bc0b336adfeefe42a8856ca1e6dee48b1ce7748a4044acfe283b01', // can pass a dummy signature during estimates 's': '0x60ab7d22e278dc43b0b37afada54666c0bf9e0beb9f0cd0159de007d127f3406' // can pass a dummy signature during estimates } }, '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' ] } ``` #### Response[​](#response-7 "Direct link to Response") It returns estimates for a UserOperation Gas parameters for: * `preVerificationGas` * `verificationGasLimit` * `paymasterVerificationGasLimit` (if the UserOperation defines a Paymaster address) * `callGasLimit` ``` { "jsonrpc": "2.0", "id": 0, "result": { callGasLimit, // uint256 preVerificationGas, // uint256 paymasterVerificationGasLimit, // uint256 verificationGasLimit, // uint256 }, } ``` ### eth\_getUserOperationByHash[​](#eth_getuseroperationbyhash-1 "Direct link to eth_getUserOperationByHash") `eth_getUserOperationByHash` returns a UserOperation by its hash returned from `eth_sendUserOperation` #### Request[​](#request-4 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationByHash", "params": ["0x123..456"] // userop hash } ``` #### Response[​](#response-8 "Direct link to Response") If the userOp is included in a block: returns a full userOp, with the addition of `entryPoint`, `blockNumber`, `blockHash` and `transactionHash`. Other status can be one of the following: | **UserOp Status** | **Response** | | ------------------ | -------------------------------------------------------------------------------- | | Pending in mempool | Returns only the `userOp` + `entrypoint` | | Submitted | Returns the `userop` + `entrypoint` + `transactionHash` | | Included | Returns `userOp`+ `entrypoint` + `transactionHash` + `blockNumber` + `blockHash` | | Error | Returns standard error code and message | | Other | Returns `null` | * success * error Example response for an Included status ``` { userOpHash: '0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', sender: '0xb8741a449d50ed0dcfe395287f85be152884c8d9', nonce: 0n, paymaster: '0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020', actualGasCost: 8078496n, actualGasUsed: 504906n, success: true, logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', receipt: { blockHash: '0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c', blockNumber: 10419807n, from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: 6978990n, gasUsed: 507053n, logs: '[{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x90","removed":false,"topics":["0xecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f8440","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ecd4ec46d4d2a6b64fe960b3d64e8b94b2234eb000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d31","logIndex":"0x91","removed":false,"topics":["0x141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a8","0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762","logIndex":"0x92","removed":false,"topics":["0x4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec670000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020","logIndex":"0x93","removed":false,"topics":["0xd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x94","removed":false,"topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b403700000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b526000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001048d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b200d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b00d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x95","removed":false,"topics":["0xb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x96","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000056"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x97","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000057"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x98","removed":false,"topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x99","removed":false,"topics":["0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', logsBloom: '0x0000040000009000000000000000010080000000000000000000000000000000000800000000000000020001000004040010000000000000800002000000000000001000000000000000000c0002000000000000010000080040000000000000020000000a0000000500002000000800008000000100000000000014000000000800010020000200008000000040000000000200000400000000000000000000000004000000000000500000000004000210000000000000000002001000000020200082000000000001000008000000000000002060000000100000000026000000082000010000000000000008100220000000000000000000000010000200', transactionHash: '0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa', transactionIndex: 29n, effectiveGasPrice: 16n } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_getUserOperationReceipt[​](#eth_getuseroperationreceipt-1 "Direct link to eth_getUserOperationReceipt") `eth_getUserOperationReceipt` returns the receipt of a UserOperation by its hash returned from `eth_sendUserOperation`. Returns `null` in case the UserOperation is not yet included in a block. #### Request[​](#request-5 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationReceipt", "params": [userOpHash] } ``` #### Response[​](#response-9 "Direct link to Response") * success * error ``` { "jsonrpc": "2.0", "id": 0, "result": { userOpHash, sender, nonce, paymaster, actualGasCost, // actual (gas price * gas used) of the user operation actualGasUsed, // actual gas used of the user operation success, // user operation revert status reason, // If reverted, user operation revert reason logs, receipt, // The TransactionReceipt object. Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_chainId[​](#eth_chainid-1 "Direct link to eth_chainId") `eth_chainId` returns the chain ID of the current network #### Request[​](#request-6 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_chainId", "params": [] } ``` #### Response[​](#response-10 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1", // Chain ID } ``` ### eth\_supportedEntryPoints[​](#eth_supportedentrypoints-1 "Direct link to eth_supportedEntryPoints") `eth_supportedEntryPoints` returns an array of the entryPoint addresses supported by the bundler. A Bundler can support multiple entrypoints. #### Request[​](#request-7 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 1, "method": "eth_supportedEntryPoints", "params": [] } ``` #### Response[​](#response-11 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 1, "result": [ "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ] } ``` ## EntryPoint v0.7[​](#entrypoint-v07 "Direct link to EntryPoint v0.7") ### eth\_sendUserOperation[​](#eth_senduseroperation-2 "Direct link to eth_sendUserOperation") `eth_sendUserOperation` asks the bundler to sign and submit a User Operation #### Request[​](#request-8 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 1, "method": "eth_sendUserOperation", "params": [ { sender, // address nonce, // uint256 factory, // address factoryData, // bytes callData, // bytes callGasLimit, // uint256 verificationGasLimit, // uint256 preVerificationGas, // uint256 maxFeePerGas, // uint256 maxPriorityFeePerGas, // uint256 paymaster, // address paymasterVerificationGasLimit, // uint256 paymasterPostOpGasLimit, // uint256 paymasterData, // bytes signature // bytes }, entryPoint // address ] } ``` #### Response[​](#response-12 "Direct link to Response") It returns the hash of the User Operation. In case of an error, it returns the error message. * success * example error ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1234...5678" // UserOpHash } ``` ``` { "jsonrpc": "2.0", "id": 1, "error": { "message": "AA21 didn't pay prefund", "code": -32500 } } ``` ``` { "jsonrpc": "2.0", "id": 1, "error": { "message": "paymaster stake too low", "data": { "paymaster": "0x123456789012345678901234567890123456790", "minimumStake": "0xde0b6b3a7640000", "minimumUnstakeDelay": "0x15180" }, "code": -32504 } } ``` ### eth\_estimateUserOperationGas[​](#eth_estimateuseroperationgas-2 "Direct link to eth_estimateUserOperationGas") `eth_estimateUserOperationGas` generates and returns an estimate of how much gas is necessary to allow the transaction to complete, given a `UserOperation`. The signature field is ignored by the wallet, so that the operation will not require the user’s approval. Still, it might require putting a “semi-valid” signature (e.g. a signature in the right length) #### Request[​](#request-9 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 1, "method": "eth_estimateUserOperationGas", "params": [ { sender, // address nonce, // uint256 factory, // address factoryData, // bytes callData, // bytes callGasLimit, // uint256 verificationGasLimit, // uint256 preVerificationGas, // uint256 maxFeePerGas, // uint256 maxPriorityFeePerGas, // uint256 paymaster, // address paymasterVerificationGasLimit, // uint256 paymasterPostOpGasLimit, // uint256 paymasterData, // bytes signature // bytes }, entryPoint // address ] } ``` #### Response[​](#response-13 "Direct link to Response") It returns estimates for a UserOperation Gas parameters for: * `preVerificationGas` * `verificationGasLimit` * `paymasterVerificationGasLimit` (if the UserOperation defines a Paymaster address) * `callGasLimit` ``` { "jsonrpc": "2.0", "id": 0, "result": { callGasLimit, // uint256 preVerificationGas, // uint256 paymasterVerificationGasLimit, // uint256 verificationGasLimit, // uint256 }, } ``` ### eth\_getUserOperationByHash[​](#eth_getuseroperationbyhash-2 "Direct link to eth_getUserOperationByHash") `eth_getUserOperationByHash` returns a UserOperation by its hash returned from `eth_sendUserOperation` #### Request[​](#request-10 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationByHash", "params": ["0x123..456"] // userop hash } ``` #### Response[​](#response-14 "Direct link to Response") If the userOp is included in a block: returns a full userOp, with the addition of `entryPoint`, `blockNumber`, `blockHash` and `transactionHash`. Other status can be one of the following: | **UserOp Status** | **Response** | | ------------------ | -------------------------------------------------------------------------------- | | Pending in mempool | Returns only the `userOp` + `entrypoint` | | Submitted | Returns the `userop` + `entrypoint` + `transactionHash` | | Included | Returns `userOp`+ `entrypoint` + `transactionHash` + `blockNumber` + `blockHash` | | Error | Returns standard error code and message | | Other | Returns `null` | * success * error Example response for an Included status ``` { userOpHash: '0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', sender: '0xb8741a449d50ed0dcfe395287f85be152884c8d9', nonce: 0n, paymaster: '0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020', actualGasCost: 8078496n, actualGasUsed: 504906n, success: true, logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', receipt: { blockHash: '0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c', blockNumber: 10419807n, from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: 6978990n, gasUsed: 507053n, logs: '[{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x90","removed":false,"topics":["0xecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f8440","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ecd4ec46d4d2a6b64fe960b3d64e8b94b2234eb000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d31","logIndex":"0x91","removed":false,"topics":["0x141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a8","0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762","logIndex":"0x92","removed":false,"topics":["0x4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec670000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020","logIndex":"0x93","removed":false,"topics":["0xd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x94","removed":false,"topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b403700000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b526000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001048d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b200d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b00d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x95","removed":false,"topics":["0xb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x96","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000056"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x97","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000057"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x98","removed":false,"topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x99","removed":false,"topics":["0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', logsBloom: '0x0000040000009000000000000000010080000000000000000000000000000000000800000000000000020001000004040010000000000000800002000000000000001000000000000000000c0002000000000000010000080040000000000000020000000a0000000500002000000800008000000100000000000014000000000800010020000200008000000040000000000200000400000000000000000000000004000000000000500000000004000210000000000000000002001000000020200082000000000001000008000000000000002060000000100000000026000000082000010000000000000008100220000000000000000000000010000200', transactionHash: '0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa', transactionIndex: 29n, effectiveGasPrice: 16n } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_getUserOperationReceipt[​](#eth_getuseroperationreceipt-2 "Direct link to eth_getUserOperationReceipt") `eth_getUserOperationReceipt` returns the receipt of a UserOperation by its hash returned from `eth_sendUserOperation`. Returns `null` in case the UserOperation is not yet included in a block. #### Request[​](#request-11 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationReceipt", "params": [userOpHash] } ``` #### Response[​](#response-15 "Direct link to Response") * success * error ``` { "jsonrpc": "2.0", "id": 0, "result": { userOpHash, sender, nonce, paymaster, actualGasCost, // actual (gas price * gas used) of the user operation actualGasUsed, // actual gas used of the user operation success, // user operation revert status reason, // If reverted, user operation revert reason logs, receipt, // The TransactionReceipt object. Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_chainId[​](#eth_chainid-2 "Direct link to eth_chainId") `eth_chainId` returns the chain ID of the current network #### Request[​](#request-12 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_chainId", "params": [] } ``` #### Response[​](#response-16 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1", // Chain ID } ``` ### eth\_supportedEntryPoints[​](#eth_supportedentrypoints-2 "Direct link to eth_supportedEntryPoints") `eth_supportedEntryPoints` returns an array of the entryPoint addresses supported by the bundler. A Bundler can support multiple entrypoints. #### Request[​](#request-13 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 1, "method": "eth_supportedEntryPoints", "params": [] } ``` #### Response[​](#response-17 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 1, "result": [ "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ] } ``` ## EntryPoint v0.6[​](#entrypoint-v06 "Direct link to EntryPoint v0.6") ### eth\_sendUserOperation[​](#eth_senduseroperation-3 "Direct link to eth_sendUserOperation") `eth_sendUserOperation` asks the bundler to sign and submit a User Operation #### Request[​](#request-14 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_sendUserOperation", "params": [ { sender, nonce, initCode, callData, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas, paymasterAndData, signature, }, entrypointAddress, ] } ``` #### Response[​](#response-18 "Direct link to Response") It returns the hash of the User Operation. In case of an error, it returns the error message. ``` { "jsonrpc": "2.0", "id": 0, "result": "0x..." // UserOpHash } ``` ### eth\_estimateUserOperationGas[​](#eth_estimateuseroperationgas-3 "Direct link to eth_estimateUserOperationGas") `eth_estimateUserOperationGas` generates and returns an estimate of how much gas is necessary to allow the transaction to complete, given a `UserOperation`. #### Request[​](#request-15 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_estimateUserOperationGas", "params": [ { sender, nonce, initCode, callData, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas, paymasterAndData, signature, }, entrypointAddress, ] } ``` #### Response[​](#response-19 "Direct link to Response") It returns estimates for a UserOperation Gas parameters for `preVerificationGas`, `verificationGasLimit`, and `callGasLimit` ``` { "jsonrpc": "2.0", "id": 0, "result": { callGasLimit preVerificationGas, verificationGasLimit, }, } ``` ### eth\_getUserOperationByHash[​](#eth_getuseroperationbyhash-3 "Direct link to eth_getUserOperationByHash") `eth_getUserOperationByHash` returns a UserOperation by its hash returned from `eth_sendUserOperation` #### Request[​](#request-16 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationByHash", "params": [userOpHash] } ``` #### Response[​](#response-20 "Direct link to Response") If the userOp is included in a block: returns a full userOp, with the addition of `entryPoint`, `blockNumber`, `blockHash` and `transactionHash`. Other status can be one of the following: | **UserOp Status** | **Response** | | ------------------ | -------------------------------------------------------------------------------- | | Pending in mempool | Returns only the `userOp` + `entrypoint` | | Submitted | Returns the `userop` + `entrypoint` + `transactionHash` | | Included | Returns `userOp`+ `entrypoint` + `transactionHash` + `blockNumber` + `blockHash` | | Error | Returns standard error code and message | | Other | Returns `null` | * success * error Example response for an `Included` status ``` { "jsonrpc": "2.0", "id": 0, "result": { "userOperation": { "sender": "0x543d6683eCB476a599cE3F084Bdf3F8A04AEDc8f", "nonce": "0x55", "initCode": "0x", "callData": "0x541d63c800000000000000000000000012c20bcee31bd34064caa6ec0fd5c4c2fce179c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000e32359892d6c5b0a4a4ad64b4326cae0fda15a8b000000000000000000000000000000000000000000000002b5e3af16b188000000000000000000000000000000000000000000000000000000000000", "callGasLimit": "0x186a0", "verificationGasLimit": "0x35c5e", "preVerificationGas": "0x18704", "maxFeePerGas": "0x1a7c9fd800", "maxPriorityFeePerGas": "0x1a7c9fd800", "paymasterAndData": "0x3fe285dcd76bcce4ac92d38a6f2f8e964041e02000000000000000000000000000000000000000000300006759820600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b7940ef6cdc9cca0ea65e49f37684914b962b36ea4cbb381dad14b291ddadb35399cddcf519dd89608866bf71abbb1cbcae83a2c022bf7fe01e0848f49b298951c", "signature": "0x000000000000000000000000032e9877498140afb58bb4c177850389c1a275ee850b24d96e881c7e48e8be6f003602a23eee383a5059f874688d179fd4e18d44261c10b1ae64193bc82a2b251bff502282aa2053b898b0544009e7a56e3ec19334781a9a366cbda40075fd5d9e667bafce929eff2829acd4134abd41e2e736d1f3cf0b858a4e16c86365a9c5751c" }, "entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "blockNumber": "0x3e52d61", "blockHash": "0xef3eba62563303e4add41aa107e67a9a98b0cac82c0b35c4c9f1161bf99b573d", "transactionHash": "0x682ddd23c100631127a945f2a2ba4edeff04911af7491148a662253cb91aaae7" } } ``` ``` { "jsonrpc": "2.0", "id": 0, "error": { "code": -000000, "message": "AAxx error message" } } ``` ### eth\_getUserOperationReceipt[​](#eth_getuseroperationreceipt-3 "Direct link to eth_getUserOperationReceipt") `eth_getUserOperationReceipt` returns the receipt of a UserOperation by its hash returned from `eth_sendUserOperation` #### Request[​](#request-17 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_getUserOperationReceipt", "params": [userOpHash] } ``` #### Response[​](#response-21 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": { userOpHash, sender, nonce, paymaster, actualGasCost, // actual (gas price * gas used) of the user operation actualGasUsed, // actual gas used of the user operation success, // user operation revert status reason, // If reverted, user operation revert reason logs, receipt, // The TransactionReceipt object. Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation } } ``` ### eth\_chainId[​](#eth_chainid-3 "Direct link to eth_chainId") `eth_chainId` returns the chain ID of the current network #### Request[​](#request-18 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_chainId", "params": [] } ``` #### Response[​](#response-22 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": "0x1", // Chain ID } ``` ### eth\_supportedEntryPoints[​](#eth_supportedentrypoints-3 "Direct link to eth_supportedEntryPoints") `eth_supportedEntryPoints` returns an array of the entryPoint addresses supported by the bundler #### Request[​](#request-19 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "eth_supportedEntryPoints", "params": [] } ``` #### Response[​](#response-23 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 0, "result": ["0x00...", "0x01..."], } ``` ## Gas Prices[​](#gas-prices "Direct link to Gas Prices") ### voltaire\_feesPerGas[​](#voltaire_feespergas "Direct link to voltaire_feesPerGas") The `voltaire_feesPerGas` method provides the recommended gas fees, `maxFeePerGas` and `maxPriorityFeePerGas`. This method simplifies gas price estimation by offering a reliable price for the various EVM networks that Candide's Bundler Voltaire supports. This allows you to set appropriate gas fees for your UserOperations without needing to implement complex gas price prediction logic for each chain. #### Request[​](#request-20 "Direct link to Request") ``` { "jsonrpc": "2.0", "id": 0, "method": "voltaire_feesPerGas", "params": [] } ``` #### Response[​](#response-24 "Direct link to Response") ``` { "jsonrpc": "2.0", "id": 1, "result": { "maxFeePerGas": "0x6fc35fb80", "maxPriorityFeePerGas": "0x124f80" } } ``` Gas Fee Concepts * `maxFeePerGas`: This represents the absolute maximum price per unit of gas you are willing to pay for a UserOperation. This fee is the sum of the baseFee and the maxPriorityFeePerGas. * `maxPriorityFeePerGas`: Also known as the "tip", this is the fee that is paid directly to the bundler. A higher priority fee can incentivize the bundler to include your UserOperation in a block more quickly, especially during times of network congestion. --- # Authentication Methods in Smart Wallets Smart Accounts, like regular accounts (EOAs), rely on signatures for validating transactions and messages. What distinguishes Smart Accounts is their ability to support various signature schemes, including Passkeys and Multisig. ## Passkeys[​](#passkeys "Direct link to Passkeys") Candide supports on-chain Passkeys, enabling users to sign transactions directly from their devices. Using default device authentication systems (PIN, biometrics, FaceID), signatures are authenticated on-chain through smart contracts without relying on centralized infrastructure. See a demo at [passkeys.candide.dev](https://passkeys.candide.dev) and learn more on the [Passkeys documentation page](https://docs.candide.dev/wallet/plugins/passkeys.md). ## Multisig[​](#multisig "Direct link to Multisig") Candide supports on-chain multisig (multi-signature) accounts that enhance security by requiring multiple approvals for transactions. This feature is ideal for implementing two-factor authentication (2FA) and suits wallets targeting companies or DAOs where transactions require multiple key approvals. Visit the dedicated [Multisig guide](https://docs.candide.dev/wallet/guides/multisig.md). ## Traditional Signers[​](#traditional-signers "Direct link to Traditional Signers") Externally Owned Accounts (EOAs) utilize private and public keys for security. Similarly, Smart Accounts can employ the same approach through locally generated private keys, hosted MPC solutions, or existing user wallets like MetaMask. ### Social / Email[​](#social--email "Direct link to Social / Email") Developers can integrate third-party "Signer services" into their smart accounts, providing the benefits of traditional Web2 onboarding experiences. These services enable user authentication through familiar methods such as email magic links, social logins, or SMS. * Candide's AbstractionKit is highly adaptable, supporting any third-party Signer service. The process for assigning the smart account owner and signing a User Operation is consistent across all services. These services generate an EOA wallet for each user in their backend and provide authentication via social logins (e.g., Twitter), email, or a backend passkey. * Since the service provides the EOA public address, it can be easily assigned as the owner of the smart wallet in AbstractionKit. The authentication method does not affect integration, ensuring a consistent process for assigning smart account ownership and signing User Operations. For details on how to integrate as third-parties as main signer, you can refer to the [guide for EOA wallets and Third Party Signers](https://docs.candide.dev/wallet/guides/authentication.md#eoa-wallets--third-party-signers). * However, it is important to note that using third-party services as the primary signer of the account makes the wallet you are offering **custodial.** Candide recommends using onchain [Safe Passkeys](https://docs.candide.dev/wallet/plugins/passkeys.md) as the main signer. Below are some third-party recovery options that you can consider. Candide does not endorse any of these options; the guides are provided for educational purposes only. | Service | Key Management Method | Authentication Methods | Plug-n-play Front End? | Guide | | ------------ | ---------------------------- | ---------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Dfns | HSM, MPC | Custom | No | | | Dynamic | MPC (Turnkey under the hood) | Email, Social, Wallets | Yes | | | Fireblocks | MPC | Custom | No | | | Lit Protocol | MPC | Email, Social, Phone | No | [Add a Google Account as a Recovery Method using Lit](https://docs.candide.dev/wallet/guides/recovery-with-google-using-lit.md) | | Magic.link | AWS KMS | Email, Social | Yes | [Add an Social Account as a Recovery Method using Magic](https://docs.candide.dev/wallet/guides/magic.md) | | Privy | MPC | Email, Social, Wallets | Yes | | | Turnkey | AWS KMS | Custom | No | [Use Turnkey with AbstractionKit](https://docs.candide.dev/wallet/guides/turnkey.md) | | Web3Auth | MPC, key sharding | Social, Email | Yes | | ### EOA Wallets & Third-party signers[​](#eoa-wallets--third-party-signers "Direct link to EOA Wallets & Third-party signers") You can allow users to use MetaMask, or any other EOA wallet, or third-party signers to be the owner of the Smart Account.EOAs exposes a JavaScript Ethereum Provider API. #### Setup Account Owner[​](#setup-account-owner "Direct link to Setup Account Owner") * ethers example * viem example ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { BrowserProvider } from "ethers"; const provider = new BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const signerAddress = await signer.getAddress(); const smartAccount = SafeAccount.initializeNewAccount([signerAddress]); ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { createWalletClient, custom } from "viem"; const viemWalletClient = createWalletClient({ transport: custom(window.ethereum!), }); const signerAddresses = await viemWalletClient.requestAddresses(); const signerAddress = signerAddresses[0]; const smartAccount = SafeAccount.initializeNewAccount([signerAddress]); ``` #### Signing a UserOperation[​](#signing-a-useroperation "Direct link to Signing a UserOperation") The recommended path for every new integration is the capability-oriented **[External Signer](#external-signers)** API. `signUserOperationWithSigner(s)` returns an account-ready signature: hash computation and signature formatting are handled for you across viem, ethers, hardware wallets, HSMs, MPC, and WebAuthn signers, without handing private keys to the SDK. The manual EIP-712 flow below is kept for advanced use cases where you need direct control over hash computation or signature formatting. ##### Manual EIP-712 signing (advanced)[​](#manual-eip-712-signing-advanced "Direct link to Manual EIP-712 signing (advanced)") Only this manual flow requires formatting the raw signature yourself. The External Signer API above does this automatically. For legacy `SafeAccountV0_3_0`, wrap raw signatures with [`formatEip712SignaturesToUseroperationSignature()`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#formateip712signaturestouseroperationsignature). For the default `SafeMultiChainSigAccountV1`, use `formatSignaturesToUseroperationSignature([{ signer, signature }], { isMultiChainSignature: true })`; the Unified Account validates even a single UserOperation through its multichain signature scheme. 1. **Direct Signing of the EIP-712 Hash** Use this method if you don't need to display additional information for the user to validate. This approach is quick using [`getUserOperationEip712Hash()`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#getuseroperationeip712hash). * ethers * viem ``` let userOperation = ... // Use createUserOperation() to help you construct the userOp below const safeUserOpHash = SafeAccount.getUserOperationEip712Hash(userOperation, chainId); const signature = signer.signingKey.sign(safeUserOpHash).serialized; userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [{ signer: signer.address, signature }], { isMultiChainSignature: true }, ); ``` ``` let userOperation = ... // Use createUserOperation() to help you construct the userOp below const safeUserOpHash = SafeAccount.getUserOperationEip712Hash( userOperation, chainId ) as `0x${string}`; const signature = await viemWalletClient.account.sign({ hash: safeUserOpHash }); userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [{ signer: viemWalletClient.account.address, signature }], { isMultiChainSignature: true }, ); ``` 2. **Signing with EIP-712 Typed Data** This method involves retrieving the EIP-712 typed data using [`getUserOperationEip712Data()`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#getuseroperationeip712data), displaying it to the user for review, and then signing the typed data. It allows the user to verify the data before signing. * ethers * viem ``` let userOperation = ... // Use createUserOperation() to help you construct the userOp below const { domain, types, messageValue } = SafeAccount.getUserOperationEip712Data(userOperation, chainId); const signature = await wallet.signTypedData(domain, types, messageValue); userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [{ signer: signer.address, signature }], { isMultiChainSignature: true }, ); ``` ``` let userOperation = ... // Use createUserOperation() to help you construct the userOp below const { domain, types, messageValue } = SafeAccount.getUserOperationEip712Data(userOperation, chainId); const signedTypedData = await viemWalletClient.signTypedData({ domain, types, message: messageValue, primaryType: "SafeOp", } as any); userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [{ signer: viemWalletClient.account.address, signature: signedTypedData }], { isMultiChainSignature: true }, ); ``` ### Private key Storage[​](#private-key-storage "Direct link to Private key Storage") Traditional wallets encrypts the private key in local storage. In this setup, the owner of the smart account is represented by it. Use [signUserOperation](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#signuseroperation). #### Signup Account Owner[​](#signup-account-owner "Direct link to Signup Account Owner") ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); ``` #### Sign UserOperation[​](#sign-useroperation "Direct link to Sign UserOperation") ``` const chainId = BigInt(process.env.CHAIN_ID as string); const ownerPrivateKey = process.env.PRIVATE_KEY as string; const userOperation = ... // returned from createUserOperation() userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], chainId, ) ``` ## External Signers[​](#external-signers "Direct link to External Signers") Available since AbstractionKit v0.3.2. `ExternalSigner` is a capability-oriented interface that lets you plug any signing source into AbstractionKit without passing raw private keys to the SDK. It covers viem local accounts, viem WalletClients, ethers Wallets, hardware wallets, HSMs, MPC services, and WebAuthn signers through a single API. Use the [External Signers SDK reference](https://docs.candide.dev/wallet/abstractionkit/external-signers.md) for the agent-oriented decision workflow, compatibility tables, adapter recipes, and full example links. Every account class exposes the same method family: ``` // Safe accounts — multi-signer (plural) userOperation.signature = await safe.signUserOperationWithSigners(userOperation, [signer], chainId) // Simple7702 / Calibur — single signer userOperation.signature = await account.signUserOperationWithSigner(userOperation, signer, chainId) // SafeMultiChainSigAccountV1 — multi-op, one signature across chains const signatures = await account.signUserOperationsWithSigners( [{ userOperation: op1, chainId: id1 }, { userOperation: op2, chainId: id2 }], [signer], ) ``` --- # Key Management with Calibur Calibur supports multiple signers on a single account. You can register secondary keys (EOA addresses, passkeys, P256 keys), each with their own settings: expiration time, per-key hooks, and admin or non-admin privileges. This guide demonstrates the full key lifecycle: listing, registering, signing with a secondary key, updating settings, and revoking. Key concepts to understand before starting: * **Three key types**: `Secp256k1` (standard EOA keys), `WebAuthnP256` (passkeys), and `P256` (raw secp256r1 keys). * **Root key**: The EOA's own key has `keyHash = bytes32(0)` and is always admin. It cannot be revoked. * **Admin keys only**: Only admin keys can call management functions (register, update, revoke). Non-admin keys can sign regular transactions but cannot modify the account's key configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Sepolia Bundler and Paymaster endpoints from the [Dashboard](https://dashboard.candide.dev) * Node and a package manager (yarn or npm) * An EOA already delegated to Calibur. Run the [Calibur quickstart](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) first if you haven't done this. Here's the [complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/03-manage-keys.ts) for you to reference if you prefer to run it directly. *** ## Step 1: Setup and list existing keys[​](#step-1-setup-and-list-existing-keys "Direct link to Step 1: Setup and list existing keys") 1. Install dependencies: ``` npm i abstractionkit dotenv ``` 2. Configure environment variables. Create a `.env` file: .env ``` CHAIN_ID=11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com BUNDLER_URL=https://api.candide.dev/public/v3/11155111 PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 PRIVATE_KEY=your_eoa_private_key_here ``` 3. Create your script file with imports and initialization: index.ts ``` import { Wallet } from "ethers"; import { Calibur7702Account, CandidePaymaster, CaliburKeyType, ZeroAddress, } from "abstractionkit"; const chainId = BigInt(process.env.CHAIN_ID as string); const bundlerUrl = process.env.BUNDLER_URL as string; const paymasterUrl = process.env.PAYMASTER_URL as string; const nodeUrl = process.env.JSON_RPC_NODE_PROVIDER as string; const privateKey = process.env.PRIVATE_KEY as string; const eoaWallet = new Wallet(privateKey); const eoaAddress = eoaWallet.address; const smartAccount = new Calibur7702Account(eoaAddress); const paymaster = new CandidePaymaster(paymasterUrl); // This guide requires an already-delegated account. const delegated = await smartAccount.isDelegatedToThisAccount(nodeUrl); if (!delegated) { console.log("EOA is not yet delegated to Calibur. Run the quickstart first."); process.exit(1); } ``` 4. List all registered keys and their settings: index.ts ``` const keyTypeNames = { [CaliburKeyType.P256]: "P256", [CaliburKeyType.WebAuthnP256]: "WebAuthn", [CaliburKeyType.Secp256k1]: "Secp256k1", }; const keys = await smartAccount.getKeys(nodeUrl); console.log("Registered keys:\n"); for (const key of keys) { const keyHash = Calibur7702Account.getKeyHash(key); const settings = await smartAccount.getKeySettings(nodeUrl, keyHash); console.log(` Key: ${keyHash.slice(0, 18)}...`); console.log(` Type: ${keyTypeNames[key.keyType] ?? key.keyType}`); console.log(` Admin: ${settings.isAdmin}`); console.log(` Expires: ${settings.expiration === 0 ? "never" : new Date(settings.expiration * 1000).toISOString()}`); console.log(` Hook: ${settings.hook === ZeroAddress ? "none" : settings.hook}`); console.log(); } ``` `getKeys` returns all currently registered keys. `getKeySettings` returns a key's expiration timestamp, hook address, and admin flag. Run the code to verify: Terminal ``` npx ts-node index.ts ``` Result example ``` Registered keys: Key: 0x0000000000000000... Type: Secp256k1 Admin: true Expires: never Hook: none ``` The zero-hash entry is the root key (your EOA). It is always admin and never expires. *** ## Step 2: Register a secondary secp256k1 key[​](#step-2-register-a-secondary-secp256k1-key "Direct link to Step 2: Register a secondary secp256k1 key") Generate a new keypair and register it as a secondary signer. In practice this would be a session key, a co-signer address, or a key stored in a different device. index.ts ``` import { generatePrivateKey, privateKeyToAddress } from "viem/accounts"; // Generate a new keypair for the secondary signer. const secondaryPrivateKey = generatePrivateKey(); const secondaryAddress = privateKeyToAddress(secondaryPrivateKey); console.log("Registering secondary key:", secondaryAddress); // Build the key descriptor from the address. const newKey = Calibur7702Account.createSecp256k1Key(secondaryAddress); const newKeyHash = Calibur7702Account.getKeyHash(newKey); ``` `createSecp256k1Key` takes an Ethereum address and returns the key descriptor Calibur expects. `getKeyHash` computes the unique identifier used to reference the key later. Registration requires **two transactions** (register + update) that must be submitted in the same UserOperation. `createRegisterKeyMetaTransactions` returns both: index.ts ``` const registerTxs = Calibur7702Account.createRegisterKeyMetaTransactions( newKey, { // Key expires in 30 days expiration: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, } ); ``` Build the UserOperation, sponsor gas, sign with the admin (root) key, and send: index.ts ``` import { calculateUserOperationMaxGasCost } from "abstractionkit"; let registerOp = await smartAccount.createUserOperation( registerTxs, nodeUrl, bundlerUrl, ); const cost = calculateUserOperationMaxGasCost(registerOp); console.log("This UserOperation may cost up to: " + cost + " wei"); const { userOperation: sponsoredRegisterOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, registerOp, bundlerUrl, ); registerOp = sponsoredRegisterOp; // Must be signed by an admin key. The root EOA key is always admin. registerOp.signature = smartAccount.signUserOperation( registerOp, privateKey, chainId, ); console.log("Sending registration UserOperation..."); const registerResponse = await smartAccount.sendUserOperation(registerOp, bundlerUrl); const registerReceipt = await registerResponse.included(); if (registerReceipt.success) { console.log("Key registered! Tx:", registerReceipt.receipt.transactionHash); } else { console.log("Registration failed:", registerReceipt); process.exit(1); } // Verify the key is now registered on-chain. const isRegistered = await smartAccount.isKeyRegistered(nodeUrl, newKeyHash); console.log("Registered on-chain:", isRegistered); // true ``` Result example ``` Registering secondary key: 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B This UserOperation may cost up to: 712040000000000 wei Sending registration UserOperation... Key registered! Tx: 0x4a1b2c3d... Registered on-chain: true ``` *** ## Step 3: Sign a transaction with the secondary key[​](#step-3-sign-a-transaction-with-the-secondary-key "Direct link to Step 3: Sign a transaction with the secondary key") Non-admin keys can sign regular transactions independently. Pass the secondary key's private key and the `keyHash` option to `signUserOperation`: index.ts ``` import { getFunctionSelector, createCallData, MetaTransaction } from "abstractionkit"; const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintCallData = createCallData( getFunctionSelector("mint(address)"), ["address"], [eoaAddress], ); const mintTx: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintCallData }; let secondaryOp = await smartAccount.createUserOperation( [mintTx], nodeUrl, bundlerUrl, ); const { userOperation: sponsoredSecondaryOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, secondaryOp, bundlerUrl, ); secondaryOp = sponsoredSecondaryOp; // Sign with the secondary key's private key. // The keyHash option tells Calibur which registered key is signing. secondaryOp.signature = smartAccount.signUserOperation( secondaryOp, secondaryPrivateKey, chainId, { keyHash: newKeyHash }, ); console.log("Sending transaction signed by secondary key..."); const secondaryResponse = await smartAccount.sendUserOperation(secondaryOp, bundlerUrl); const secondaryReceipt = await secondaryResponse.included(); if (secondaryReceipt.success) { console.log("Transaction confirmed! Tx:", secondaryReceipt.receipt.transactionHash); } else { console.log("Transaction failed:", secondaryReceipt); } ``` The `keyHash` override wraps the signature so Calibur verifies it against the correct registered key rather than the root key. Result example ``` Sending transaction signed by secondary key... Transaction confirmed! Tx: 0x9f8e7d6c... ``` *** ## Step 4: Update key settings[​](#step-4-update-key-settings "Direct link to Step 4: Update key settings") An admin key can update the settings of any registered key at any time. This is useful for extending an expiration, changing the hook, or modifying other attributes: index.ts ``` const updateTx = Calibur7702Account.createUpdateKeySettingsMetaTransaction( newKeyHash, { // Extend expiration to 1 year from now expiration: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, } ); let updateOp = await smartAccount.createUserOperation( [updateTx], nodeUrl, bundlerUrl, ); const { userOperation: sponsoredUpdateOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, updateOp, bundlerUrl, ); updateOp = sponsoredUpdateOp; // Admin signature required. updateOp.signature = smartAccount.signUserOperation( updateOp, privateKey, chainId, ); const updateResponse = await smartAccount.sendUserOperation(updateOp, bundlerUrl); const updateReceipt = await updateResponse.included(); if (updateReceipt.success) { const updatedSettings = await smartAccount.getKeySettings(nodeUrl, newKeyHash); console.log( "Expiration updated to:", new Date(updatedSettings.expiration * 1000).toISOString() ); console.log("Tx:", updateReceipt.receipt.transactionHash); } else { console.log("Update failed:", updateReceipt); } ``` A non-admin key attempting this call will be rejected on-chain. Result example ``` Expiration updated to: 2027-03-28T12:00:00.000Z Tx: 0x1a2b3c4d... ``` *** ## Step 5: Revoke a key[​](#step-5-revoke-a-key "Direct link to Step 5: Revoke a key") Use `createRevokeKeyMetaTransaction` to permanently remove a key. Like register and update, revoke requires an admin key signature: index.ts ``` const revokeTx = Calibur7702Account.createRevokeKeyMetaTransaction(newKeyHash); let revokeOp = await smartAccount.createUserOperation( [revokeTx], nodeUrl, bundlerUrl, ); const { userOperation: sponsoredRevokeOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, revokeOp, bundlerUrl, ); revokeOp = sponsoredRevokeOp; // Admin signature required. revokeOp.signature = smartAccount.signUserOperation( revokeOp, privateKey, chainId, ); const revokeResponse = await smartAccount.sendUserOperation(revokeOp, bundlerUrl); const revokeReceipt = await revokeResponse.included(); if (revokeReceipt.success) { const stillRegistered = await smartAccount.isKeyRegistered(nodeUrl, newKeyHash); console.log("Key still registered:", stillRegistered); // false console.log("Tx:", revokeReceipt.receipt.transactionHash); } else { console.log("Revocation failed:", revokeReceipt); } ``` After revocation, `isKeyRegistered` returns `false` and any UserOperation signed with that key will be rejected. Result example ``` Key still registered: false Tx: 0x5e6f7a8b... ``` *** ## Full Example[​](#full-example "Direct link to Full Example") 03-manage-keys.ts ``` loading... ``` [See full example on GitHub]() ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore the [Calibur SDK reference](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md) for the full API * Add passkey authentication: [Passkey Authentication with Calibur](https://docs.candide.dev/wallet/guides/calibur-passkeys.md) --- # Passkey Authentication with Calibur Calibur supports WebAuthn passkeys (Face ID, fingerprint, security keys) as secondary signers. This guide shows how to register a passkey on a Calibur account and use it to sign a transaction. Key points to understand before starting: * Passkey registration must be signed by an admin key (the EOA root key), because only admin keys can call key management functions. * Once registered, the passkey can sign regular transactions independently without the root key. * Each registered key has its own settings: expiration time, hook, and admin flag. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Sepolia Bundler and Paymaster endpoints from the [Dashboard](https://dashboard.candide.dev) * Node and a package manager (yarn or npm) * An EOA already delegated to Calibur. Run the [Calibur quickstart](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) first if you haven't done this. Here's the [complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/02-passkeys.ts) for you to reference if you prefer to run it directly. *** ## Part 1: Register a Passkey[​](#part-1-register-a-passkey "Direct link to Part 1: Register a Passkey") ## Step 1: Setup and imports[​](#step-1-setup-and-imports "Direct link to Step 1: Setup and imports") 1. Install dependencies: ``` npm i abstractionkit dotenv ``` 2. Configure environment variables. Create a `.env` file: .env ``` CHAIN_ID=11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com BUNDLER_URL=https://api.candide.dev/public/v3/11155111 PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 PRIVATE_KEY=your_eoa_private_key_here ``` 3. Create your script file with the imports and initialization: index.ts ``` import * as crypto from "crypto"; import { Wallet } from "ethers"; import { toBytes, toHex } from "viem"; import { Calibur7702Account, CandidePaymaster, createAndSignEip7702DelegationAuthorization, WebAuthnSignatureData, } from "abstractionkit"; const chainId = BigInt(process.env.CHAIN_ID as string); const bundlerUrl = process.env.BUNDLER_URL as string; const paymasterUrl = process.env.PAYMASTER_URL as string; const nodeUrl = process.env.JSON_RPC_NODE_PROVIDER as string; const privateKey = process.env.PRIVATE_KEY as string; const eoaWallet = new Wallet(privateKey); const eoaAddress = eoaWallet.address; const smartAccount = new Calibur7702Account(eoaAddress); const paymaster = new CandidePaymaster(paymasterUrl); // Check if the EOA is already delegated to Calibur. // isDelegatedToThisAccount() returns true only if delegated to this account's delegateeAddress. const alreadyDelegated = await smartAccount.isDelegatedToThisAccount(nodeUrl); console.log("Already delegated:", alreadyDelegated); ``` ## Step 2: Create a WebAuthn credential[​](#step-2-create-a-webauthn-credential "Direct link to Step 2: Create a WebAuthn credential") Create a passkey credential. In a real browser application, this call triggers a biometric prompt (Face ID, fingerprint, or security key). The example below uses the browser's native `navigator.credentials` API shape. index.ts ``` // In a browser, navigator.credentials is available globally. // Call navigator.credentials.create() with your relying party details. const credential = await navigator.credentials.create({ publicKey: { rp: { name: "My Wallet", id: window.location.hostname }, user: { id: crypto.getRandomValues(new Uint8Array(32)), name: "user@example.com", displayName: "My Account", }, challenge: crypto.getRandomValues(new Uint8Array(32)), pubKeyCredParams: [{ type: "public-key", alg: -7 }], }, }); // Extract the P-256 public key coordinates from the credential. // x and y are the BigInt coordinates of the passkey's public key. const { x, y } = extractPublicKey(credential.response); console.log("Passkey public key:"); console.log(" x:", toHex(x)); console.log(" y:", toHex(y)); ``` The `extractPublicKey` function parses the CBOR-encoded attestation response and returns the `x` and `y` coordinates of the P-256 public key. These coordinates are what gets registered on-chain. ## Step 3: Build key registration transactions[​](#step-3-build-key-registration-transactions "Direct link to Step 3: Build key registration transactions") Use the public key coordinates to create a `CaliburKey` struct and compute its hash. Then build the registration transactions. index.ts ``` // createWebAuthnP256Key wraps the x, y coordinates into a CaliburKey struct. const webAuthnKey = Calibur7702Account.createWebAuthnP256Key(x, y); // getKeyHash computes the on-chain identifier for this key. const keyHash = Calibur7702Account.getKeyHash(webAuthnKey); console.log("Key hash:", keyHash); // createRegisterKeyMetaTransactions returns two MetaTransactions that // MUST be included in the same UserOperation: register() and update(). // Splitting them across separate UserOperations will revert. const registerTxs = Calibur7702Account.createRegisterKeyMetaTransactions( webAuthnKey, { // Key expires in 1 year. Set expiration to 0 for no expiration. expiration: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, } ); ``` The key is registered as non-admin by default. Non-admin keys can sign regular transactions but cannot call key management functions (register, revoke, or update settings). Only admin keys can perform those operations. ## Step 4: Create, sponsor, sign, and send the registration UserOperation[​](#step-4-create-sponsor-sign-and-send-the-registration-useroperation "Direct link to Step 4: Create, sponsor, sign, and send the registration UserOperation") Build the UserOperation from the registration transactions. If the EOA is not yet delegated to Calibur, include `eip7702Auth` to delegate in the same UserOperation. index.ts ``` let registerOp = await smartAccount.createUserOperation( registerTxs, nodeUrl, bundlerUrl, { // Include eip7702Auth only if not yet delegated. eip7702Auth: alreadyDelegated ? undefined : { chainId }, } ); // Sign the delegation authorization if this is the first UserOperation. if (!alreadyDelegated) { registerOp.eip7702Auth = createAndSignEip7702DelegationAuthorization( BigInt(registerOp.eip7702Auth.chainId), registerOp.eip7702Auth.address, BigInt(registerOp.eip7702Auth.nonce), privateKey, ); } // Sponsor gas with the paymaster. const { userOperation: sponsoredRegisterOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, registerOp, bundlerUrl, ); registerOp = sponsoredRegisterOp; // Sign with the root key (EOA private key). Only admin keys can register // new keys, so the root key must sign this UserOperation. registerOp.signature = smartAccount.signUserOperation( registerOp, privateKey, chainId, ); console.log( alreadyDelegated ? "Registering passkey..." : "Registering passkey and delegating EOA..." ); const registerResponse = await smartAccount.sendUserOperation(registerOp, bundlerUrl); const registerReceipt = await registerResponse.included(); if (!registerReceipt.success) { console.log("Registration failed:", registerReceipt); return; } console.log("Passkey registered! Tx:", registerReceipt.receipt.transactionHash); // Verify the key is registered on-chain. const isRegistered = await smartAccount.isKeyRegistered(nodeUrl, keyHash); console.log("Key registered on-chain:", isRegistered); const settings = await smartAccount.getKeySettings(nodeUrl, keyHash); console.log("Key settings:", { isAdmin: settings.isAdmin, expiration: new Date(settings.expiration * 1000).toISOString(), hook: settings.hook, }); ``` Result example ``` Passkey registered! Tx: 0x763dc353dee853da059b9e8c4b9997cccd4597b4cfcfc5fc3133dcffc778d93a Key registered on-chain: true Key settings: { isAdmin: false, expiration: '2027-03-28T00:00:00.000Z', hook: '0x0000000000000000000000000000000000000000' } ``` *** ## Part 2: Sign a Transaction with the Passkey[​](#part-2-sign-a-transaction-with-the-passkey "Direct link to Part 2: Sign a Transaction with the Passkey") ## Step 5: Create a UserOperation with a dummy WebAuthn signature[​](#step-5-create-a-useroperation-with-a-dummy-webauthn-signature "Direct link to Step 5: Create a UserOperation with a dummy WebAuthn signature") WebAuthn signatures are larger than ECDSA signatures, so the bundler needs an accurate dummy signature to estimate gas correctly. Use `createDummyWebAuthnSignature` and pass it as the `dummySignature` override. index.ts ``` import { getFunctionSelector, createCallData } from "abstractionkit"; const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSelector = getFunctionSelector("mint(address)"); const mintCallData = createCallData( mintFunctionSelector, ["address"], [eoaAddress], ); // Provide a dummy WebAuthn signature so the bundler can estimate gas accurately. // WebAuthn signatures are larger than ECDSA, and underestimating gas will cause // the UserOperation to revert on-chain. const dummyWebAuthnSig = Calibur7702Account.createDummyWebAuthnSignature(keyHash); let userOperation = await smartAccount.createUserOperation( [{ to: nftContractAddress, value: 0n, data: mintCallData }], nodeUrl, bundlerUrl, { dummySignature: dummyWebAuthnSig }, ); // Sponsor gas before signing. In ERC-4337 v0.8, paymaster data is included // in the UserOperation hash, so it must be set before the passkey signs. const { userOperation: sponsoredUserOperation } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, ); userOperation = sponsoredUserOperation; ``` ## Step 6: Get the UserOperation hash and sign with the passkey[​](#step-6-get-the-useroperation-hash-and-sign-with-the-passkey "Direct link to Step 6: Get the UserOperation hash and sign with the passkey") Compute the UserOperation hash and pass it as the WebAuthn challenge. In a browser, this triggers the biometric prompt for the user to sign. index.ts ``` // getUserOperationHash returns the hash that the passkey will sign. const userOpHash = smartAccount.getUserOperationHash(userOperation, chainId); // In a browser, navigator.credentials.get() triggers the biometric prompt. const assertion = await navigator.credentials.get({ publicKey: { challenge: toBytes(userOpHash as `0x${string}`), rpId: window.location.hostname, allowCredentials: [{ type: "public-key", id: new Uint8Array(credential.rawId), }], }, }); // Extract r and s from the DER-encoded signature in the assertion response. const { r, s } = extractSignature(assertion.response); const clientDataJSON = new TextDecoder().decode(assertion.response.clientDataJSON); // Build the WebAuthnSignatureData struct from the assertion fields. const webAuthnSignatureData: WebAuthnSignatureData = { authenticatorData: toHex(new Uint8Array(assertion.response.authenticatorData)), clientDataJSON, challengeIndex: BigInt(clientDataJSON.indexOf('"challenge"')), typeIndex: BigInt(clientDataJSON.indexOf('"type"')), r, s, }; // formatWebAuthnSignature encodes the signature into the Calibur format: // abi.encode(keyHash, webAuthnAuth, hookData) userOperation.signature = smartAccount.formatWebAuthnSignature( keyHash, webAuthnSignatureData, ); ``` `challengeIndex` and `typeIndex` are byte offsets within the `clientDataJSON` string. The Calibur on-chain verifier uses them to locate the `"challenge"` and `"type"` fields without parsing the full JSON. ## Step 7: Send the passkey-signed transaction[​](#step-7-send-the-passkey-signed-transaction "Direct link to Step 7: Send the passkey-signed transaction") Send the UserOperation and wait for on-chain inclusion. index.ts ``` console.log("Sending passkey-signed UserOperation..."); const response = await smartAccount.sendUserOperation(userOperation, bundlerUrl); console.log("UserOp hash:", response.userOperationHash); const receipt = await response.included(); if (receipt.success) { console.log("NFT minted with passkey signature!"); console.log("Transaction:", receipt.receipt.transactionHash); } else { console.log("UserOperation failed:", receipt); } ``` Result example ``` Sending passkey-signed UserOperation... UserOp hash: 0x89a5111d40c4ca45977a28419a08ca33e496a88e973bc995ec6a5a28da564cb5 NFT minted with passkey signature! Transaction: 0x763dc353dee853da059b9e8c4b9997cccd4597b4cfcfc5fc3133dcffc778d93a ``` You can look up the hash on explorers that support user operations like [Blockscout](https://www.blockscout.com/). ## Full Example[​](#full-example "Direct link to Full Example") 02-passkeys.ts ``` loading... ``` [See full example on GitHub]() ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore the [Calibur SDK reference](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md) for the full API * Manage registered keys and permissions: [03-manage-keys.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/03-manage-keys.ts) --- # Getting Started with Chain Abstraction **Add a new owner to your Safe on multiple chains with a single signature.** This guide walks you through the core value proposition of chain abstraction: instead of signing N times for N chains, you sign once. #### What You'll Build[​](#what-youll-build "Direct link to What You'll Build") By the end of this tutorial, you'll have: * Created a Safe that exists on two chains with the same address * Added a new owner to both Safes simultaneously * Signed only once for both operations #### Traditional vs Unified Approach[​](#traditional-vs-unified-approach "Direct link to Traditional vs Unified Approach") | Approach | Chains | Signatures Required | | --------------- | ------ | ------------------- | | Traditional | 5 | 5 signing sessions | | Unified Account | 5 | 1 signing session | ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, make sure you have: * **Node.js 18+** and npm/yarn * **Basic TypeScript knowledge** Complete Example You can [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/tree/main/chain-abstraction) and follow along. ## Step 1: Project Setup[​](#step-1-project-setup "Direct link to Step 1: Project Setup") #### Create Project Directory[​](#create-project-directory "Direct link to Create Project Directory") ``` mkdir chain-abstraction-demo cd chain-abstraction-demo npx tsc --init ``` #### Install Dependencies[​](#install-dependencies "Direct link to Install Dependencies") ``` npm install typescript ts-node --save-dev npm install abstractionkit dotenv viem ``` **What each package does:** * `abstractionkit` - Candide's SDK with multi-chain signature support * `dotenv` - Loads environment variables * `viem` - Account utilities for key generation #### Configure Environment Variables[​](#configure-environment-variables "Direct link to Configure Environment Variables") Create a `.env` file in your project root: .env ``` # Chain 1 (Sepolia) CHAIN_ID1=11155111 BUNDLER_URL1=https://api.candide.dev/public/v3/11155111 NODE_URL1=https://ethereum-sepolia-rpc.publicnode.com PAYMASTER_URL1=https://api.candide.dev/public/v3/11155111 # Chain 2 (Optimism Sepolia) CHAIN_ID2=11155420 BUNDLER_URL2=https://api.candide.dev/public/v3/11155420 NODE_URL2=https://sepolia.optimism.io PAYMASTER_URL2=https://api.candide.dev/public/v3/11155420 # Your keys (auto-generated if not provided) PRIVATE_KEY= PUBLIC_ADDRESS= ``` #### Create Main Script[​](#create-main-script "Direct link to Create Main Script") Create an `index.ts` file: index.ts ``` import * as dotenv from 'dotenv' async function main(): Promise { dotenv.config() // We'll build our multi-chain logic here } main().catch(console.error) ``` ## Step 2: Initialize the Multi-Chain Safe Account[​](#step-2-initialize-the-multi-chain-safe-account "Direct link to Step 2: Initialize the Multi-Chain Safe Account") `SafeMultiChainSigAccountV1` extends the standard Safe account with multi-chain signature capabilities. It uses a Merkle tree to batch multiple UserOperations across chains into a single signable root. index.ts ``` import * as dotenv from 'dotenv' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit" async function main(): Promise { dotenv.config() // Auto-generate keys if not provided (for quick testing) const ownerPrivateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as `0x${string}` const ownerAccount = privateKeyToAccount(ownerPrivateKey) const ownerPublicAddress = process.env.PUBLIC_ADDRESS || ownerAccount.address console.log("Owner:", ownerPublicAddress) // Initialize the multi-chain Safe account const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) console.log("Safe Account Address:", smartAccount.accountAddress) } main().catch(console.error) ``` #### Test Your Setup[​](#test-your-setup "Direct link to Test Your Setup") ``` npx ts-node index.ts ``` **Expected output:** ``` Owner: 0x... Safe Account Address: 0x... ``` How It Works `initializeNewAccount()` deterministically computes your smart account address locally using only the provided inputs. No network calls are required. The account contract is not deployed at this stage; deployment occurs automatically when you send your first transaction. ## Step 3: Create the Add Owner Transaction[​](#step-3-create-the-add-owner-transaction "Direct link to Step 3: Create the Add Owner Transaction") We'll add a new owner to the Safe. This same transaction will execute on both chains. index.ts ``` // Generate a new owner address to add const newOwnerAccount = privateKeyToAccount(generatePrivateKey()) const newOwnerAddress = newOwnerAccount.address console.log("New owner to add:", newOwnerAddress) // Create add owner transaction (works on any chain) const addOwnerTx = smartAccount.createStandardAddOwnerWithThresholdMetaTransaction( newOwnerAddress, 1 // threshold: any single owner can sign ) ``` ## Step 4: Create UserOperations for Both Chains[​](#step-4-create-useroperations-for-both-chains "Direct link to Step 4: Create UserOperations for Both Chains") Now we create a UserOperation for each chain and sponsor gas using the `CandidePaymaster`. The transaction is identical, but gas parameters differ per chain. index.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, CandidePaymaster, } from "abstractionkit" // Chain configuration from .env const chainId1 = BigInt(process.env.CHAIN_ID1 as string) const chainId2 = BigInt(process.env.CHAIN_ID2 as string) const bundlerUrl1 = process.env.BUNDLER_URL1 as string const bundlerUrl2 = process.env.BUNDLER_URL2 as string const nodeUrl1 = process.env.NODE_URL1 as string const nodeUrl2 = process.env.NODE_URL2 as string // Set up paymasters for gas sponsorship const paymaster1 = new CandidePaymaster(process.env.PAYMASTER_URL1 as string) const paymaster2 = new CandidePaymaster(process.env.PAYMASTER_URL2 as string) console.log("[1/4] Creating UserOperations for both chains...") // Create UserOperations in parallel let [userOperation1, userOperation2] = await Promise.all([ smartAccount.createUserOperation([addOwnerTx], nodeUrl1, bundlerUrl1), smartAccount.createUserOperation([addOwnerTx], nodeUrl2, bundlerUrl2), ]) ``` #### Commit Phase: Reserve Gas Sponsorship[​](#commit-phase-reserve-gas-sponsorship "Direct link to Commit Phase: Reserve Gas Sponsorship") Before signing, we request the paymaster to reserve gas sponsorship for both operations. This is the **commit** phase of the two-phase paymaster flow. index.ts ``` console.log("[2/4] Requesting paymaster sponsorship (commit phase)...") const commitContext = { signingPhase: "commit" as const } const commitOverrides = { preVerificationGasPercentageMultiplier: 20 } const [{ userOperation: commitOp1 }, { userOperation: commitOp2 }] = await Promise.all([ paymaster1.createSponsorPaymasterUserOperation( smartAccount, userOperation1, bundlerUrl1, undefined, commitContext, commitOverrides ), paymaster2.createSponsorPaymasterUserOperation( smartAccount, userOperation2, bundlerUrl2, undefined, commitContext, commitOverrides ), ]) userOperation1 = commitOp1 userOperation2 = commitOp2 ``` Two-Phase Paymaster The CandidePaymaster uses a two-phase flow for multichain operations. The **commit** phase (before signing) reserves gas sponsorship. The **finalize** phase (after signing) completes the sponsorship with the final signed operation. ## Step 5: Sign Once for All Chains[​](#step-5-sign-once-for-all-chains "Direct link to Step 5: Sign Once for All Chains") This is where the magic happens. Instead of signing each UserOperation separately, we sign them all at once. index.ts ``` console.log("[3/4] Signing for BOTH chains with ONE signature...") // Sign all UserOperations with a single signing session const signatures = smartAccount.signUserOperations( [ { userOperation: userOperation1, chainId: chainId1 }, { userOperation: userOperation2, chainId: chainId2 }, ], [ownerPrivateKey], ) // Attach signatures userOperation1.signature = signatures[0] userOperation2.signature = signatures[1] console.log(" Single signing operation generated", signatures.length, "signatures!") ``` How It Works `signUserOperations` builds a Merkle tree where each leaf is a UserOperation hash. You sign the Merkle root once. Each chain receives the signature plus a Merkle proof to verify independently. ## Step 6: Finalize and Submit[​](#step-6-finalize-and-submit "Direct link to Step 6: Finalize and Submit") After signing, finalize the paymaster sponsorship and submit both operations. #### Finalize Phase[​](#finalize-phase "Direct link to Finalize Phase") index.ts ``` console.log("[4/4] Finalizing sponsorship and submitting...") const finalizeContext = { signingPhase: "finalize" as const } const [{ userOperation: finalOp1 }, { userOperation: finalOp2 }] = await Promise.all([ paymaster1.createSponsorPaymasterUserOperation( smartAccount, userOperation1, bundlerUrl1, undefined, finalizeContext ), paymaster2.createSponsorPaymasterUserOperation( smartAccount, userOperation2, bundlerUrl2, undefined, finalizeContext ), ]) userOperation1 = finalOp1 userOperation2 = finalOp2 ``` #### Submit and Verify[​](#submit-and-verify "Direct link to Submit and Verify") index.ts ``` import { UserOperationV9 } from "abstractionkit" // Submit in parallel await Promise.all([ sendAndMonitor(userOperation1, bundlerUrl1, "Chain 1"), sendAndMonitor(userOperation2, bundlerUrl2, "Chain 2"), ]) // Verify owners on both chains console.log("\nVerifying owners on both chains...") const [owners1, owners2] = await Promise.all([ smartAccount.getOwners(nodeUrl1), smartAccount.getOwners(nodeUrl2), ]) console.log("\nOwners on Chain 1:", owners1) console.log("Owners on Chain 2:", owners2) const hasNewOwner1 = owners1.map(o => o.toLowerCase()).includes(newOwnerAddress.toLowerCase()) const hasNewOwner2 = owners2.map(o => o.toLowerCase()).includes(newOwnerAddress.toLowerCase()) if (hasNewOwner1 && hasNewOwner2) { console.log("\nNew owner successfully added on BOTH chains with ONE signature!") } // Helper function async function sendAndMonitor( userOperation: UserOperationV9, bundlerUrl: string, name: string, ): Promise { const account = new SafeAccount(userOperation.sender) const response = await account.sendUserOperation(userOperation, bundlerUrl) console.log(` [${name}] Submitted. Waiting...`) const receipt = await response.included() if (receipt.success) { console.log(` [${name}] Success! Tx: ${receipt.receipt.transactionHash}`) } else { console.log(` [${name}] Execution failed`) } } ``` #### Run the Complete Example[​](#run-the-complete-example "Direct link to Run the Complete Example") ``` npx ts-node index.ts ``` **Expected output:** ``` Owner: 0x... Safe Account Address: 0x... New owner to add: 0x... [1/4] Creating UserOperations for both chains... [2/4] Requesting paymaster sponsorship (commit phase)... [3/4] Signing for BOTH chains with ONE signature... Single signing operation generated 2 signatures! [4/4] Finalizing sponsorship and submitting... [Chain 1] Submitted. Waiting... [Chain 2] Submitted. Waiting... [Chain 1] Success! Tx: 0x... [Chain 2] Success! Tx: 0x... Owners on Chain 1: ['0x...', '0x...'] Owners on Chain 2: ['0x...', '0x...'] New owner successfully added on BOTH chains with ONE signature! ``` ## Full Example[​](#full-example "Direct link to Full Example") Complete add-owner.ts The complete script below ties all the steps together. You can also find it in the [abstractionkit-examples repo](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner.ts). add-owner.ts ``` loading... ``` [See full example on GitHub]() ## What's Next?[​](#whats-next "Direct link to What's Next?") Explore more chain abstraction examples and use cases in the [Overview](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md#examples), or check the [SDK Reference](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) for all available methods. --- # Chain Abstraction with Safe Unified Account *Sign once, execute on every chain.* Safe Unified Account is a Safe module that enables chain abstraction. Instead of signing separately on each chain, you sign once and that single signature authorizes execution across every chain you target. This works across any EVM chain where Safe is deployed: L2 rollups, sidechains like Gnosis Chain, and other L1s. ## Demo[​](#demo "Direct link to Demo") [Safe Unified Account: consolidate USDC/USDT across chains with one signature](https://www.youtube.com/embed/VF-hI0dvA2o?si=KHK1hRXJoJJ5zSCH) Try the live demo at **[unified.candide.dev](https://unified.candide.dev)**. ## Start Integrating[​](#start-integrating "Direct link to Start Integrating") Install the Candide skills so your AI agent knows how to wire up the Safe Unified Account. Source: [github.com/candidelabs/skills](https://github.com/candidelabs/skills). * Claude Code * Codex CLI * Other agents Run inside Claude Code: ``` /plugin marketplace add candidelabs/skills /plugin install candide@candide ``` Then ask: *"Integrate the Safe Unified Account."* ``` npx -y github:candidelabs/skills ``` Then ask Codex: *"Integrate the Safe Unified Account."* Paste the raw skill URL into Cursor, Windsurf, Copilot, or any chat-based agent: ``` https://raw.githubusercontent.com/candidelabs/skills/main/skills/safe-unified-account/SKILL.md ``` ## What You Can Do[​](#what-you-can-do "Direct link to What You Can Do") | Action | Without Chain Abstraction | With Safe Unified Account | | -------------------------------------- | --------------------------------- | --------------------------------------------------------------------------- | | Consolidate USDC/USDT to one chain | Bridge from each chain separately | 1 signature triggers transfers via CCTP or LayerZero from all source chains | | Replace owner on 5 chains | 5 signing sessions | 1 signature | | Add recovery guardian on 5 chains | 5 signing sessions | 1 signature | | Update multisig threshold on 5 chains | 5 signing sessions | 1 signature | | Deploy consistent config to new chains | Manual per-chain setup | 1 signature | ## Audits[​](#audits "Direct link to Audits") * [Audit Report by Nethermind (NM-0874)](https://github.com/candidelabs/safe-4337-multi-chain-signature-module/blob/main/audit/NM_0874_Candide_safe.pdf) * [Safe 4337 Multi-Chain Signature Module repository](https://github.com/candidelabs/safe-4337-multi-chain-signature-module) ## Examples[​](#examples "Direct link to Examples") | Example | Description | Code | | --------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | Add Owner | Add an owner across chains with one signature | [add-owner.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner.ts) | | Add Guardian | Sync recovery guardians across chains | [add-guardian.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-guardian.ts) | | Passkey Signing | Use WebAuthn/passkeys for multichain signing | [add-owner-passkey.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner-passkey.ts) | | EIP-712 Signing | Wallet-compatible signing for browser and hardware wallets | [add-owner-eip712-signed.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/chain-abstraction/add-owner-eip712-signed.ts) | ## Get Started[​](#get-started "Direct link to Get Started") Follow the [Getting Started guide](https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started.md) to add an owner across multiple chains with a single signature. For the full SDK method reference, see [Safe Unified Account SDK Reference](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md). *** *Technical questions? Reach out on [Discord](https://discord.gg/MfbK7aNWsY) or [GitHub](https://github.com/candidelabs)*. --- # Calibur Account Quickstart Calibur turns your EOA into a smart account via EIP-7702 delegation. After delegation, the address stays the same but gains batching, gas sponsorship, and key management capabilities including passkey authentication. This guide demonstrates how to upgrade your EOA to a Calibur smart account and batch two NFT mints in a single sponsored transaction. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Sepolia Bundler and Paymaster endpoints from the [Dashboard](https://dashboard.candide.dev) * Node and a package manager (yarn or npm) Here's the [complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/01-upgrade-eoa.ts) for you to reference if you prefer to run directly. ## Step 1: Get setup[​](#step-1-get-setup "Direct link to Step 1: Get setup") 1. Create a new directory for your project: ``` mkdir candide-calibur-quickstart cd candide-calibur-quickstart npx tsc --init ``` 2. Install dependencies ``` npm i typescript --save-dev npm i abstractionkit dotenv ``` 3. Configure Environment Variables * Create a `.env` file and add the following environment variables with your own values .env ``` CHAIN_ID=11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com BUNDLER_URL=https://api.candide.dev/public/v3/11155111 PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 PRIVATE_KEY=your_eoa_private_key_here ``` 4. Create an empty file and a function to run your script index.ts ``` async function main(): Promise { // Rest of the code will go here... } main(); ``` ## Step 2: Initialize Account and Paymaster[​](#step-2-initialize-account-and-paymaster "Direct link to Step 2: Initialize Account and Paymaster") Initialize the [Calibur7702Account](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md) using your EOA address. The account address stays the same after delegation. index.ts ``` import { Calibur7702Account, CandidePaymaster } from "abstractionkit"; const chainId = BigInt(process.env.CHAIN_ID as string); const bundlerUrl = process.env.BUNDLER_URL as string; const paymasterUrl = process.env.PAYMASTER_URL as string; const nodeUrl = process.env.JSON_RPC_NODE_PROVIDER as string; const privateKey = process.env.PRIVATE_KEY as string; // Derive the EOA public address from the private key import { Wallet } from "ethers"; const eoaWallet = new Wallet(privateKey); const eoaAddress = eoaWallet.address; // The account address is your EOA address. After delegation, it becomes // a smart account while keeping the same address. const smartAccount = new Calibur7702Account(eoaAddress); console.log("Smart account address: " + smartAccount.accountAddress); // Check if the EOA is already delegated to Calibur const alreadyDelegated = await smartAccount.isDelegatedToThisAccount(nodeUrl); // CandidePaymaster sponsors gas so the EOA doesn't need native tokens. const paymaster = new CandidePaymaster(paymasterUrl); ``` Run the code to verify the account address: Terminal ``` npx ts-node index.ts ``` Example output: ``` Smart account address: 0x32afdcfa1e3bfe70d03ecb55b5c8045c26515c9d ``` ## Step 3: Build Transactions[​](#step-3-build-transactions "Direct link to Step 3: Build Transactions") Batch two NFT mints into a single UserOperation to demonstrate the smart account's batching capability. index.ts ``` import { getFunctionSelector, createCallData, MetaTransaction, } from "abstractionkit"; const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSelector = getFunctionSelector('mint(address)'); const mintCallData = createCallData( mintFunctionSelector, ["address"], [eoaAddress], ); const mintNft1: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintCallData }; const mintNft2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintCallData }; ``` ## Step 4: Create UserOperation with EIP-7702 Delegation[​](#step-4-create-useroperation-with-eip-7702-delegation "Direct link to Step 4: Create UserOperation with EIP-7702 Delegation") Call `createUserOperation` to build the unsigned UserOperation. Pass `eip7702Auth` on the first delegation. If the EOA is already delegated, skip the authorization by passing `undefined`. index.ts ``` import { calculateUserOperationMaxGasCost } from "abstractionkit"; // eip7702Auth tells the bundler to include a delegation authorization // in the transaction. Only needed for the first UserOperation. let userOperation = await smartAccount.createUserOperation( [mintNft1, mintNft2], nodeUrl, bundlerUrl, { eip7702Auth: alreadyDelegated ? undefined : { chainId }, }, ); const cost = calculateUserOperationMaxGasCost(userOperation); console.log("This UserOperation may cost up to: " + cost + " wei"); ``` ## Step 5: Sign the Delegation Authorization[​](#step-5-sign-the-delegation-authorization "Direct link to Step 5: Sign the Delegation Authorization") Sign the `eip7702Auth` tuple to authorize the Calibur singleton at your EOA address. This is only required during the first upgrade transaction. index.ts ``` import { createAndSignEip7702DelegationAuthorization } from "abstractionkit"; // Option A: Pass private key string directly if (!alreadyDelegated) { userOperation.eip7702Auth = createAndSignEip7702DelegationAuthorization( BigInt(userOperation.eip7702Auth.chainId), userOperation.eip7702Auth.address, BigInt(userOperation.eip7702Auth.nonce), privateKey, ); } // Option B: Use a viem signer callback (private key never leaves the client) // import { privateKeyToAccount } from "viem/accounts"; // const viemAccount = privateKeyToAccount(privateKey as `0x${string}`); // if (!alreadyDelegated) { // userOperation.eip7702Auth = await createAndSignEip7702DelegationAuthorization( // BigInt(userOperation.eip7702Auth.chainId), // userOperation.eip7702Auth.address, // BigInt(userOperation.eip7702Auth.nonce), // async (hash) => viemAccount.sign({ hash: hash as `0x${string}` }), // ); // } ``` ## Step 6: Sponsor Gas[​](#step-6-sponsor-gas "Direct link to Step 6: Sponsor Gas") Optionally [sponsor gas](https://docs.candide.dev/wallet/guides/send-gasless-eip-7702.md) for your user. In ERC-4337 v0.8, paymaster data is included in the UserOperation hash, so it must be set before signing. index.ts ``` const { userOperation: sponsoredUserOperation } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, ); userOperation = sponsoredUserOperation; ``` ## Step 7: Sign the UserOperation[​](#step-7-sign-the-useroperation "Direct link to Step 7: Sign the UserOperation") Sign the UserOperation with the EOA's root key. index.ts ``` // Option A: Pass private key string userOperation.signature = smartAccount.signUserOperation( userOperation, privateKey, chainId, ); // Option B: Use a viem account via fromViem adapter // import { fromViem } from "abstractionkit"; // userOperation.signature = await smartAccount.signUserOperationWithSigner( // userOperation, // fromViem(viemAccount), // chainId, // ); ``` ## Step 8: Send and Wait for Inclusion[​](#step-8-send-and-wait-for-inclusion "Direct link to Step 8: Send and Wait for Inclusion") Send the UserOperation to the bundler and wait for on-chain inclusion. index.ts ``` console.log("Sending sponsored UserOperation..."); const response = await smartAccount.sendUserOperation(userOperation, bundlerUrl); console.log("UserOp hash:", response.userOperationHash); const receipt = await response.included(); if (receipt.success) { if (!alreadyDelegated) { console.log("EOA upgraded to Calibur smart account!"); } console.log("Minted 2 NFTs in a single batched UserOperation!"); console.log("Gas was sponsored by CandidePaymaster."); console.log("Transaction:", receipt.receipt.transactionHash); } else { console.log("UserOperation execution failed"); console.log(receipt); } ``` Run the script: Terminal ``` npx ts-node index.ts ``` Example output: ``` Sending sponsored UserOperation... UserOp hash: 0x89a5111d40c4ca45977a28419a08ca33e496a88e973bc995ec6a5a28da564cb5 EOA upgraded to Calibur smart account! Minted 2 NFTs in a single batched UserOperation! Gas was sponsored by CandidePaymaster. Transaction: 0x763dc353dee853da059b9e8c4b9997cccd4597b4cfcfc5fc3133dcffc778d93a ``` You can look up the hash on explorers that support user operations like [Blockscout](https://www.blockscout.com/). ## Full Example[​](#full-example "Direct link to Full Example") 01-upgrade-eoa.ts ``` loading... ``` [See full example on GitHub]() ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore the [Calibur SDK reference](https://docs.candide.dev/wallet/abstractionkit/calibur-account.md) for the full API * Add passkey authentication: [02-passkeys.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/02-passkeys.ts) * Manage keys and permissions: [03-manage-keys.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/calibur-account/03-manage-keys.ts) --- # EIP-7702 Quickstart EIP-7702 enables Ethereum Externally Owned Accounts (EOAs) to authorize smart contract code at their address, unlocking: * **Gas Sponsorship**: Abstract gas fees through third-party sponsorship or ERC-20 token payments * **Transaction Batching**: Improve UX and security by combining approvals and contract interactions atomically * **Granular Permissions**: Grant specific, limited access to third parties for use cases like recurring payments * **Forward Compatibility**: Maintains compatibility with ERC-4337 and future native account abstraction Learn more in the dedicated [7702 Overview](https://docs.candide.dev/account-abstraction/7702/overview.md). This guide demonstrates how to upgrade your EOA to a Smart Account for batching and gas sponsorship capabilities. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Sepolia Bundler and Paymaster endpoints from the [Dashboard](https://dashboard.candide.dev) * Node and a package manager (yarn or npm) Here's the [complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/01-upgrade-eoa.ts) for you to reference if you prefer to run directly. ## Step 1: Get setup[​](#step-1-get-setup "Direct link to Step 1: Get setup") 1. Create a new directory for your project: ``` mkdir candide-eip7702-upgrade-eoa cd candide-eip7702-upgrade-eoa npx tsc --init ``` 2. Install dependencies ``` npm i typescript --save-dev npm i abstractionkit dotenv ethers ``` 3. Configure Environment Variables * Create a `.env` file and add the following environment variables with your own values .env ``` CHAIN_ID=11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com BUNDLER_URL=https://api.candide.dev/public/v3/11155111 PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 ``` 4. Create an empty file and a function to run your script index.ts ``` async function main(): Promise { // Rest of the code will go here... } main(); ``` ## Step 2: Prepare Smart Account Instance[​](#step-2-prepare-smart-account-instance "Direct link to Step 2: Prepare Smart Account Instance") Prepare the smart account instance using [Simple7702AccountV09](https://docs.candide.dev/wallet/abstractionkit/simple-7702-account-v09.md), a fully audited minimalist smart contract account safely authorized by any EOA. It provides full support for smart account features including batching and gas sponsorship. index.ts ``` import { Simple7702AccountV09 } from "abstractionkit"; import { Wallet } from "ethers"; // For demo: generate a random EOA. In production, use user's EOA/private key const eoaDelegator = Wallet.createRandom(); const smartAccount = new Simple7702AccountV09(eoaDelegator.address); console.log("Smart account address (sender): " + smartAccount.accountAddress); ``` Let's run this code Terminal ``` npx ts-node index.ts ``` If everything worked, you will see the computed smart account address in the console, which is the same as the EOA address. Example output: ``` Account address(sender): 0x32afdcfa1e3bfe70d03ecb55b5c8045c26515c9d ``` ## Step 3: Generate callData for Minting NFTs[​](#step-3-generate-calldata-for-minting-nfts "Direct link to Step 3: Generate callData for Minting NFTs") Beyond upgrading the EOA to a smart account, this example demonstrates executing two NFT mints in a single transaction. index.ts ``` import { MetaTransaction, getFunctionSelector, createCallData, } from "abstractionkit"; // We will be minting two random NFTs in a single tx const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] ); const transaction1: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } const transaction2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } ``` ## Step 4: Create UserOperation[​](#step-4-create-useroperation "Direct link to Step 4: Create UserOperation") Now the fun part. Call `createUserOperation`, which will: 1. Prepare the `eip7702Auth` tuple for signature authorization. This is used during the authorization transaction of the EOA. 2. Determine the nonce and fetch the gas prices from the provided node RPC 3. Estimate gas limits from the provided bundler This returns an unsigned user operation. Use `calculateUserOperationMaxGasCost` to calculate its cost. index.ts ``` import { // ... calculateUserOperationMaxGasCost } from "abstractionkit"; const bundlerUrl = process.env.BUNDLER_URL as string; const chainId = BigInt(process.env.CHAIN_ID as string); const eoaDelegatorPrivateKey = process.env.PRIVATE_KEY as string; const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string; let userOperation = await smartAccount.createUserOperation( [transaction1, transaction2], jsonRpcNodeProvider, bundlerUrl, { eip7702Auth:{ chainId, // chainId at which the account will be authorized } } ); const cost = calculateUserOperationMaxGasCost(userOperation) console.log("This useroperation may cost up to : " + cost + " wei") console.log("Please fund the sender account : " + userOperation.sender +" with more than " + cost + " wei") ``` ## Step 5: Sign the Authorization[​](#step-5-sign-the-authorization "Direct link to Step 5: Sign the Authorization") Sign the `eip7702Auth` tuple to authorize the smart account code at your EOA address. This is only needed during the first upgrade transaction. If the EOA is already delegated, `eip7702Auth` will be `null` and signing can be skipped. ``` import { createAndSignEip7702DelegationAuthorization } from "abstractionkit" if (userOperation.eip7702Auth) { userOperation.eip7702Auth = createAndSignEip7702DelegationAuthorization( BigInt(userOperation.eip7702Auth.chainId), userOperation.eip7702Auth.address, BigInt(userOperation.eip7702Auth.nonce), eoaDelegatorPrivateKey ) } ``` ## Step 6: Get Paymaster data (Optional)[​](#step-6-get-paymaster-data-optional "Direct link to Step 6: Get Paymaster data (Optional)") Optionally [sponsor gas](https://docs.candide.dev/wallet/guides/send-gasless-eip-7702.md) for your user transaction or offer them to pay [gas in erc-20 tokens](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20-eip-7702.md). ``` import { Erc7677Paymaster } from "abstractionkit"; const paymasterUrl = process.env.PAYMASTER_URL as string; const paymaster = new Erc7677Paymaster(paymasterUrl) const { userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, ); userOperation = paymasterUserOperation; ``` ## Step 7: Sign and Submit[​](#step-7-sign-and-submit "Direct link to Step 7: Sign and Submit") 1. Call `signUserOperation`, which will create a signature for the private key provided of the owner of the EOA. index.ts ``` const privateKey = process.env.PRIVATE_KEY as string; userOperation.signature = smartAccount.signUserOperation( userOperation, privateKey, chainId, ); ``` 2. Use the Bundler URL to send the user operation to the bundler with `sendUserOperation`, and await the return `SendUseroperationResponse` object to confirm the on-chain inclusion of the user operation. index.ts ``` const sendUserOperationResponse = await smartAccount.sendUserOperation(userOperation, bundlerUrl) console.log("UserOperation sent. Waiting to be included ......") ``` 3. Track the userOperation and wait for its inclusion onchain ``` let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("Useroperation receipt received.") console.log(userOperationReceiptResult) if (userOperationReceiptResult.success) { console.log("EOA upgraded to a Smart Account and minted two NFTs! The transaction hash is : " + userOperationReceiptResult.receipt.transactionHash) } else { console.log("Useroperation execution failed") } ``` Now let's run this code again Terminal ``` npx ts-node index.ts ``` You've now enabled smart account features for your EOA! If everything went well, you should see the bundler returning a user operation receipt. Example output: ``` Useroperation sent. Waiting to be included ...... Useroperation receipt received. { userOpHash: '0x89a5111d40c4ca45977a28419a08ca33e496a88e973bc995ec6a5a28da564cb5', entryPoint: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', sender: '0xbdbc5fbc9ca8c3f514d073ec3de840ac84fc6d31', nonce: 0n, paymaster: '0x0000000000000000000000000000000000000000', actualGasCost: 243581295447n, actualGasUsed: 84429n, success: true, // ... receipt: { // ... transactionHash: '0x763dc353dee853da059b9e8c4b9997cccd4597b4cfcfc5fc3133dcffc778d93a', // ... } } EOA upgraded to a Smart Account and minted two NFTs! The transaction hash is : 0x763dc353dee853da059b9e8c4b9997cccd4597b4cfcfc5fc3133dcffc778d93a ``` You can look up the hash on explorers that support user operations like [Blockscout](https://www.blockscout.com/). ## Full Example[​](#full-example "Direct link to Full Example") 01-upgrade-eoa.ts ``` loading... ``` [See full example on GitHub]() --- # Your First Gasless Smart Account Transaction **Create a Smart Account and send gasless batched transactions in under 10 minutes.** This guide walks you through creating your first smart account and sending multiple gasless transactions in a single UserOperation. Learn the core concepts of account abstraction while building a practical example that requires no ETH for gas fees. #### What You'll Build[​](#what-youll-build "Direct link to What You'll Build") By the end of this tutorial, you'll have: * Created a smart contract wallet (smart account) * Batched two NFT minting transactions into one operation * Sent your first gasless UserOperation using a paymaster #### What is a UserOperation?[​](#what-is-a-useroperation "Direct link to What is a UserOperation?") A UserOperation is the mechanism smart accounts use to interact with Ethereum. Unlike regular transactions sent directly to the network, UserOperations offer: * **Batching**: Group multiple transactions into a single atomic operation * **Gas Flexibility**: Sponsor transactions or pay with any ERC-20 token * **Programmability**: Include custom validation and execution logic ## Quickstart[​](#quickstart "Direct link to Quickstart") [YouTube video player](https://www.youtube-nocookie.com/embed/t1ycwhN-Lvw?si=OMAiB7lK50XMkF68) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, make sure you have: * **Node.js 18+** and npm/yarn * **Basic TypeScript knowledge** - We'll explain the smart account concepts Complete Example You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/sponsor-gas/sponsor-gas.ts) and follow along. ## Step 1: Project Setup[​](#step-1-project-setup "Direct link to Step 1: Project Setup") #### Create Project Directory[​](#create-project-directory "Direct link to Create Project Directory") ``` mkdir candide-smart-account cd candide-smart-account npx tsc --init ``` #### Install Dependencies[​](#install-dependencies "Direct link to Install Dependencies") ``` npm install typescript --save-dev npm install abstractionkit dotenv ``` **What each package does:** * `abstractionkit` - Candide's SDK for smart account operations * `dotenv` - Loads environment variables from .env file #### Configure Environment Variables[​](#configure-environment-variables "Direct link to Configure Environment Variables") Create a `.env` file in your project root: .env ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 SPONSORSHIP_POLICY_ID= # optional PRIVATE_KEY=YOUR_PRIVATE_KEY_HERE PUBLIC_ADDRESS=YOUR_PUBLIC_ADDRESS_HERE ``` #### Create Main Script[​](#create-main-script "Direct link to Create Main Script") Create an `index.ts` file: index.ts ``` async function main(): Promise { // We'll build our smart account logic here } main().catch(console.error); ``` ## Step 2: Create Your Smart Account[​](#step-2-create-your-smart-account "Direct link to Step 2: Create Your Smart Account") #### Understanding Smart Accounts[​](#understanding-smart-accounts "Direct link to Understanding Smart Accounts") Smart accounts are smart contracts. Unlike regular wallets (EOAs), they can: * Execute multiple transactions atomically * Implement custom validation logic * Support multiple signers or alternative signature schemes This guide uses the Safe Unified Account (`SafeMultiChainSigAccountV1`), which works for single-chain transactions and multichain flows with the same account class. See [Safe Account](https://docs.candide.dev/wallet/abstractionkit/safe-account.md) for the account versions and how the `SafeAccount` alias maps to the real class. #### Generate Account Address[​](#generate-account-address "Direct link to Generate Account Address") index.ts ``` import * as dotenv from "dotenv"; // SafeMultiChainSigAccountV1 is the Safe Unified Account, aliased to SafeAccount for brevity. import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; async function main(): Promise { // Load environment variables dotenv.config(); const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string; // Create a new smart account controlled by your EOA const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Smart Account Address:", smartAccount.accountAddress); } main().catch(console.error); ``` #### Test Your Setup[​](#test-your-setup "Direct link to Test Your Setup") Run the code to generate your smart account address: ``` npx ts-node index.ts ``` **Expected output:** ``` Smart Account Address: 0x32afdcfa1e3bfe70d03ecb55b5c8045c26515c9d ``` How It Works The `initializeNewAccount()` method deterministically computes your smart account address locally using only the provided inputs—no network calls required. The account contract is not deployed at this stage; deployment occurs automatically when you send your first transaction. ## Step 3: Prepare Your Transactions[​](#step-3-prepare-your-transactions "Direct link to Step 3: Prepare Your Transactions") #### What We're Building[​](#what-were-building "Direct link to What We're Building") We'll create two NFT minting transactions and batch them together, demonstrating smart accounts' ability to execute multiple operations atomically. #### Understanding Transaction Data[​](#understanding-transaction-data "Direct link to Understanding Transaction Data") Every transaction needs three things: * `to`: The contract address to interact with * `value`: ETH amount to send (0 for most contract calls) * `data`: Encoded function call data #### Build the Transactions[​](#build-the-transactions "Direct link to Build the Transactions") Add this to your `index.ts` (after the account creation code): index.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, getFunctionSelector, createCallData, } from "abstractionkit"; // Inside your main() function, after creating smartAccount: // NFT contract we'll interact with (a test contract on Sepolia) const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; // Prepare the function call data const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] // Mint NFT to our smart account ); // Create two identical transactions (we'll mint 2 NFTs) const transaction1: MetaTransaction = { to: nftContractAddress, value: 0n, // No ETH needed for minting data: mintTransactionCallData, }; const transaction2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, }; console.log("Prepared 2 NFT minting transactions"); ``` Why Batch Transactions? Instead of sending two separate transactions, batch them into one UserOperation for a seamless one-click user experience. ## Step 4: Create UserOperation[​](#step-4-create-useroperation "Direct link to Step 4: Create UserOperation") index.ts ``` const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string; const bundlerUrl = process.env.BUNDLER_URL as string; // Create the UserOperation (batching both transactions) let userOperation = await smartAccount.createUserOperation( [transaction1, transaction2], // Batch both NFT mints together jsonRpcNodeProvider, // Used for nonce and gas price data bundlerUrl // Used for gas limit estimation ); ``` ## Step 5: Get Paymaster Data[​](#step-5-get-paymaster-data "Direct link to Step 5: Get Paymaster Data") We'll use a paymaster to sponsor the gas fees, making this transaction completely gasless for the user. index.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, getFunctionSelector, createCallData, Erc7677Paymaster, } from "abstractionkit"; const paymasterUrl = process.env.PAYMASTER_URL as string; const paymaster = new Erc7677Paymaster(paymasterUrl); const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID; const context = sponsorshipPolicyId ? { sponsorshipPolicyId, policyId: sponsorshipPolicyId } : {}; const { userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, context ) userOperation = paymasterUserOperation; ``` ## Step 6: Sign and Submit[​](#step-6-sign-and-submit "Direct link to Step 6: Sign and Submit") #### Sign the UserOperation[​](#sign-the-useroperation "Direct link to Sign the UserOperation") Your smart account needs to be authorized by your EOA. Add this signing code: index.ts ``` const chainId = BigInt(process.env.CHAIN_ID as string); const privateKey = process.env.PRIVATE_KEY as string; // Sign the UserOperation with your private key userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey], // Array because Safe supports multiple signers chainId, ); console.log("UserOperation signed successfully"); ``` #### Submit to Network[​](#submit-to-network "Direct link to Submit to Network") Now send your UserOperation to the bundler: index.ts ``` // Submit the UserOperation const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ); console.log("UserOperation submitted! Waiting for confirmation..."); ``` #### Wait for Confirmation[​](#wait-for-confirmation "Direct link to Wait for Confirmation") Track your UserOperation until it's included in a block: index.ts ``` let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("UserOperation receipt received.") console.log(userOperationReceiptResult) if (userOperationReceiptResult.success) { console.log("Two NFTs were minted. The transaction hash is : " + userOperationReceiptResult.receipt.transactionHash) } else { console.log("UserOperation execution failed") } ``` #### Run Your Complete Code[​](#run-your-complete-code "Direct link to Run Your Complete Code") Execute your smart account transaction: ``` npx ts-node index.ts ``` **Expected output:** Example Receipt Result ``` Useroperation sent. Waiting to be included ...... Useroperation receipt received. { userOpHash: '0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514', entryPoint: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', sender: '0xb8741a449d50ed0dcfe395287f85be152884c8d9', nonce: 0n, paymaster: '0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020', actualGasCost: 8078496n, actualGasUsed: 504906n, success: true, logs: '[{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', receipt: { blockHash: '0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c', blockNumber: 10419807n, from: '0x3cfdc212769c890907bce93d3d8c2c53de6a7a89', cumulativeGasUsed: 6978990n, gasUsed: 507053n, logs: '[{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x90","removed":false,"topics":["0xecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f8440","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ecd4ec46d4d2a6b64fe960b3d64e8b94b2234eb000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bdbc5fbc9ca8c3f514d073ec3de840ac84fc6d31","logIndex":"0x91","removed":false,"topics":["0x141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a8","0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762","logIndex":"0x92","removed":false,"topics":["0x4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec670000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020","logIndex":"0x93","removed":false,"topics":["0xd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x94","removed":false,"topics":["0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b403700000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b526000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001048d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b200d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b00d9de104e3386d9a45a61bce269c43e48b534e4e7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041249c58b000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x95","removed":false,"topics":["0xb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x96","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000056"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xd9de104e3386d9a45a61bce269c43e48b534e4e7","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x97","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000057"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0xb8741a449d50ed0dcfe395287f85be152884c8d9","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x","logIndex":"0x98","removed":false,"topics":["0x6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb8","0x000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b4037"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x3fe285dcd76bcce4ac92d38a6f2f8e964041e020","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x99","removed":false,"topics":["0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"},{"address":"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789","blockHash":"0xe19e52b4c222c1cdbc765f1a4e196ff4bf40c5550926e02974570e1845e88e2c","blockNumber":"0x9efe5f","data":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000007b44a0000000000000000000000000000000000000000000000000000000000007b44a","logIndex":"0x9a","removed":false,"topics":["0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f","0x1acede61123ab7116eb29c797aeaec3c03615c37732ba66428524aebdb4b4514","0x000000000000000000000000b8741a449d50ed0dcfe395287f85be152884c8d9","0x0000000000000000000000003fe285dcd76bcce4ac92d38a6f2f8e964041e020"],"transactionHash":"0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa","transactionIndex":"0x1d"}]', logsBloom: '0x0000040000009000000000000000010080000000000000000000000000000000000800000000000000020001000004040010000000000000800002000000000000001000000000000000000c0002000000000000010000080040000000000000020000000a0000000500002000000800008000000100000000000014000000000800010020000200008000000040000000000200000400000000000000000000000004000000000000500000000004000210000000000000000002001000000020200082000000000001000008000000000000002060000000100000000026000000082000010000000000000008100220000000000000000000000010000200', transactionHash: '0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa', transactionIndex: 29n, effectiveGasPrice: 16n } } Two Nfts were minted. The transaction hash is : 0xf43576a07f39660737a342c99a187eb70ac59d89bc0df92ff3c1bb8a8da370aa ``` 🎉 **Congratulations!** You've successfully sent your first batched gasless smart account transaction. ## Full Example[​](#full-example "Direct link to Full Example") Below is the complete example code that you can copy directly to implement the functionality described in the tutorial. index.js ``` import * as dotenv from 'dotenv' import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, Erc7677Paymaster, getFunctionSelector, createCallData, } from "abstractionkit"; async function main(): Promise { //get values from .env dotenv.config() const chainId = BigInt(process.env.CHAIN_ID as string) const bundlerUrl = process.env.BUNDLER_URL as string const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const paymasterURL = process.env.PAYMASTER_URL as string const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const ownerPrivateKey = process.env.PRIVATE_KEY as string //initializeNewAccount only needed when the smart account //have not been deployed yet for its first useroperation. //You can store the accountAddress to use it to initialize //the SafeAccount object for the following useroperations let smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress], ) //After the account contract is deployed, no need to call initializeNewAccount //let smartAccount = new SafeAccount(accountAddress) console.log("Account address(sender) : " + smartAccount.accountAddress) //create two meta transaction to mint two NFTs //you can use favorite method (like ethers.js) to construct the call data const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] ); const transaction1 :MetaTransaction ={ to: nftContractAddress, value: 0n, data: mintTransactionCallData, } const transaction2 :MetaTransaction ={ to: nftContractAddress, value: 0n, data: mintTransactionCallData, } //createUserOperation will determine the nonce, fetch the gas prices, //estimate gas limits and return a useroperation to be signed. //you can override all these values using the overrides parameter. let userOperation = await smartAccount.createUserOperation( [ //You can batch multiple transactions to be executed in one useroperation. transaction1, transaction2, ], jsonRpcNodeProvider, //the node rpc is used to fetch the current nonce and fetch gas prices. bundlerUrl, //the bundler rpc is used to estimate the gas limits. ) // Get paymaster data to sponsor the transaction const paymasterUrl = process.env.PAYMASTER_URL as string; const paymaster = new Erc7677Paymaster(paymasterUrl); const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID; const context = sponsorshipPolicyId ? { sponsorshipPolicyId, policyId: sponsorshipPolicyId } : {}; const { userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, context ) userOperation = paymasterUserOperation; console.log("Transaction will be sponsored - no ETH required!"); //Safe is a multisig that can have multiple owners/signers //signUserOperation will create a signature for the provided //privateKeys userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], chainId ) console.log(userOperation) //use the bundler rpc to send a userOperation //sendUserOperation will return a SendUseroperationResponse object //that can be awaited for the useroperation to be included onchain const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ) console.log("Useroperation sent. Waiting to be included ......") //included will return a UserOperationReceiptResult when //useroperation is included onchain let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("Useroperation receipt received.") console.log(userOperationReceiptResult) if(userOperationReceiptResult.success){ console.log("Two Nfts were minted. The transaction hash is : " + userOperationReceiptResult.receipt.transactionHash) }else{ console.log("Useroperation execution failed") } } main() ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") **"Insufficient funds" error** * Make sure your smart account address has enough Sepolia ETH, or are using a paymaster to sponsor gas fees **"Invalid signature" error** * Verify your `PRIVATE_KEY` matches your `PUBLIC_ADDRESS` * Ensure you're using the correct chain ID **Network/timeout errors** * Try a different node endpoint if the current one is down * Check your internet connection For runnable examples that classify and recover from common failures (underfunded paymaster, insufficient token balance, sponsor denial, on-chain reverts, and gas-too-low retries), see the [error-handling examples](https://github.com/candidelabs/abstractionkit-examples/tree/main/error-handling). #### Getting Help[​](#getting-help "Direct link to Getting Help") If you're still stuck: * Ask questions in abstractionkit's [GitHub Discussions](https://github.com/candidelabs/abstractionkit/discussions) * Join our [Discord](https://discord.gg/7R4g9XcxDQ) * Check the [complete example code](https://github.com/candidelabs/abstractionkit-examples/blob/main/batch-transactions/batch-transactions.ts) You can view your transaction on block explorers that support UserOperations like [Blockscout](https://www.blockscout.com/). ## What's Next?[​](#whats-next "Direct link to What's Next?") Now that you've mastered the basics, explore more advanced smart account features: #### Recommended Next Steps[​](#recommended-next-steps "Direct link to Recommended Next Steps") 1. **[Pay Gas in ERC-20](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md)** - Let users pay fees with USDC or other tokens 2. **[Passkeys](https://docs.candide.dev/wallet/plugins/passkeys.md)** - Let users secure their account with biometrics login 3. **[Enable Account Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md)** - Let users add recovery methods through guardians or traditional sms/email --- info Looking for a simpler approach? Candide offers a managed [Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md) service that handles key management for you. # Add a Social Account as a Recovery Method using Magic [Magic](https://magic.link) is a popular hosted wallet provider for EOAs, enabling users to log in using authentication methods like Social Logins and Email OTP. Leverage the full potential of Account Abstraction by combining Magic with AbstractionKit to enable email/social recovery experiences while using a Smart Account to sponsor gas, batch transactions, and more. Below are some relevant links that provide additional information and resources which you might find helpful as you go through this guide. * [How on-chain guardian recovery works](https://docs.candide.dev/wallet/plugins/recovery-with-guardians/) * [Guardian Recovery SDK Reference](https://docs.candide.dev/blog/making-accounts-recoverable/) * [Simple code example on GitHub](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts) * [Magic Documentation Website](https://magic.link/docs) ## Installation[​](#installation "Direct link to Installation") ### Install required dependencies[​](#install-required-dependencies "Direct link to Install required dependencies") * ethers * viem ``` npm i abstractionkit && magic-sdk ``` ``` npm i abstractionkit && magic-sdk && viem ``` ### Create a Magic account[​](#create-a-magic-account "Direct link to Create a Magic account") Create an account and get the API key on [Magic's Dashboard](https://dashboard.magic.link). You will need the PUBLISHABLE API KEY. ### Configure .env file[​](#configure-env-file "Direct link to Configure .env file") Configure the values you created from Magic dashboard in an .env file ``` // magic PUBLISHABLE_API_KEY= // candide BUNDLER_URL= JSON_RPC_NODE_PROVIDER= OWNER_PUBLIC_ADDRESS= NEW_OWNER_PUBLIC_ADDRESS= ``` ## Setup Guardian[​](#setup-guardian "Direct link to Setup Guardian") * ethers * viem ``` import { SafeMultiChainSigAccountV1 as SafeAccount, SocialRecoveryModule, } from "abstractionkit"; import { Magic } from "magic-sdk"; import { BrowserProvider } from 'ethers'; const magic = new Magic(process.env.PUBLISHABLE_API_KEY); const provider = new BrowserProvider(magic.rpcProvider); const guardianSigner = await provider.getSigner(); const smartAccount = SafeAccount.initializeNewAccount([process.env.OWNER_PUBLIC_ADDRESS]); const srm = new SocialRecoveryModule(); const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( smartAccount.accountAddress, guardianSigner.address, // Magic Guardian Address 1n //threshold ); let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.JSON_RPC_NODE_PROVIDER, process.env.BUNDLER_URL, ); ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { Magic } from "magic-sdk"; import { createWalletClient, custom } from "viem"; const magic = new Magic(process.env.PUBLISHABLE_API_KEY); const guardianSigner = createWalletClient({ transport: custom(magic.rpcProvider), }); const guardianAddresses = await signer.getAddresses(); const srm = new SocialRecoveryModule(); const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( smartAccount.accountAddress, guardianAddresses[0], // Magic Guardian Address 1n //threshold ); let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.JSON_RPC_NODE_PROVIDER, process.env.BUNDLER_URL, ); ``` info To use MagicSigner in your app's client, you must ensure the `window` object is defined. For example, use magic in webapps and not node scripts. ## Initiate Recovery[​](#initiate-recovery "Direct link to Initiate Recovery") * ethers * viem ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction( smartAccount.address, [process.env.NEW_OWNER_PUBLIC_ADDRESS], 1, // new threshold true, // whether to auto-start execution of recovery ); // make sure to fund the guardian address on magic // otherwise, you can make the guardian a smart account from the start // so you are able to sponsor the gas! const sendTx1 = guardianSigner.sendTransaction({ to: initiateRecoveryMetaTx.to, data: initiateRecoveryMetaTx.data, value: 0, }); ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { createWalletClient, http } from "viem"; const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction( smartAccount.address, [process.env.NEW_ONWER_PUBLIC_ADDRESS], 1, // new threshold true, // whether to auto-start execution of recovery ) // make sure to fund the guardian address on magic const sendTx1 = await guardianSigner.sendTransaction({ to: initiateRecoveryMetaTx.to, data: initiateRecoveryMetaTx.data, }); ``` ## Finilize Recovery[​](#finilize-recovery "Direct link to Finilize Recovery") Wait for recovery period to pass before finilizing * ethers * viem ``` const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress) // Anyone can call the finilize function after the grace period is over const sendTx2 = await guardianSigner.sendTransaction({ to: finalizeRecoveryMetaTx.to, data: finalizeRecoveryMetaTx.data, }) ``` ``` const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress) // Anyone can call the finilize function after the grace period is over const sendTx2 = await guardianSigner.sendTransaction({ to: finalizeRecoveryMetaTx.to, data: finalizeRecoveryMetaTx.data, }) ``` --- # Multisig Smart Accounts with Safe Create and manage multisig Safe accounts with multiple owners, threshold signing, and dynamic owner management for secure shared control. > For the basics, see the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md). [YouTube video player](https://www.youtube.com/embed/jZFx8cYRR48?si=EsS_DT5NRWyKDSaZ) ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/multisig/multisig.ts) and follow along. ### Create Multisig Account[​](#create-multisig-account "Direct link to Create Multisig Account") Set up a multisig account with multiple owners and signature threshold: * index.ts * .env Create multisig account ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const ownerPublicAddress1 = process.env.PUBLIC_ADDRESS1 as string; const ownerPublicAddress2 = process.env.PUBLIC_ADDRESS2 as string; // Create a 2/2 multisig account (requires both signatures) const smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress1, ownerPublicAddress2], { threshold: 2 } ); console.log("Multisig Account Address:", smartAccount.accountAddress); ``` > Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#initializenewaccount) Fund Your Account Make sure to fund your multisig account with ETH before sending transactions, or use a [paymaster for gas sponsorship](https://docs.candide.dev/wallet/guides/send-gasless-tx.md). ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com # Multisig owners PUBLIC_ADDRESS1= PRIVATE_KEY1= PUBLIC_ADDRESS2= PRIVATE_KEY2= ``` ### Sign with Multiple Keys[​](#sign-with-multiple-keys "Direct link to Sign with Multiple Keys") Sign UserOperations with all required private keys: * signing.ts * Threshold Examples Multisig signing ``` const chainId = BigInt(process.env.CHAIN_ID as string); const privateKey1 = process.env.PRIVATE_KEY1 as string; const privateKey2 = process.env.PRIVATE_KEY2 as string; // Sign with both private keys for 2/2 multisig userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey1, privateKey2], // Array of all required signatures chainId ); ``` > Learn more: [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#signuseroperation) Different threshold configurations ``` // 1 of 2 multisig (only one signature required) const smartAccount_1of2 = SafeAccount.initializeNewAccount( [ownerAddress1, ownerAddress2], { threshold: 1 } ); // 2 of 3 multisig (two signatures required) const smartAccount_2of3 = SafeAccount.initializeNewAccount( [ownerAddress1, ownerAddress2, ownerAddress3], { threshold: 2 } ); // For 2/3 multisig, sign with any 2 owners: userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey1, privateKey3], // Only 2 out of 3 needed chainId ); ``` > Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#initializenewaccount) | [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#signuseroperation) #### Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example") Below is a complete example that demonstrates multisig account creation and signing: Full Working Example * index.ts * .env ``` import * as dotenv from 'dotenv' import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, getFunctionSelector, createCallData, } from "abstractionkit"; async function main(): Promise { // Load environment variables dotenv.config() const chainId = BigInt(process.env.CHAIN_ID as string) const bundlerUrl = process.env.BUNDLER_URL as string const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const ownerPublicAddress1 = process.env.PUBLIC_ADDRESS1 as string const ownerPrivateKey1 = process.env.PRIVATE_KEY1 as string const ownerPublicAddress2 = process.env.PUBLIC_ADDRESS2 as string const ownerPrivateKey2 = process.env.PRIVATE_KEY2 as string // Create 2/2 multisig account const smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress1, ownerPublicAddress2], { threshold: 2 } ) console.log("🔐 Multisig Account Address:", smartAccount.accountAddress) // Create example transactions (minting NFTs) const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] ); const transaction1: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } const transaction2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } // Create UserOperation let userOperation = await smartAccount.createUserOperation( [transaction1, transaction2], jsonRpcNodeProvider, bundlerUrl ) console.log("📋 UserOperation created, awaiting multisig signatures...") // Sign with both private keys (2/2 multisig) userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey1, ownerPrivateKey2], // All required signatures chainId ) console.log("✍️ UserOperation signed by all parties") // Submit the multisig transaction const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ) console.log("📤 Multisig UserOperation sent. Waiting for confirmation...") // Wait for transaction to be included let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("📋 UserOperation receipt received.") console.log(userOperationReceiptResult) if (userOperationReceiptResult.success) { console.log("🎉 Multisig transaction successful! Hash:", userOperationReceiptResult.receipt.transactionHash) } else { console.log("❌ UserOperation execution failed") } } main().catch(console.error) ``` > Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#initializenewaccount) | [`createUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#createuseroperation) | [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#signuseroperation) | [`sendUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#senduseroperation) ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com # Multisig owners PUBLIC_ADDRESS1=your_first_public_address PRIVATE_KEY1=your_first_private_key PUBLIC_ADDRESS2=your_second_public_address PRIVATE_KEY2=your_second_private_key ``` ## Managing Multisig Owners[​](#managing-multisig-owners "Direct link to Managing Multisig Owners") After creating a multisig account, you can dynamically add, remove, or swap owners: * Add Owner * Remove Owner * Swap Owner Add a new owner ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; // Add a new owner with threshold of 3 (now 3/3 multisig) const newOwnerAddress = "0x..."; // New owner's address const newThreshold = 3; const addOwnerTx = await smartAccount.createAddOwnerWithThresholdMetaTransactions( newOwnerAddress, newThreshold, jsonRpcNodeProvider ); // Create UserOperation with the add owner transaction let userOperation = await smartAccount.createUserOperation( [addOwnerTx], jsonRpcNodeProvider, bundlerUrl ); // Sign with existing owners (2/2 required) userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey1, privateKey2], chainId ); ``` > Learn more: [`createAddOwnerWithThresholdMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md#createaddownerwiththresholdmetatransactions) Remove an owner ``` const ownerToRemove = "0x..."; // Owner address to remove const newThreshold = 1; // New threshold after removal const removeOwnerTx = await smartAccount.createRemoveOwnerMetaTransaction( ownerToRemove, newThreshold, jsonRpcNodeProvider ); // Create UserOperation with the remove owner transaction let userOperation = await smartAccount.createUserOperation( [removeOwnerTx], jsonRpcNodeProvider, bundlerUrl ); // Sign with required owners (current threshold) userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey1, privateKey2], // All current owners must sign chainId ); ``` > Learn more: [`createRemoveOwnerMetaTransaction`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createremoveownermetatransaction) Replace an owner ``` const oldOwnerAddress = "0x..."; // Owner to replace const newOwnerAddress = "0x..."; // New owner address const swapOwnerTxs = await smartAccount.createSwapOwnerMetaTransactions( oldOwnerAddress, newOwnerAddress, jsonRpcNodeProvider ); // Note: This returns an array of transactions (might include deployment) let userOperation = await smartAccount.createUserOperation( swapOwnerTxs, // Array of meta-transactions jsonRpcNodeProvider, bundlerUrl ); // Sign with existing owners userOperation.signature = smartAccount.signUserOperation( userOperation, [privateKey1, privateKey2], chainId ); ``` > Learn more: [`createSwapOwnerMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createswapownermetatransactions) ## Multisig Configurations[​](#multisig-configurations "Direct link to Multisig Configurations") Different threshold configurations serve various real-world scenarios: #### 2/2 - Maximum Security[​](#22---maximum-security "Direct link to 2/2 - Maximum Security") * **Two-Factor Authentication**: Phone wallet + hardware wallet / second device for personal accounts * **Business Partnership**: Joint business account where both partners must approve all transactions * **High-Value Storage**: Maximum security for large amounts Account Recovery Required 2/2 setups can lock owners out permanently if they lose any key. Always implement [Account Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md). #### 2/3 - Balanced Control[​](#23---balanced-control "Direct link to 2/3 - Balanced Control") * **Company Treasury**: CEO, CFO, CTO where any 2 can approve (business continuity) * **Family Trust**: Parents + adult child with built-in inheritance planning * **Small Team**: Flexible governance with backup access #### 3/5+ - Distributed Governance[​](#35---distributed-governance "Direct link to 3/5+ - Distributed Governance") * **DAO Treasury**: Democratic council governance requiring majority consensus * **Professional Custody**: Institutional-grade security with distributed validators * **Large Organizations**: Democratic decision making with higher coordination overhead #### 1/N - Flexible Access[​](#1n---flexible-access "Direct link to 1/N - Flexible Access") * **Team Operations**: Shared operational funds for routine expenses * **Development Teams**: Independent spending for daily operations --- # On-Chain Tracking - Adding an Identifier to Your Safe Accounts Attribute active users and UserOperations to your project by tagging each userOp's `callData` with a 32-byte marker. Your indexer filters on the marker, no extra infrastructure, no off-chain correlation. ## Generate an On-Chain Identifier[​](#generate-an-on-chain-identifier "Direct link to Generate an On-Chain Identifier") Pass `onChainIdentifierParams` when you initialize the Safe account. The SDK appends the marker to every userOp's `callData`. ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress], { onChainIdentifierParams: { project: "YourProject", // required — the thing you key analytics off platform: "Web", // optional — 'Web' | 'Mobile' | 'Safe App' | 'Widget' tool: "abstractionkit", // optional — which SDK toolVersion: "0.3.2", // optional — SDK version }, }); ``` Only `project` is required. `platform`, `tool`, and `toolVersion` refine attribution (Web vs Mobile, which SDK, which version). Use the same values everywhere, analytics key off this exact string. ### Already-deployed accounts[​](#already-deployed-accounts "Direct link to Already-deployed accounts") For accounts that already exist on-chain, pass the same params to the constructor. New userOps will carry the marker from that point on; historical userOps are not retroactively tagged. ``` const smartAccount = new SafeAccount(accountAddress, { onChainIdentifierParams: { project: "YourProject" }, }); ``` ## Getting the On-Chain Identifier[​](#getting-the-on-chain-identifier "Direct link to Getting the On-Chain Identifier") ``` const onchainIdentifier = smartAccount.onChainIdentifier; // "0x5afe00..." ``` View the identifier generation in the [generateOnChainIdentifier](https://github.com/candidelabs/abstractionkit/blob/v0.4.0/src/account/Safe/SafeAccount.ts#L3458) source code. ## Indexer patterns[​](#indexer-patterns "Direct link to Indexer patterns") ### Exact match: `UserOperationEvent`[​](#exact-match-useroperationevent "Direct link to exact-match-useroperationevent") The EntryPoint emits a `UserOperationEvent` log for every included userOp. Decode it, pull the userOp's `callData`, and check the suffix. This is the right approach: it's per-userOp and the marker sits exactly at the tail. ``` const endsWithId = userOp.callData .toLowerCase() .endsWith(identifier.toLowerCase()); ``` Aggregate: * Unique `sender` values → active users * Total matching events → userOp volume * Group by the identifier's trailing hashes → split by platform / tool / version ### Fuzzy match: `handleOps` tx input[​](#fuzzy-match-handleops-tx-input "Direct link to fuzzy-match-handleops-tx-input") The bundler wraps userOps in `EntryPoint.handleOps(ops[], beneficiary)`. The ABI-encoded tx calldata is laid out as: ``` selector (4 bytes) │ ├─ head │ offset-to-ops (32 bytes) │ beneficiary (32 bytes) │ └─ ops data (dynamic: length + each op, with callData inlined as dynamic bytes) ``` The marker is inside each op's `callData`, so it appears somewhere in the dynamic tail of the tx input, not strictly at the end. With multiple userOps batched into one `handleOps` call, each op's callData contributes its own marker occurrence at a different offset. Suffix matching on `tx.input` is not reliable. Use substring match, or decode the ops array and inspect each `callData`: ``` const tagged = tx.input .toLowerCase() .includes(identifier.toLowerCase()); ``` Good enough for quick dashboards, but batches of multiple userOps in one `handleOps` call collapse into one match. For per-userOp attribution, decode the ops array (ABI-decode the tx input) and check each op's `callData` individually, or prefer the event-based approach above. ## Full example[​](#full-example "Direct link to Full example") A complete working script (Safe v0.3.0 on Arbitrum Sepolia, with sponsored gas and a mint transaction) is available on GitHub: [onchain-identifier.ts](https://github.com/candidelabs/abstractionkit-examples/blob/main/onchain-identifier/onchain-identifier.ts). ## Marker layout[​](#marker-layout "Direct link to Marker layout") What's the format of the identifier? The identifier is 32 bytes and follows the format below: `5afe` `00` `6363643438383836663461336661366162653539` `646561` `393238` `653366` Check the last 32 bytes of the `callData` field in a `UserOperationEvent` log, or inside the `handleOps` tx input, to see how the identifier appears after the transaction is executed. ``` 5afe │ 00 │ project(20) │ platform(3) │ tool(3) │ toolVersion(3) └─prefix │ version ``` Each variable-content field is `keccak256(value)` truncated to its byte width. #### Prefix hash[​](#prefix-hash "Direct link to Prefix hash") * Type: 2 bytes * Example: `5afe` Static prefix to identify the Safe on-chain identifier. #### Version hash[​](#version-hash "Direct link to Version hash") * Type: 1 byte * Example: `00` Version number of the Safe on-chain identifier format. #### Project hash[​](#project-hash "Direct link to Project hash") * Type: 20 bytes * Example: `6363643438383836663461336661366162653539` Truncated hash of the project's name (for example, "Gnosis", "CoW Swap"). #### Platform hash[​](#platform-hash "Direct link to Platform hash") * Type: 3 bytes * Example: `646561` Truncated hash of the platform's name (for example, "Web", "Mobile", "Safe App", "Widget"). #### Tool hash[​](#tool-hash "Direct link to Tool hash") * Type: 3 bytes * Example: `393238` Truncated hash of the tool's name (for example, "protocol-kit", "relay-kit", or any custom tool built by projects). #### Tool version hash[​](#tool-version-hash "Direct link to Tool version hash") * Type: 3 bytes * Example: `653366` Truncated hash of the tool's version (for example, "1.0.0", "1.0.1"). ## Submission Form[​](#submission-form "Direct link to Submission Form") The Safe team aims to better understand and recognise key contributors who are driving the adoption of smart accounts within the ecosystem. By submitting your on-chain identifiers through the provided form, you will help Safe accurately attribute activity. You can fill out the form by clicking [this link](https://forms.gle/NYkorYebc6Fz1fMW6). --- # How to Pay Gas in ERC-20 Tokens | EIP-7702 **Smart EOAs via EIP-7702** Enable users to pay gas in ERC-20 tokens using any [supported ERC-20 token](https://docs.candide.dev/wallet/paymaster/tokens-supported.md) on Candide Paymaster. 1. Create a new app on the [dashboard](https://dashboard.candide.dev) and copy the Paymaster URL to your `.env` file 2. Select an ERC-20 token for gas payment and add its address to the `.env` file (this example uses CTT) If you would like to see a full example, you can reference it [here](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/02-upgrade-eoa-erc20-gas.ts). * index.ts * .env ``` import { ... Erc7677Paymaster, } from "abstractionkit"; const paymasterRPC = process.env.PAYMASTER_URL as string; const tokenAddress = process.env.TOKEN_ADDRESS as string; const paymaster = new Erc7677Paymaster(paymasterRPC) const { userOperation: tokenOp, tokenQuote } = await paymaster.createPaymasterUserOperation( smartAccount, // = new Simple7702AccountV09(eoaDelegator.address) userOperation, bundlerUrl, { token: tokenAddress }, ) userOperation = tokenOp; const cost = tokenQuote?.tokenCost; console.log("This useroperation may cost up to : " + cost + " in the token") console.log("Please fund the sender account: " + userOperation.sender + " with at least " + cost + " of the token") ``` ``` PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 TOKEN_ADDRESS=0xFa5854FBf9964330d761961F46565AB7326e5a3b # CTT address on sepolia ``` info Don't forget to fund the account with ERC-20s to pay for gas. We are using Candide Test Tokens (CTT) for this example. Get test tokens from our [dashboard faucet](https://dashboard.candide.dev/faucet) - supporting CTT and USDT on testnet. --- # Pay Gas with ERC-20 Tokens Enable users to pay gas fees with ERC-20 tokens instead of native tokens using Candide's Token Paymaster. > For the basics, see the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md). ![](/img/network-fees-sponsorship.png) ## Why Use Token Paymaster[​](#why-use-token-paymaster "Direct link to Why Use Token Paymaster") Token paymasters enable users to pay gas fees with ERC-20 tokens instead of ETH, eliminating the need to hold native tokens for transactions. * **Better UX**: Users transact with only the tokens they need * **Cost Calculation**: Calculate exact token amounts for gas costs * **Supported Tokens**: USDC, USDT, and other [popular ERC-20 tokens](https://docs.candide.dev/wallet/paymaster/tokens-supported.md) ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/pay-gas-in-erc20/pay-gas-in-erc20.ts) and follow along. [YouTube video player](https://www.youtube.com/embed/3jU_baONS9s?si=OfWuHpqpHWbjOnJD) ### Step 1: Fetch Supported Tokens[​](#step-1-fetch-supported-tokens "Direct link to Step 1: Fetch Supported Tokens") * index.ts * .env Token paymaster implementation ``` import { Erc7677Paymaster } from "abstractionkit"; const paymasterRPC = process.env.PAYMASTER_URL as string; const tokenAddress = process.env.TOKEN_ADDRESS as string; const paymaster = new Erc7677Paymaster(paymasterRPC); // Pass any supported token address. Erc7677Paymaster fetches the quote // and prepends the required ERC-20 approval automatically. console.log(`Using ${tokenAddress} for gas payment`); ``` Test Tokens Get ERC-20 faucet tokens (CTT or USDT) from our [dashboard faucet](https://dashboard.candide.dev/faucet) for testing. Ensure your smart account has sufficient ERC-20 tokens to pay for gas. ``` # Paymaster service endpoint PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 # Token address for gas payment (CTT on Sepolia for testing) TOKEN_ADDRESS=0xFa5854FBf9964330d761961F46565AB7326e5a3b ``` ### Step2: Get Token Paymaster Data[​](#step2-get-token-paymaster-data "Direct link to Step2: Get Token Paymaster Data") Get token paymaster data ``` const { userOperation: tokenOp, tokenQuote } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, { token: tokenAddress }, ); userOperation = tokenOp; console.log(`Max token cost: ${tokenQuote?.tokenCost} (smallest units)`); ``` ## Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example") Below is a complete example that demonstrates ERC-20 token gas payments: Full Working Example * index.ts * .env ``` import * as dotenv from 'dotenv' import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, Erc7677Paymaster, getFunctionSelector, createCallData, } from "abstractionkit"; async function main(): Promise { // Load environment variables dotenv.config() const chainId = BigInt(process.env.CHAIN_ID as string) const bundlerUrl = process.env.BUNDLER_URL as string const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const paymasterRPC = process.env.PAYMASTER_URL as string const tokenAddress = process.env.TOKEN_ADDRESS as string const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const ownerPrivateKey = process.env.PRIVATE_KEY as string // Create smart account let smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) console.log("Smart Account Address:", smartAccount.accountAddress) // Create example transactions (minting NFTs) const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] ); const transaction1: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } const transaction2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } // Create UserOperation let userOperation = await smartAccount.createUserOperation( [transaction1, transaction2], jsonRpcNodeProvider, bundlerUrl ) // Set up token paymaster const paymaster = new Erc7677Paymaster(paymasterRPC) console.log(`Using ${tokenAddress} for gas payment`); // Create token paymaster UserOperation const { userOperation: tokenOp, tokenQuote } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, { token: tokenAddress }, ); userOperation = tokenOp; console.log(`Max token cost: ${tokenQuote?.tokenCost} (smallest units)`); console.log(`Gas cost: ${tokenQuote?.tokenCost} in token smallest units`); console.log(`Make sure account has enough tokens`); // Sign the UserOperation userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], chainId ) // Submit the UserOperation const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ) console.log("UserOperation sent. Waiting for confirmation...") // Wait for transaction to be included let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("UserOperation receipt received.") console.log(userOperationReceiptResult) if (userOperationReceiptResult.success) { console.log("Transaction successful! Hash:", userOperationReceiptResult.receipt.transactionHash) console.log(`Gas paid with token ${tokenAddress}`); } else { console.log("UserOperation execution failed") } } main().catch(console.error) ``` ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com # Paymaster configuration PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 TOKEN_ADDRESS=0xFa5854FBf9964330d761961F46565AB7326e5a3b # Your EOA credentials PRIVATE_KEY=your_private_key_here PUBLIC_ADDRESS=your_public_address_here ``` --- # Pay Gas in USDT on Solana Send SPL token transfers from Solana accounts that hold **zero SOL**. Candide's **Solana Paymaster**, a hosted [Kora](https://solana.com/docs/tools/kora) endpoint, signs as the transaction fee payer and collects the fee in USDT inside the same transaction. This guide uses [`@tetherto/wdk-wallet-solana-gasless`](https://github.com/tetherto/wdk-wallet-solana-gasless), part of Tether's [Wallet Development Kit](https://docs.wallet.tether.io), which wraps the whole flow behind a simple `transfer()` call. ## How It Works[​](#how-it-works "Direct link to How It Works") The paymaster speaks Kora's JSON-RPC protocol, the Solana Foundation's open fee-payer standard, so any Kora client can point at it. The full API is documented in the [Solana Paymaster API reference](https://docs.candide.dev/wallet/solana-paymaster/rpc-methods.md). For each transaction: 1. Your app builds an SPL transfer and includes a small USDT payment to the fee payer 2. The paymaster validates the transaction against its policy, co-signs as fee payer, and submits it 3. The fee payer spends the SOL for fees and rent; the user pays in USDT The USDT fee is priced from the live SOL cost of the transaction. If the recipient does not yet have a USDT token account, the rent for creating it is included in the quoted fee. Supported fee tokens Mainnet USDT (`Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`) is the fee token enabled by default. USDC, other SPL tokens, and full gas sponsorship are available on request: [contact us](https://www.candide.dev/contact) to enable them for your API key. See the [Solana Paymaster API reference](https://docs.candide.dev/wallet/solana-paymaster/rpc-methods.md#supported-tokens) for details. ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete example](https://github.com/candidelabs/tether-wdk-candide/tree/main/solana-gasless/01-usdt-gas) and follow along. ### Step 1: Install[​](#step-1-install "Direct link to Step 1: Install") ``` npm install @tetherto/wdk-wallet-solana-gasless @solana/kora ``` `@solana/kora` is only used to discover the paymaster's fee payer address at runtime. The code in this guide was verified on Solana mainnet against `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.1` and `@solana/kora@0.2.1`. ### Step 2: Configure the Wallet[​](#step-2-configure-the-wallet "Direct link to Step 2: Configure the Wallet") Get your Solana Paymaster URL from the [Candide dashboard](https://dashboard.candide.dev). The URL includes your API key, so keep it in an environment variable. .env ``` # Candide's Solana Paymaster endpoint (hosted Kora). The URL includes your API key: keep it secret. SOLANA_PAYMASTER_URL= # Solana mainnet RPC. Use a paid provider for production. SOLANA_NODE_URL=https://api.mainnet-beta.solana.com ``` index.ts ``` import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless' import { KoraClient } from '@solana/kora' const nodeUrl = process.env.SOLANA_NODE_URL as string const paymasterUrl = process.env.SOLANA_PAYMASTER_URL as string const usdtMint = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB' // WDK wallets are seed-phrase based: the account is derived from a // BIP-39 mnemonic at m/44'/501'/0'/0' const seedPhrase = process.env.SEED_PHRASE || WalletManagerSolanaGasless.getRandomSeedPhrase() // The paymaster reports its fee payer address; transactions must name it const kora = new KoraClient({ rpcUrl: paymasterUrl }) const { signer_address: paymasterAddress } = await kora.getPayerSigner() const wallet = new WalletManagerSolanaGasless(seedPhrase, { provider: nodeUrl, paymasterUrl, paymasterAddress, paymasterToken: { address: usdtMint }, transferMaxFee: 1_000000n, // abort if the quoted fee exceeds 1 USDT }) const account = await wallet.getAccount(0) console.log(`Solana account: ${await account.getAddress()}`) ``` ### Step 3: Quote the Fee[​](#step-3-quote-the-fee "Direct link to Step 3: Quote the Fee") `quoteTransfer` prices the transfer without submitting it. The fee is returned in USDT base units (6 decimals). ``` const quote = await account.quoteTransfer({ token: usdtMint, recipient, amount: 100000n, // 0.1 USDT }) console.log(`Estimated network fee: ${Number(quote.fee) / 1e6} USDT`) ``` ### Step 4: Send the Transfer[​](#step-4-send-the-transfer "Direct link to Step 4: Send the Transfer") `transfer()` builds the transaction, gets it co-signed by the fee payer, and submits it. The account pays the fee from its USDT balance and never touches SOL. ``` const result = await account.transfer({ token: usdtMint, recipient, amount: 100000n, }) console.log(`Transaction signature: ${result.hash}`) console.log(`Fee paid: ${Number(result.fee) / 1e6} USDT`) ``` `transfer()` returns as soon as the transaction is submitted. Poll `getSignatureStatuses` on your RPC if you need to wait for confirmation, as the complete example below does. ## Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example") The full example lives in the [WDK + Candide examples repository](https://github.com/candidelabs/tether-wdk-candide/tree/main/solana-gasless/01-usdt-gas). It checks the account's USDT balance, quotes the fee, sends the transfer, and waits for on-chain confirmation: ``` git clone https://github.com/candidelabs/tether-wdk-candide.git cd tether-wdk-candide && npm install cp .env.example .env # fill in SOLANA_PAYMASTER_URL npm run solana-transfer-usdt-gas ``` solana-gasless/01-usdt-gas/index.ts ``` loading... ``` [See full example on GitHub]() .env ``` # Candide's Solana Paymaster endpoint (hosted Kora). The URL includes your API key: keep it secret. SOLANA_PAYMASTER_URL= # Solana mainnet RPC. Use a paid provider for production. SOLANA_NODE_URL=https://api.mainnet-beta.solana.com # BIP-39 seed phrase. If not set, the script generates one and prints it. SEED_PHRASE= # Optional: transfer recipient. Defaults to the account's own address. # SOLANA_RECIPIENT= # Optional: amount in USDT base units (6 decimals). Defaults to 100000 (0.1 USDT). # SOLANA_TRANSFER_AMOUNT= ``` ## Good to Know[​](#good-to-know "Direct link to Good to Know") * **Fee guardrail**: `transferMaxFee` aborts any transfer whose quoted fee exceeds the limit, protecting users from fee spikes. * **New recipients cost more**: the first transfer to an address without a USDT token account includes the account's rent in the fee. Subsequent transfers cost a fraction of a cent. * **Token program support**: the paymaster accepts the legacy SPL Token program; Token-2022 mints are not supported yet. * **No SOL ever needed**: the account never holds SOL, not for fees and not for rent. --- info Looking for a simpler approach? Candide offers a managed [Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md) service that handles key management for you. # Add a Google Account as a Recovery Method using Lit (Guide 1/2) This is the first of the two guides on how to add a Google account as a Recovery method for a Safe Account. In the [second guide](https://developer.litprotocol.com/integrations/aa/candide), we show the steps to execute the recovery. ## What is Lit Protocol?[​](#what-is-lit-protocol "Direct link to What is Lit Protocol?") [Lit](https://litprotocol.com) is a network for signing and encryption. One of their usecases is to enable users to login using various authentication methods like Social Logins and Email OTP. To leverage the full potential of Account Abstraction, you can combine Lit with AbstractionKit to enable email / social recovery experience, while using a Smart Account as the smart wallet to sponsor gas for users, batch transactions, and more. ### Relevant links[​](#relevant-links "Direct link to Relevant links") For additional information during this guide: * [Full demo example using Lit on Github](https://hackmd.io/TeFu2k56SgG4HCQQFZOqig) * [How on-chain guardian recovery works](https://docs.candide.dev/wallet/plugins/recovery-with-guardians/) * [Guardian Recovery SDK Reference](https://docs.candide.dev/blog/making-accounts-recoverable/) * [Simple recovery demo on GitHub](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts) * [Lit Documentation Website](https://developer.litprotocol.com/) ## Installation[​](#installation "Direct link to Installation") ### Install required dependencies[​](#install-required-dependencies "Direct link to Install required dependencies") ``` npm i abstractionkit && @lit-protocol/lit-auth-client ``` ### Configure .env file[​](#configure-env-file "Direct link to Configure .env file") Configure the values you created from Lit dashboard in an .env file ``` // Lit LIT_API_KEY=Request Relay Server API Key from Lit at https://forms.gle/RNZYtGYTY9BcD9MEA // Candide BUNDLER_URL="https://api.candide.dev/public/v3/11155111" // Other networks are found here: https://docs.candide.dev/wallet/api/supported-networks PAYMASTER_URL="Request an API key from Candide on Discord" // Generate a Public/Private Key OWNER_PUBLIC_ADDRESS= OWNER_PRIVATE_KEY= // Network Info VITE_CHAIN_ID= JSON_RPC_NODE_PROVIDER= // "Get an RPC from a Node provider" ``` ## Setup The Google Account with Lit[​](#setup-the-google-account-with-lit "Direct link to Setup The Google Account with Lit") ### Initialize the Lit client and provider[​](#initialize-the-lit-client-and-provider "Direct link to Initialize the Lit client and provider") * Connect to the Lit Network using LitNodeClient. * Set up the LitAuthClient for authentication. * Initialize a GoogleProvider for Google sign-in. ``` import { LitNodeClient } from "@lit-protocol/lit-node-client"; import { LitAuthClient, GoogleProvider } from "@lit-protocol/lit-auth-client"; import { ProviderType } from "@lit-protocol/constants"; const initalizeClientsAndProvider = async () => { const litNodeClient = new LitNodeClient({ litNetwork: "datil-dev", debug: true, }); await litNodeClient.connect(); const litAuthClient = new LitAuthClient({ litRelayConfig: { relayApiKey: "Anything", }, litNodeClient, }); console.log("Connected to Lit Node and Lit Auth Clients ✔️"); const provider = litAuthClient.initProvider( ProviderType.Google, { //redirectUri: VITE_REDIRECT_URI, } ); return { litNodeClient, litAuthClient, provider }; }; ``` ### Authentication with Gmail[​](#authentication-with-gmail "Direct link to Authentication with Gmail") * Generate Authentication Method * Check if the user is already authenticated. If not, redirect to Google sign-in ``` import { AuthMethod } from "@lit-protocol/types"; const generateAuthMethod = async () => { const url = new URL(window.location.href); if (!url.searchParams.get("provider")) { console.log("Signing in with Google..."); provider.signIn((url) => { window.location.href = url; }); } else if (url.searchParams.get("provider") === "google") { const authMethod = await provider.authenticate(); return authMethod; } }; const authMethod = await generateAuthMethod(); if (!authMethod) { return; } ``` ### Mint PKP (Programmable Key Pair)[​](#mint-pkp-programmable-key-pair "Direct link to Mint PKP (Programmable Key Pair)") ``` import { LitAuthClient } from "@lit-protocol/lit-auth-client"; const mintWithGoogle = async (authMethod) => { const pkp = await litAuthClient.mintPKPWithAuthMethods([authMethod], { addPkpEthAddressAsPermittedAddress: true }); console.log("Fetched PKP", pkp); return pkp; }; const pkp = await mintWithGoogle(authMethod); console.log("Minted PKP ✔️"); ``` #### Setup the Google Account as a Guardian[​](#setup-the-google-account-as-a-guardian "Direct link to Setup the Google Account as a Guardian") ``` import { PKPEthersWallet } from "@lit-protocol/pkp-ethers"; import { LitAbility, LitPKPResource } from "@lit-protocol/auth-helpers"; import { AuthCallbackParams } from "@lit-protocol/types"; const authNeededCallback = async (params: AuthCallbackParams) => { console.log(`auth needed callback params`, JSON.stringify(params, null, 2)); const response = await litNodeClient.signSessionKey({ statement: params.statement, authMethods: [authMethod], resourceAbilityRequests: [ { resource: new LitPKPResource("*"), ability: LitAbility.PKPSigning, }, ], expiration: params.expiration, resources: params.resources, chainId: 1, pkpPublicKey: pkp.pkpPublicKey, }); console.log("AUTHSIG", response); return response.authSig; }; const guardianSigner = new PKPEthersWallet({ litNodeClient, authContext: { getSessionSigsProps: { chain: "ethereum", expiration: new Date(Date.now() + 60_000 * 60).toISOString(), resourceAbilityRequests: [ { resource: new LitPKPResource("*"), ability: LitAbility.PKPSigning, }, ], authNeededCallback: authNeededCallback, }, }, pkpPubKey: pkp.pkpPublicKey, rpc: "https://yellowstone-rpc.litprotocol.com", }); console.log("Created PKPEthersWallet using the PKP ✔️"); ``` ## Add Google as a Guardian to the Smart Account[​](#add-google-as-a-guardian-to-the-smart-account "Direct link to Add Google as a Guardian to the Smart Account") We will integrate Lit as the guardian of the Smart Account. Not only that, we will be creating a special Smart Account that will be owned by the Lit key, allowing us to sponsor gas fees during the recovery process. No need to fund the associated Gmail account with funds to execute a recovery process. ### Initilize a Smart Account[​](#initilize-a-smart-account "Direct link to Initilize a Smart Account") ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); console.log("Smart Account Address:", smartAccount.accountAddress); ``` ### Enable The Module and Add the Guardian[​](#enable-the-module-and-add-the-guardian "Direct link to Enable The Module and Add the Guardian") ``` import { SocialRecoveryModule } from "abstractionkit"; const srm = new SocialRecoveryModule(); const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); // Note that the Guardian added is also a Smart Account, to enable easy recovery without gas const guardianOwnerAddress = await guardianSigner.getAddress(); const guardianSmartAccount = SafeAccount.initializeNewAccount([guardianOwnerAddress]); const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( smartAccount.accountAddress, guardianSmartAccount.accountAddress, 1n ); let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.JSON_RPC_NODE_PROVIDER, process.env.BUNDLER_URL ); ``` ### Sponsor the Gas[​](#sponsor-the-gas "Direct link to Sponsor the Gas") ``` import { CandidePaymaster } from "abstractionkit"; const paymaster = new CandidePaymaster(process.env.PAYMASTER_URL); const { userOperation: sponsoredOp } = await paymaster.createSponsorPaymasterUserOperation( smartAccount, userOperation, process.env.BUNDLER_URL ); userOperation = sponsoredOp; ``` ### Sign and Submit UserOperation[​](#sign-and-submit-useroperation "Direct link to Sign and Submit UserOperation") ``` userOperation.signature = smartAccount.signUserOperation( userOperation, [process.env.OWNER_PRIVATE_KEY], process.env.CHAIN_ID ); const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, process.env.BUNDLER_URL ); ``` ### Monitor UserOperation[​](#monitor-useroperation "Direct link to Monitor UserOperation") ``` console.log("User operation sent. Waiting to be included..."); const userOperationReceiptResult = await sendUserOperationResponse.included(); if (userOperationReceiptResult.success) { console.log( "Successful User operation. Transaction hash: " + userOperationReceiptResult.receipt.transactionHash ); const isGuardian = await srm.isGuardian( jsonRpcNodeProvider, smartAccount.accountAddress, guardianSmartAccount.accountAddress ); if (isGuardian) { console.log("Guardian added confirmed ✔️. Guardian address: " + guardianSmartAccount.accountAddress); } else { console.log("Adding guardian failed."); } } else { console.log("User operation execution failed"); } ``` *** That's it! You've successfully added a Guardian capable of recovering a smart account with a Google Account using Lit. If you're interested in learning how to recovery the account with Google using Lit, you can explore [the second guide](https://developer.litprotocol.com/integrations/aa/candide) that published on lit docs. Find here the complete documentation for [Account Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md). *** --- # How to Send a Gasless Transaction | EIP-7702 **Smart EOAs via EIP-7702** EIP-7702 enables Ethereum EOAs (Externally Owned Accounts) to function as smart accounts for transactions, unlocking features like gas sponsorship and multisig without permanently converting the EOA. With EIP-7702, users access gasless transactions through publicly available gas policies on InstaGas from third parties like PoolTogether, Revoke, and AAVE. If no matching public gas policy is found, fall back to your private gas policy. Definitions: * **Public Gas Policies**: Gas sponsorships provided by dApps that don't require a sponsorship policy ID * **Private Gas Policies**: Require a sponsorship policy ID and serve as fallback when no public gas policy matches the user operation Steps to Implement Gasless Transactions: 1. Create a new app on the [dashboard](https://dashboard.candide.dev) and copy the Paymaster RPC URL into your `.env` file. 2. Optionally, set up a Private Gas Policy on the dashboard and copy its sponsorship policy ID into your `.env` file. If you would like to see a full example, you can reference it [here](https://github.com/candidelabs/abstractionkit-examples/blob/main/eip-7702/simple-account/01-upgrade-eoa.ts). * index.ts * .env ``` import { Erc7677Paymaster, } from "abstractionkit"; const paymasterRPC = process.env.PAYMASTER_URL as string; const paymaster: Erc7677Paymaster = new Erc7677Paymaster(paymasterRPC); const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID; // userOperation and bundlerUrl should be defined elsewhere in your code let paymasterUserOperation; try { // Sponsor gas using public gas policies ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, )); } catch (publicGasPolicyUnavailable) { try { // Sponsor gas using a private gas policy ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, sponsorshipPolicyId ? { sponsorshipPolicyId, policyId: sponsorshipPolicyId } : {}, )); } catch (privateGasPolicyUnavailable) { // Fallback: propose ERC-20 token gas or native-token gas. throw privateGasPolicyUnavailable; } } userOperation = paymasterUserOperation; ``` ``` PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 SPONSORSHIP_POLICY_ID= ``` --- # Gas Sponsorship with Paymaster Sponsor gas fees for UserOperations using Candide's Paymaster [InstaGas](https://docs.candide.dev/instagas/overview.md) with public and private gas policies. > For smart account basics, see the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md). ![](/img/gasless-transaction-screen.png) ## Getting Started[​](#getting-started "Direct link to Getting Started") ### Setup Gas Policy[​](#setup-gas-policy "Direct link to Setup Gas Policy") To create a private gas policy for sponsoring specific transactions: 1. Create an account on the [Candide Dashboard](https://dashboard.candide.dev) 2. Navigate to Gas Policies and create a new private policy 3. Configure sponsorship [rules and conditions](https://docs.candide.dev/instagas/gas-policies.md) for transaction sponsorship 4. Fund your policy by depositing native tokens to cover gas costs 5. Copy the Policy ID for referencing your policy #### Environment Configuration[​](#environment-configuration "Direct link to Environment Configuration") Add the paymaster configuration to your `.env`: .env ``` # Paymaster service endpoint PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 # Optional: Your private gas policy ID (if you created one) SPONSORSHIP_POLICY_ID= ``` ### Getting Paymaster Data[​](#getting-paymaster-data "Direct link to Getting Paymaster Data") Try public policies first, then fallback to private if no public policy matches > You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/sponsor-gas/sponsor-gas.ts) and follow along. * index.ts * .env Public first, private fallback ``` import { Erc7677Paymaster } from "abstractionkit"; const paymasterRPC = process.env.PAYMASTER_URL as string; const paymaster = new Erc7677Paymaster(paymasterRPC); const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID; let paymasterUserOperation; try { // First, try public gas policies ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl )); console.log("Sponsored by public gas policy!"); } catch (error) { try { // Fallback to private gas policy ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, sponsorshipPolicyId ? { sponsorshipPolicyId, policyId: sponsorshipPolicyId } : {} )); console.log("Sponsored by your private gas policy!"); } catch (finalError) { console.log("No gas sponsorship available"); throw finalError; } } userOperation = paymasterUserOperation; ``` ``` # Paymaster service endpoint PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 # Optional: Your private gas policy ID (if you created one) SPONSORSHIP_POLICY_ID= ``` #### Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example") Below is a complete example that demonstrates gas sponsorship with public and private policy fallback: Full Working Example * index.ts * .env ``` import * as dotenv from 'dotenv' import { SafeMultiChainSigAccountV1 as SafeAccount, MetaTransaction, Erc7677Paymaster, getFunctionSelector, createCallData, } from "abstractionkit"; async function main(): Promise { // Load environment variables dotenv.config() const chainId = BigInt(process.env.CHAIN_ID as string) const bundlerUrl = process.env.BUNDLER_URL as string const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const paymasterRPC = process.env.PAYMASTER_URL as string const sponsorshipPolicyId = process.env.SPONSORSHIP_POLICY_ID const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const ownerPrivateKey = process.env.PRIVATE_KEY as string // Create smart account let smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) console.log("Smart Account Address:", smartAccount.accountAddress) // Create example transactions (minting NFTs) const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; const mintFunctionSignature = 'mint(address)'; const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); const mintTransactionCallData = createCallData( mintFunctionSelector, ["address"], [smartAccount.accountAddress] ); const transaction1: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } const transaction2: MetaTransaction = { to: nftContractAddress, value: 0n, data: mintTransactionCallData, } // Create UserOperation let userOperation = await smartAccount.createUserOperation( [transaction1, transaction2], jsonRpcNodeProvider, bundlerUrl ) // Add paymaster sponsorship - try public first, fallback to private const paymaster = new Erc7677Paymaster(paymasterRPC) let paymasterUserOperation; try { // Try public gas policies first console.log("Checking for public gas policies...") ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl )); console.log("Sponsored by public gas policy!") } catch (error) { try { // Fallback to private gas policy console.log("Trying private gas policy fallback...") ({ userOperation: paymasterUserOperation } = await paymaster.createPaymasterUserOperation( smartAccount, userOperation, bundlerUrl, sponsorshipPolicyId ? { sponsorshipPolicyId, policyId: sponsorshipPolicyId } : {} )); console.log("Sponsored by private gas policy!") } catch (finalError) { console.log("No gas sponsorship available"); throw finalError; } } userOperation = paymasterUserOperation; console.log("Transaction will be gasless for the user!") // Sign the UserOperation userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], chainId ) // Submit the sponsored UserOperation const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ) console.log("Sponsored UserOperation sent. Waiting for confirmation...") // Wait for transaction to be included let userOperationReceiptResult = await sendUserOperationResponse.included() console.log("UserOperation receipt received.") console.log(userOperationReceiptResult) if (userOperationReceiptResult.success) { console.log("Gasless transaction successful! Hash:", userOperationReceiptResult.receipt.transactionHash) } else { console.log("UserOperation execution failed") } } main().catch(console.error) ``` ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 # Optional: Your private gas policy ID SPONSORSHIP_POLICY_ID= # Your EOA credentials PRIVATE_KEY=your_private_key_here PUBLIC_ADDRESS=your_public_address_here ``` ## Gas Policies Overview[​](#gas-policies-overview "Direct link to Gas Policies Overview") Candide's Paymaster supports two types of gas policies: ### Public Gas Policies[​](#public-gas-policies "Direct link to Public Gas Policies") * Set up by external applications - Apps like PoolTogether and Revoke.cash sponsor gas for their users * Application-specific - Each policy targets specific contract interactions or user flows * No setup required - Available automatically when your UserOperation matches their criteria * Best for - Users interacting with supported dApps and protocols ### Private Gas Policies[​](#private-gas-policies "Direct link to Private Gas Policies") * Your custom rules - You create and fund policies for your specific application * Full control - Set custom sponsorship conditions, limits, and restrictions * Your funding - You deposit native tokens to sponsor transactions that match your criteria * Best for - Wallets wanting to sponsor their users' transactions --- # Sign & Validate a Message with a Smart Wallet Smart wallets can sign arbitrary messages similar to EOAs. Learn how to sign messages using Safe wallet and verify signatures using EIP-1271 Signature Validation. ## Single Owner Safe[​](#single-owner-safe "Direct link to Single Owner Safe") ### Sign Message[​](#sign-message "Direct link to Sign Message") Signing ``` import * as dotenv from 'dotenv'; import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { Contract, JsonRpcProvider, Wallet, hashMessage} from "ethers"; // v6 dotenv.config() const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress], ) const chainId = BigInt(process.env.CHAIN_ID as string) const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const provider = new JsonRpcProvider(jsonRpcNodeProvider, chainId); const abiSmartWallet = [ "function isValidSignature(bytes32 _dataHash, bytes calldata _signature) external view returns (bytes4)", "function getMessageHash(bytes memory message) public view returns (bytes32)" ]; const safeContract = new Contract( smartAccount.accountAddress, abiSmartWallet, provider, ); const message = hashMessage("Hello World"); const safeMessageHash = await safeContract.getMessageHash(message); const ownerPrivateKey = process.env.PRIVATE_KEY as string const signer = new Wallet(ownerPrivateKey); const signature = await signer.signingKey.sign(safeMessageHash).serialized; ``` Verifying the Signature ``` const returnValue = await safeContract.isValidSignature( message, signature, ); console.log(returnValue, "Success! You should see the magic value 0x1626ba7e"); ``` ### Sign Typed Data EIP-712[​](#sign-typed-data-eip-712 "Direct link to Sign Typed Data EIP-712") Signing a Message ``` import * as dotenv from 'dotenv'; import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { Contract, JsonRpcProvider, Wallet, hashMessage} from "ethers"; // v6 dotenv.config(); const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress], ) const chainId = BigInt(process.env.CHAIN_ID as string) const message = hashMessage("Hello World"); const domain = { chainId, verifyingContract: smartAccount.accountAddress }; const types = { SafeMessage: [ { type: "bytes", name: "message" }, ], }; const value = { message }; // For demo purposes, we are signing directly using etheres Wallet. // You can also request this signature from the users MPC service if you are using one const ownerPrivateKey = process.env.PRIVATE_KEY as string const signer = new Wallet(ownerPrivateKey); const signature = await signer.signTypedData(domain, types, value); ``` Verifying the Signature ``` const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string const provider = new JsonRpcProvider(jsonRpcNodeProvider, chainId); const abiSafeWallet = [ "function isValidSignature(bytes32 _dataHash, bytes calldata _signature) external view returns (bytes4)", ]; const safeContract = new Contract( smartAccount.accountAddress, abiSafeWallet, provider, ); const returnValue = await safeContract.isValidSignature( message, signature, ); console.log(returnValue, "Success! You should see the magic value 0x1626ba7e"); ``` --- # Simulate Transactions with Tenderly This guide demonstrates using AbstractionKit to simulate UserOperations and raw smart contract calldata with [Tenderly](https://tenderly.co/). Simulating transactions enables you to: * Test transaction behavior * Debug potential issues before on-chain execution * Verify outcomes and state changes * Preview transaction effects for end users before signing **Complete example**: [simulate-with-tenderly](https://github.com/candidelabs/abstractionkit-examples/blob/main/simulate-with-tenderly/simulate-with-tenderly.ts) ## Configuration[​](#configuration "Direct link to Configuration") Configure the following Tenderly settings to use their services: 1. Visit [dashboard.tenderly.co](https://dashboard.tenderly.co) 2. Create a free account 3. Go to Settings >> Integration ``` TENDERLY_ACCOUNT_SLUG= TENDERLY_PROJECT_SlUG= TENDERLY_ACCESS_KEY= ``` ## Simulate User Operation[​](#simulate-user-operation "Direct link to Simulate User Operation") ``` import { simulateUserOperationWithTenderlyAndCreateShareLink } from "abstractionkit"; const tenderlyAccountSlug = process.env.TENDERLY_ACCOUNT_SLUG as string const tenderlyProjectSlug = process.env.TENDERLY_PROJECT_SlUG as string const tenderlyAccessKey = process.env.TENDERLY_ACCESS_KEY as string const userOpSimulationLink = await simulateUserOperationWithTenderlyAndCreateShareLink( tenderlyAccountSlug, tenderlyProjectSlug, tenderlyAccessKey, chainId, entrypointAddress, userOperation ); console.log("userop simulation link: ", userOpSimulationLink); ``` ## Simulate Safe CallData[​](#simulate-safe-calldata "Direct link to Simulate Safe CallData") ``` import { SafeMultiChainSigAccountV1 as SafeAccount} from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount( [ownerPublicAddress], ); const metaTx: MetaTransaction = { to: "0x0000000000000000000000000000000000000000", value: 0n, data: "0x", }; const callDataSimulatedLink = await smartAccount.simulateCallDataWithTenderlyAndCreateShareLink( tenderlyAccountSlug, tenderlyProjectSlug, tenderlyAccessKey, nodeUrl, chainId, [metaTx], ); console.log("calldata simulation link: ", callDataSimulatedLink.callDataSimulationShareLink); ``` --- info Looking for a simpler approach? Candide offers a managed [Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md) service that handles key management for you. # Use Turnkey with AbstractionKit [Turnkey](https://turnkey.com) offers a flexible private key management solution leveraging AWS Nitro Systems to secure user keys. Fully leverage Account Abstraction by combining Turnkey with AbstractionKit to enable a trusted recoverer. Enabling the Account Recovery Module requires an on-chain transaction. For user convenience, batch recovery transactions and sponsor gas fees. Relevant links for additional information during this guide: * [High level explanation: How on-chain guardian recovery works](https://docs.candide.dev/wallet/plugins/recovery-with-guardians/) * [SDK: Guardian Recovery References](https://docs.candide.dev/blog/making-accounts-recoverable/) * [Turnkey Docs](https://docs.turnkey.com) * [Code Example on GitHub](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts) ## Installation[​](#installation "Direct link to Installation") ### Install required dependencies[​](#install-required-dependencies "Direct link to Install required dependencies") * ethers * viem ``` npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/ethers ``` ``` npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/viem ``` ### Create a Turnkey account[​](#create-a-turnkey-account "Direct link to Create a Turnkey account") Create an account and get the API keys on [Turnkey's Dashboard](https://app.turnkey.com). You will need: * Organization ID * Public API Key * Private API Key * Create a private/public key pair on Turnkey dashboard. This is Guardian Account. ### Configure .env file[​](#configure-env-file "Direct link to Configure .env file") Configure the values you created from Turnkey dashboard in an .env file, along with a node endpoint. ``` // turnkey guardian TURNKEY_PUBLIC_KEY= TURNKEY_PRIVATE_KEY= // For this demo, we exported the private key of the guardian account. In production, use Turnkey api TURNKEY_ORG_ID= TURNKEY_WALLET_ADDRESS= // candide BUNDLER_URL= JSON_RPC_NODE_PROVIDER= OWNER_PUBLIC_ADDRESS= NEW_ONWER_PUBLIC_ADDRESS= ``` ## Setup Guardian[​](#setup-guardian "Direct link to Setup Guardian") This step shows how to contrust the calldata for creating the userop to add a guardian. ``` import { SocialRecoveryModule } from "abstractionkit"; const srm = new SocialRecoveryModule(); const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( smartAccount.accountAddress, process.env.TURNKEY_PUBLIC_KEY, // Turnkey Guardian Address 1n //threshold ); let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.JSON_RPC_NODE_PROVIDER, process.env.BUNDLER_URL, ) ``` ## Initiate Recovery[​](#initiate-recovery "Direct link to Initiate Recovery") * ethers * viem ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { TurnkeySigner } from "@turnkey/ethers"; import { TurnkeyClient } from "@turnkey/http"; import { ApiKeyStamper } from "@turnkey/api-key-stamper"; const turnkeyClient = new TurnkeyClient( { baseUrl: "https://api.turnkey.com" }, new ApiKeyStamper({ apiPublicKey: process.env.TURNKEY_PUBLIC_KEY as string, apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY as string, }) ); const guardianSigner = new TurnkeySigner({ client: turnkeyClient, organizationId: process.env.TURNKEY_ORG_ID as string, signWith: process.env.TURNKEY_WALLET_ADDRESS as string, // For this demo, we manually generated the signer on turnkey dashboard }); const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction( smartAccount.address, [process.env.NEW_ONWER_PUBLIC_ADDRESS], 1, // new threshold true, // whether to auto-start execution of recovery ) // make sure to fund the guardian address on turnkey const sendTx = guardianSigner.sendTransaction({ to: initiateRecoveryMetaTx.to, data: initiateRecoveryMetaTx.data, value: 0, }); ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { createWalletClient, http } from "viem"; import { TurnkeyClient } from "@turnkey/http"; import { ApiKeyStamper } from "@turnkey/api-key-stamper"; import { createAccount } from "@turnkey/viem"; const turnkeyClient = new TurnkeyClient( { baseUrl: "https://api.turnkey.com" }, new ApiKeyStamper({ apiPublicKey: process.env.TURNKEY_PUBLIC_KEY as string, apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY as string, }), ); const turnkeyAccount = await createAccount({ client: turnkeyClient, organizationId: process.env.TURNKEY_ORG_ID as string, signWith: process.env.TURNKEY_WALLET_ADDRESS as string, // for this example, we manually generated the signer on turnkey dashboard }); const guardianSigner = createWalletClient({ account: turnkeyAccount, transport: http(process.env.JSON_RPC_NODE_PROVIDER as string), }); const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction( smartAccount.address, [process.env.NEW_ONWER_PUBLIC_ADDRESS], 1, // new threshold true, // whether to auto-start execution of recovery ) // make sure to fund the guardian address on turnkey const sendTx1 = await guardianSigner.sendTransaction({ to: initiateRecoveryMetaTx.to, data: initiateRecoveryMetaTx.data, }); ``` ## Finilize Recovery[​](#finilize-recovery "Direct link to Finilize Recovery") Wait for recovery period to pass before finilizing * ethers * viem ``` const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress) // Anyone can call the finilize function after the grace period is over const sendTx2 = await guardianSigner.sendTransaction({ to: finalizeRecoveryMetaTx.to, data: finalizeRecoveryMetaTx.data, }) ``` ``` const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress) // Anyone can call the finilize function after the grace period is over const sendTx2 = await guardianSigner.sendTransaction({ to: finalizeRecoveryMetaTx.to, data: finalizeRecoveryMetaTx.data, }) ``` --- # Welcome to Candide A developer platform for building Ethereum smart wallets: gas sponsorship, passkeys, chain abstraction, and account recovery, all on top of Safe contracts. In production since ERC-4337 launched in 2023, Candide is the longest running account abstraction infrastructure provider: millions of UserOperations bundled across hundreds of thousands of accounts. Every account contract in these docs ships with public audits from auditors such as OpenZeppelin, Ackee, Certora, Nethermind, and Cantina. ## Quick Start[​](#quick-start "Direct link to Quick Start") ``` npm install abstractionkit ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const account = SafeAccount.initializeNewAccount([ownerPublicKey]); console.log(account.accountAddress); // 0x… deterministic smart wallet address ``` The account you just created is a [Safe Unified Account](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md). It has the same address on every EVM chain, and a single signature can authorize execution across all of them. [Send your First Gasless Transaction](https://docs.candide.dev/wallet/guides/getting-started.md) [Create a smart account and submit your first gasless UserOperation with batched transactions](https://docs.candide.dev/wallet/guides/getting-started.md) [Pay Gas in ERC-20](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md) [Let users pay transaction fees in USDC, DAI, or any ERC-20 token](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md) [Chain Abstraction](https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started.md) [Sign once, execute on every chain. One account, one address, every EVM network](https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started.md) [EIP-7702 Account Upgrades](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) [Upgrade existing EOAs to smart accounts with Calibur (passkeys, multi-key) or Simple Account](https://docs.candide.dev/wallet/guides/getting-started-calibur.md) [Pay Gas in USDT on Solana](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md) [Send SPL transfers from accounts with zero SOL through the Solana Paymaster, built on Kora](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md) ## Core Features[​](#core-features "Direct link to Core Features") ### Onboarding & Deposits[​](#onboarding--deposits "Direct link to Onboarding & Deposits") * Let users deposit from any chain to wallet using a single address via [Forwarding Address](https://docs.candide.dev/forwarding-address/overview.md). ### Gas Abstraction[​](#gas-abstraction "Direct link to Gas Abstraction") * Gasless transactions: [Sponsor user fees](https://docs.candide.dev/wallet/guides/send-gasless-tx.md) with flexible rules and policies * ERC-20 gas payments: Let users [pay gas in any token](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md) (USDC, DAI, etc.). No ETH required ### Modern Authentication[​](#modern-authentication "Direct link to Modern Authentication") * Passkey integration: [Onchain WebAuthn](https://docs.candide.dev/wallet/plugins/passkeys.md) support for biometric login. No seed phrases * Social login: [Email and OAuth](https://docs.candide.dev/wallet/guides/authentication.md#social--email) integration ### Chain Abstraction[​](#chain-abstraction "Direct link to Chain Abstraction") * Sign once, execute on every chain: one signature authorizes [UserOperations across every chain](https://docs.candide.dev/wallet/guides/chain-abstraction-overview.md) you target * Same account everywhere: one address on every EVM network where Safe is deployed ### Advanced Transactions[​](#advanced-transactions "Direct link to Advanced Transactions") * Batch operations: [Execute multiple transactions](https://docs.candide.dev/wallet/guides/getting-started.md) atomically * Spending controls: [Set limits and policies](https://docs.candide.dev/wallet/plugins/allowance.md) for subscriptions and recurring payments ### Security[​](#security "Direct link to Security") * Account recovery: [Social Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md) or [traditional](https://docs.candide.dev/wallet/recovery/auth-api.md) recovery options * Multi-signature: A single account controlled with [multiple signatures](https://docs.candide.dev/wallet/guides/multisig.md) ## Developer Tools[​](#developer-tools "Direct link to Developer Tools") | Tool | What it does | | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | [AbstractionKit SDK](https://docs.candide.dev/wallet/abstractionkit/introduction.md) | TypeScript library for building and sending UserOperations. First-class support for Safe and EIP-7702 accounts. | | [Bundler](https://docs.candide.dev/wallet/bundler/rpc-methods.md) | ERC-4337 compliant nodes for submitting UserOperations on all major EVM chains | | [Paymaster](https://docs.candide.dev/wallet/paymaster/rpc-methods.md) | Sponsor gas or accept ERC-20 token payments with configurable policies | | [Solana Paymaster](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md) | Gasless Solana transactions with fees collected in USDT. Built on Kora | | [InstaGas](https://docs.candide.dev/instagas/overview.md) | Dashboard and [Platform API](https://docs.candide.dev/platform/overview.md) for managing gas sponsorship rules | | [Forwarding Address](https://docs.candide.dev/forwarding-address/overview.md) | One deposit address per user that routes funds from any supported chain to the destination wallet | | [Account Recovery](https://docs.candide.dev/wallet/recovery/overview.md) | Email/SMS recovery with an alert system and automatic on-chain execution | Access [Candide's Dashboard](https://dashboard.candide.dev) to get API keys and configure your integration. ## Chains Supported[​](#chains-supported "Direct link to Chains Supported") Candide is available on all major EVM equivalent networks, plus Solana through the [Solana Paymaster](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md). Reach out if you need a network added. Every chain runs the same [reliability guarantees](https://docs.candide.dev/wallet/api/reliability.md): 99.9% uptime SLA on Enterprise, verified every minute. ![Arbitrum](/img/networks/42161.png)Arbitrum ![Avalanche](/img/networks/43114.png)Avalanche ![Base](/img/networks/8453.png)Base ![BnB Chain](/img/networks/56.png)BnB Chain ![Celo](/img/networks/5567.png)Celo ![Ethereum](/img/networks/1.png)Ethereum ![Gnosis](/img/networks/100.svg)Gnosis ![HyperEVM](/img/networks/999.png)HyperEVM ![Optimism](/img/networks/10.png)Optimism ![Plasma](/img/networks/9745.svg)Plasma ![Polygon](/img/networks/137.png)Polygon ![Solana](/img/networks/solana.svg)Solana ![Somnia](/img/networks/5031.png)Somnia ![Tempo](/img/networks/4217.png)Tempo ![World Chain](/img/networks/480.svg)World Chain See the full list of [supported networks](https://docs.candide.dev/wallet/api/supported-networks.md), including testnets and extended networks. ## Support & Feedback[​](#support--feedback "Direct link to Support & Feedback") Find us on [Discord](https://discord.gg/7R4g9XcxDQ), [Twitter](https://twitter.com/candidelabs), [Telegram](https://t.me/heymarcopolo), or at . We have night owls and early birds on the team. Expect a reply regardless of your timezone. **Using AI to build faster**: Every page has a **Copy Page** button in the toolbar. Paste it directly into Claude or ChatGPT with a pre-filled prompt. For agents and IDE integrations like Claude Code or Cursor, point them to [`/llms.txt`](https://docs.candide.dev/llms.txt) for a full index, or install the [Candide agent skills](https://github.com/candidelabs/skills). --- # Paymaster API V3 RPC Methods JSON-RPC API reference for Candide's Paymaster V3 service. Visit the [dashboard](https://dashboard.candide.dev) to get an API key for Candide's Paymaster. ## EntryPoint v0.9[​](#entrypoint-v09 "Direct link to EntryPoint v0.9") EntryPoint v0.9 is ABI-compatible with v0.8 and exposes the same Paymaster API methods with the same UserOperation shape. Pass the v0.9 EntryPoint address (`0x433709009B8330FDa32311DF1C2AFA402eD8D009`) as the `entrypoint` parameter. Key v0.9 addition for the Paymaster API: * **Parallelizable Paymaster Signing**: A two-phase signing flow lets the dApp send the paymaster data to the wallet for signing in parallel with the paymaster producing its own signature. This is opted into via the `signingPhase` context parameter on `pm_getPaymasterData`, and eliminates the round-trip delay between the user signing and the paymaster signing. ### Parallelizable Paymaster Signing[​](#parallelizable-paymaster-signing "Direct link to Parallelizable Paymaster Signing") The two-phase flow uses a single context field, `signingPhase`, which can be `"commit"` or `"finalize"`. It is only valid on `pm_getPaymasterData` calls against the EntryPoint v0.9 entrypoint. 1. **Commit phase** — call `pm_getPaymasterData` with `context: { signingPhase: "commit", ... }`. The paymaster reserves gas limits, computes the paymaster data and its signature, stores the signed result in a server-side commitment keyed by `sender:nonce`, and returns the **unsigned** paymaster data so the wallet can compute the UserOp hash and sign it in parallel. 2. **Finalize phase** — once the user has signed the UserOp, call `pm_getPaymasterData` again with `context: { signingPhase: "finalize", ... }` and the same UserOp (unchanged between commit and finalize). The paymaster returns the previously committed signed paymaster data. If anything about the UserOp changes between the two calls, the commitment lookup fails and the call is rejected. ### pm\_supportedERC20Tokens[​](#pm_supportederc20tokens "Direct link to pm_supportedERC20Tokens") Same semantics and response shape as the [v0.8 method](#pm_supportederc20tokens). Pass the v0.9 EntryPoint address as `params[0]`. ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedERC20Tokens", "params": ["0x433709009B8330FDa32311DF1C2AFA402eD8D009"] } ``` ### pm\_sponsorUserOperation[​](#pm_sponsoruseroperation "Direct link to pm_sponsorUserOperation") Same semantics, UserOp shape, and response shape as the [v0.8 method](#pm_sponsoruseroperation). Pass the v0.9 EntryPoint address. * Request Types * Response Type | Parameter | Type | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce bigint Anti-replay parameter (see Semi-abstracted Nonce Support) factory string \| null Account factory address, only for new accounts (null if account already exists) factoryData string \| null Data for account factory (null if account already exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string \| null Address of paymaster contract (null if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step (null if no paymaster) paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code (null if no paymaster) paymasterData string \| null Data for paymaster (null if no paymaster) eip7702Auth Authorization7702Hex \| null EIP-7702 authorization data for EOA delegation (null if not using EIP-7702) signature string Data passed into the account to verify authorization. Resolves to '0x' when the user did not provide their signature yet | | entrypoint | `string` — EntryPoint v0.9 address | | context | Objectkey type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :--------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsorMetadata` | `object` | Information about the Gas Policy Sponsor | | `sponsorMetadata.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsorMetadata.description` | `string` | A short description of the sponsor | | `sponsorMetadata.url` | `string` | Website of the Sponsor | | `sponsorMetadata.icons` | `string[]` | Logos or Icons that the Sponsor is using to promote their Gas Policy | ### pm\_supportedEntryPoints[​](#pm_supportedentrypoints "Direct link to pm_supportedEntryPoints") Returns the entryPoint addresses supported by the paymaster. The response array includes the v0.9 EntryPoint address (`0x433709009B8330FDa32311DF1C2AFA402eD8D009`) when supported. See the [v0.8 reference](#pm_supportedentrypoints). ### pm\_chainId[​](#pm_chainid "Direct link to pm_chainId") Same as [v0.8 reference](#pm_chainid). ### pm\_getPaymasterStubData[​](#pm_getpaymasterstubdata "Direct link to pm_getPaymasterStubData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Same semantics, UserOp shape, and response shape as the [v0.8 method](#pm_getpaymasterstubdata). Pass the v0.9 EntryPoint address. The `signingPhase` context parameter is **not** valid here; use `pm_getPaymasterData` for the two-phase flow. ### pm\_getPaymasterData[​](#pm_getpaymasterdata "Direct link to pm_getPaymasterData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Same semantics, UserOp shape, and response shape as the [v0.8 method](#pm_getpaymasterdata). Pass the v0.9 EntryPoint address. To opt into parallelizable paymaster signing, set `context.signingPhase` to `"commit"` for the first call and `"finalize"` for the second call (see [Parallelizable Paymaster Signing](#parallelizable-paymaster-signing) above). ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x" }, "0x433709009B8330FDa32311DF1C2AFA402eD8D009", "0x01", { "signingPhase": "commit", "sponsorshipPolicyId": "1234" } ] } ``` ## EntryPoint v0.8[​](#entrypoint-v08 "Direct link to EntryPoint v0.8") EIP-7702 support is added with EntryPoint v0.8. You can use the Paymaster API to offer gas sponsorship to end users through full gas policies or by offering to pay gas in ERC-20 tokens. The Paymaster API for EntryPoint v0.8 is fully backward compatible and has no breaking changes from EntryPoint v0.7. ### pm\_supportedERC20Tokens[​](#pm_supportederc20tokens-1 "Direct link to pm_supportedERC20Tokens") Gets supported ERC-20 tokens for gas payments, alongside the paymaster metadata. #### Invocation[​](#invocation "Direct link to Invocation") ``` { "method": "pm_supportedERC20Tokens", "params": [entrypoint: string] } ``` #### Return[​](#return "Direct link to Return") ``` { "result": { paymasterMetadata: { name: string, description: string, icons: string[], address: string, sponsoredEventTopic: string, dummyPaymasterAndData: { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string }, }, tokens: [{ name: string, symbol: string, address: string, decimals: number, exchangeRate: string, }] } } ``` * Example Request * Example Response * Response Types ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedERC20Tokens", "params": ["0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterMetadata": { "name": "Candide Paymaster", "description": "Candide Paymaster a fast, secure and feature-rich 4337 Paymaster", "icons": [], "address": "0x7e3393ebA62DA6f555a5341E079e0F6585CE8c56", "sponsoredEventTopic": "0x13..fa1", "dummyPaymasterAndData": { "paymaster": "0x...", "paymasterData": "0x...", "paymasterVerificationGasLimit": "0x...", "paymasterPostOpGasLimit": "0x..." } }, "tokens": [{ "name": "Maker DAI", "symbol": "DAI", "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "decimal": "0x12", "exchangeRate": "0x0ddeb609310c89b1" }] } } ``` | Parameter | Type | | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | paymasterMetadata | Objectkey type descriptionname string Paymaster Name description string A short description for the paymaster service icons string\[] Icons associated with the paymaster service address string Paymaster Contract Address sponsoredEventTopic string Emitted topic event if a paymaster is being used dummyPaymasterAndData object Dummy values to use for estimating user operation gas before sponsorship dummyPaymasterAndData.paymaster string Address of paymaster contract dummyPaymasterAndData.paymasterData string Data for paymaster dummyPaymasterAndData.paymasterVerificationGasLimit string The amount of gas to allocate for the paymaster validation code dummyPaymasterAndData.paymasterPostOpGasLimit string The amount of gas to allocate for the paymaster post-operation code sponsorMetadata object Information about the Gas Policy Sponsor sponsorMetadata.name string Name of Sponsor who is sponsoring the Gas Policy sponsorMetadata.description string A short description of the sponsor sponsorMetadata.url string Website of the Sponsor sponsorMetadata.icons string\[] Logos or Icons that the Sponsor is using to promote their Gas Policy | | tokens | Object\[]key type descriptionname string Name of the ERC-20 Token symbol string Token Symbol address string Token contract address decimals number Decimals of token exchangeRate string Exchange rate of token | ### pm\_sponsorUserOperation[​](#pm_sponsoruseroperation-1 "Direct link to pm_sponsorUserOperation") Sends a UserOperation to a paymaster for off-chain verification. If approved, it will return the `paymasterData`. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason. #### Invocation[​](#invocation-1 "Direct link to Invocation") ``` { "method": "pm_sponsorUserOperation", "params": [userOperation: object, entrypoint: string, context: object] } ``` #### Return[​](#return-1 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsorMetadata?: { name: string, description: string, url: string, icons: string[], } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_sponsorUserOperation", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsorMetadata": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icons": ["https://aave.com/aave-2030-header.svg"], } } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :--------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsorMetadata` | `object` | Information about the Gas Policy Sponsor | | `sponsorMetadata.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsorMetadata.description` | `string` | A short description of the sponsor | | `sponsorMetadata.url` | `string` | Website of the Sponsor | | `sponsorMetadata.icons` | `string[]` | Logos or Icons that the Sponsor is using to promote their Gas Policy | ### pm\_supportedEntryPoints[​](#pm_supportedentrypoints-1 "Direct link to pm_supportedEntryPoints") Returns the entryPoint addresses supported by the paymaster. #### Invocation[​](#invocation-2 "Direct link to Invocation") ``` { "method": "pm_supportedEntryPoints", "params": [] } ``` #### Return[​](#return-2 "Direct link to Return") ``` { "result": string[] } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedEntryPoints", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x0000000071727de22e5e9d8baf0edac6f37da032", "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"] // Entrypoint addresses } ``` | key | type | description | | :------------ | :--------- | :-------------------------------------------------------- | | `entrypoints` | `string[]` | The EntryPoint Addresses that the paymaster is supporting | ### pm\_chainId[​](#pm_chainid-1 "Direct link to pm_chainId") Get Chain ID asks the paymaster service to return the chain\_id it currently supports #### Invocation[​](#invocation-3 "Direct link to Invocation") ``` { "method": "pm_chainId", "params": [] } ``` #### Return[​](#return-3 "Direct link to Return") ``` { "result": number } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_chainId", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": 1 } ``` | Parameter | Type | Description | | :-------- | :------- | :----------------------------------- | | chainId | `number` | Chain ID that the paymaster supports | ### pm\_getPaymasterStubData[​](#pm_getpaymasterstubdata-1 "Direct link to pm_getPaymasterStubData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation). It can return `isFinal: true` if the stub data is sufficient and no further data is needed. It receives a userOperation for off-chain verification. If approved, it will return the paymasterData. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason #### Invocation[​](#invocation-4 "Direct link to Invocation") ``` { "method": "pm_getPaymasterStubData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-4 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsor?: { name: string, description: string, url: string, icons: string[], } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterStubData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsor": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icons": ["https://aave.com/aave-2030-header.svg"], }, "isFinal": true, } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsor` | `object` | Information about the Gas Policy Sponsor | | `sponsor.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsor.description` | `string` | A short description of the sponsor | | `sponsor.url` | `string` | Website of the Sponsor | | `sponsor.icon` | `string` | Logo of the Sponsor to promote their Gas Policy | ### pm\_getPaymasterData[​](#pm_getpaymasterdata-1 "Direct link to pm_getPaymasterData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation). It receives a userOperation for off-chain verification. If approved, it will return the paymasterData. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason #### Invocation[​](#invocation-5 "Direct link to Invocation") ``` { "method": "pm_getPaymasterData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-5 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | ## Entrypoint V0.7[​](#entrypoint-v07 "Direct link to Entrypoint V0.7") ### pm\_supportedERC20Tokens[​](#pm_supportederc20tokens-2 "Direct link to pm_supportedERC20Tokens") Gets supported ERC-20 Tokens for gas payments, along side the paymaster metadata #### Invocation[​](#invocation-6 "Direct link to Invocation") ``` { "method": "pm_supportedERC20Tokens", "params": [entrypoint: string] } ``` #### Return[​](#return-6 "Direct link to Return") ``` { "result": { paymasterMetadata: { name: string, description: string, icons: string[], address: string, sponsoredEventTopic: string, dummyPaymasterAndData: { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string }, }, tokens: [{ name: string, symbol: string, address: string, decimals: number, exchangeRate: string, }] } } ``` * Example Request * Example Response * Response Types ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedERC20Tokens", "params": ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterMetadata": { "name": "Candide Paymaster", "description": "Candide Paymaster a fast, secure and feature-rich 4337 Paymaster", "icons": [], "address": "0x7e3393ebA62DA6f555a5341E079e0F6585CE8c56", "sponsoredEventTopic": "0x13..fa1", "dummyPaymasterAndData": { "paymaster": "0x...", "paymasterData": "0x...", "paymasterVerificationGasLimit": "0x...", "paymasterPostOpGasLimit": "0x..." } }, "tokens": [{ "name": "Maker DAI", "symbol": "DAI", "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "decimal": "0x12", "exchangeRate": "0x0ddeb609310c89b1" }] } } ``` | Parameter | Type | | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | paymasterMetadata | Objectkey type descriptionname string Paymaster Name description string A short description for the paymaster service icons string\[] Icons associated with the paymaster service address string Paymaster Contract Address sponsoredEventTopic string Emitted topic event if a paymaster is being used dummyPaymasterAndData object Dummy values to use for estimating user operation gas before sponsorship dummyPaymasterAndData.paymaster string Address of paymaster contract dummyPaymasterAndData.paymasterData string Data for paymaster dummyPaymasterAndData.paymasterVerificationGasLimit string The amount of gas to allocate for the paymaster validation code dummyPaymasterAndData.paymasterPostOpGasLimit string The amount of gas to allocate for the paymaster post-operation code sponsorMetadata object Information about the Gas Policy Sponsor sponsorMetadata.name string Name of Sponsor who is sponsoring the Gas Policy sponsorMetadata.description string A short description of the sponsor sponsorMetadata.url string Website of the Sponsor sponsorMetadata.icons string\[] Logos or Icons that the Sponsor is using to promote their Gas Policy | | tokens | Object\[]key type descriptionname string Name of the ERC-20 Token symbol string Token Symbol address string Token contract address decimals number Decimals of token exchangeRate string Exchange rate of token | ### pm\_sponsorUserOperation[​](#pm_sponsoruseroperation-2 "Direct link to pm_sponsorUserOperation") Sends a UserOperation to a paymaster for off-chain verification. If approved, it will return the `paymasterData`. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason. #### Invocation[​](#invocation-7 "Direct link to Invocation") ``` { "method": "pm_sponsorUserOperation", "params": [userOperation: object, entrypoint: string, context: object] } ``` #### Return[​](#return-7 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsorMetadata?: { name: string, description: string, url: string, icons: string[], } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_sponsorUserOperation", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsorMetadata": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icons": ["https://aave.com/aave-2030-header.svg"], } } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :--------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsorMetadata` | `object` | Information about the Gas Policy Sponsor | | `sponsorMetadata.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsorMetadata.description` | `string` | A short description of the sponsor | | `sponsorMetadata.url` | `string` | Website of the Sponsor | | `sponsorMetadata.icons` | `string[]` | Logos or Icons that the Sponsor is using to promote their Gas Policy | ### pm\_supportedEntryPoints[​](#pm_supportedentrypoints-2 "Direct link to pm_supportedEntryPoints") Returns the entryPoint addresses supported by the paymaster. #### Invocation[​](#invocation-8 "Direct link to Invocation") ``` { "method": "pm_supportedEntryPoints", "params": [] } ``` #### Return[​](#return-8 "Direct link to Return") ``` { "result": string[] } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedEntryPoints", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x0000000071727de22e5e9d8baf0edac6f37da032"] // Entrypoint addresses } ``` | key | type | description | | :------------ | :--------- | :-------------------------------------------------------- | | `entrypoints` | `string[]` | The EntryPoint Addresses that the paymaster is supporting | ### pm\_chainId[​](#pm_chainid-2 "Direct link to pm_chainId") Get Chain ID asks the paymaster service to return the chain\_id it currently supports #### Invocation[​](#invocation-9 "Direct link to Invocation") ``` { "method": "pm_chainId", "params": [] } ``` #### Return[​](#return-9 "Direct link to Return") ``` { "result": number } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_chainId", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": 1 } ``` | Parameter | Type | Description | | :-------- | :------- | :----------------------------------- | | chainId | `number` | Chain ID that the paymaster supports | ### pm\_getPaymasterStubData[​](#pm_getpaymasterstubdata-2 "Direct link to pm_getPaymasterStubData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation). It can return `isFinal: true` if the stub data is sufficient and no further data is needed. It receives a userOperation for off-chain verification. If approved, it will return the paymasterData. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason #### Invocation[​](#invocation-10 "Direct link to Invocation") ``` { "method": "pm_getPaymasterStubData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-10 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsor?: { name: string, description: string, url: string, icons: string[], } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterStubData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsor": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icons": ["https://aave.com/aave-2030-header.svg"], }, "isFinal": true, } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsor` | `object` | Information about the Gas Policy Sponsor | | `sponsor.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsor.description` | `string` | A short description of the sponsor | | `sponsor.url` | `string` | Website of the Sponsor | | `sponsor.icon` | `string` | Logo of the Sponsor to promote their Gas Policy | ### pm\_getPaymasterData[​](#pm_getpaymasterdata-2 "Direct link to pm_getPaymasterData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation). It receives a userOperation for off-chain verification. If approved, it will return the paymasterData. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason #### Invocation[​](#invocation-11 "Direct link to Invocation") ``` { "method": "pm_getPaymasterData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-11 "Direct link to Return") ``` { "result": { paymaster: string, paymasterData: string, paymasterVerificationGasLimit: string, paymasterPostOpGasLimit: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "factory": "0x..", "factoryData": "0xf3....0000", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymaster": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "paymasterAndData": "0x", "signature": "0x", }, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymaster": "0x..", "paymasterData": "0x..", "paymasterVerificationGasLimit": "0x..", "paymasterPostOpGasLimit": "0x..", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", } } ``` | Parameter | Type | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) factory string account factory, only for new accounts factoryData string data for account factory (only if account factory exists) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint Extra gas to pay the bunder maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymaster string Address of paymaster contract, (or empty, if account pays for itself) paymasterVerificationGasLimit bigint \| null The amount of gas to allocate for the paymaster verification step paymasterPostOpGasLimit bigint \| null The amount of gas to allocate for the paymaster post-operation code paymasterData string Data for paymaster (only if paymaster exists) signature string Data passed into the account to verify authorization | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :------------------------------ | :------- | :------------------------------------------------------------------------------ | | `paymaster` | `string` | Address of paymaster contract, (or empty, if account pays for itself) | | `paymasterVerificationGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterPostOpGasLimit` | `string` | The amount of gas to allocate for the paymaster post-operation code | | `paymasterData` | `string` | Data for paymaster (only if paymaster exists) | | `callGasLimit` | `string` | The amount of gas to allocate the main execution call | | `verificationGasLimit` | `string` | The amount of gas to allocate for the verification step | | `preVerificationGas` | `string` | Extra gas to pay the bunder | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | ## Entrypoint V0.6[​](#entrypoint-v06 "Direct link to Entrypoint V0.6") ### pm\_supportedERC20Tokens[​](#pm_supportederc20tokens-3 "Direct link to pm_supportedERC20Tokens") Gets supported ERC-20 Tokens for gas payments, along side the paymaster metadata #### Invocation[​](#invocation-12 "Direct link to Invocation") ``` { "method": "pm_supportedERC20Tokens", "params": [entrypoint: string] } ``` #### Return[​](#return-12 "Direct link to Return") ``` { "result": { paymasterMetadata: { name: string, description: string, icons: string[], address: string, sponsoredEventTopic: string, dummyPaymasterAndData: string, }, tokens: [{ name: string, symbol: string, address: string, decimals: number, exchangeRate: string, }] } } ``` * Example Request * Example Response * Response Types ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedERC20Tokens", "params": ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterMetadata": { "name": "Candide Paymaster", "description": "Candide Paymaster a fast, secure and feature-rich 4337 Paymaster", "icons": [], "address": "0x7e3393ebA62DA6f555a5341E079e0F6585CE8c56", "sponsoredEventTopic": "0x13..fa1", "dummyPaymasterAndData": "0x69...135", }, "tokens": [{ "name": "Maker DAI", "symbol": "DAI", "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "decimal": "0x12", "exchangeRate": "0x0ddeb609310c89b1" }] } } ``` | Parameter | Type | | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | paymasterMetadata | Objectkey type descriptionname string Paymaster Name description string A short description for the paymaster service icons string\[] icons associated with the paymaster service address string Paymaster Contract Address sponsoredEventTopic string Emmited topic event if a paymaster is being used dummyPaymasterAndData string Dummy value to use for estimating userop gas before sponsorship | | tokens | Object\[]key type descriptionname string Name of the ERC-20 Token symbol string Token Symbol address string Token contract address decimals number Decimals of token exchangeRate string Exchange rate of token | ### pm\_sponsorUserOperation[​](#pm_sponsoruseroperation-3 "Direct link to pm_sponsorUserOperation") Sends a UserOperation to a paymaster for off-chain verification. If approved, it will return the `paymasterData`. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason. #### Invocation[​](#invocation-13 "Direct link to Invocation") ``` { "method": "pm_sponsorUserOperation", "params": [userOperation: object, entrypoint: string, context: object] } ``` #### Return[​](#return-13 "Direct link to Return") ``` { "result": { paymasterAndData: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsorMetadata?: { name: string, description: string, url: string, icons: string[], } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_sponsorUserOperation", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "initCode": "0x", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymasterAndData": "0x", "signature": "0x", }, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterAndData": "0x....", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsorMetadata": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icons": ["https://aave.com/aave-2030-header.svg"], } } } ``` | Parameter | Type | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) initCode string The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymasterAndData string Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas signature string The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | entrypoint | Type Descriptionstring Entrypoint address | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :--------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- | | `paymasterAndData` | `string` | Paymaster address and data required to pass paymaster contract validation | | `callGasLimit` | `string?` | The amount of gas to allocate the main execution call. Optional return. | | `verificationGasLimit` | `string?` | The amount of gas to allocate for the verification step. Optional return. | | `preVerificationGas` | `string?` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata. Optional return. | | `maxFeePerGas` | `string?` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas). Optional Return. | | `maxPriorityFeePerGas` | `string?` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas). Optional Return | ### pm\_supportedEntryPoints[​](#pm_supportedentrypoints-3 "Direct link to pm_supportedEntryPoints") Returns the entryPoint addresses supported by the paymaster. #### Invocation[​](#invocation-14 "Direct link to Invocation") ``` { "method": "pm_supportedEntryPoints", "params": [] } ``` #### Return[​](#return-14 "Direct link to Return") ``` { "result": string[] } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_supportedEntryPoints", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x0000000071727de22e5e9d8baf0edac6f37da032"] // Entrypoint addresses } ``` | key | type | description | | :------------ | :--------- | :-------------------------------------------------------- | | `entrypoints` | `string[]` | The EntryPoint Addresses that the paymaster is supporting | ### pm\_chainId[​](#pm_chainid-3 "Direct link to pm_chainId") Get Chain ID asks the paymaster service to return the chain\_id it currently supports #### Invocation[​](#invocation-15 "Direct link to Invocation") ``` { "method": "pm_chainId", "params": [] } ``` #### Return[​](#return-15 "Direct link to Return") ``` { "result": number } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_chainId", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": 1 } ``` | Parameter | Type | Description | | :-------- | :------- | :----------------------------------- | | chainId | `number` | Chain ID that the paymaster supports | ### pm\_getPaymasterStubData[​](#pm_getpaymasterstubdata-3 "Direct link to pm_getPaymasterStubData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation-1). It can return `isFinal: true` if the stub data is sufficient and no further data is needed. Sends a UserOperation to a paymaster for off-chain verification. If approved, it will return the `paymasterAndData`. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason. #### Invocation[​](#invocation-16 "Direct link to Invocation") ``` { "method": "pm_getPaymasterStubData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-16 "Direct link to Return") ``` { "result": { paymasterAndData: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, sponsor?: { name: string, icon?: string, } } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterStubData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "initCode": "0x", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymasterAndData": "0x", "signature": "0x", }, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterAndData": "0x....", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsor": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icon": "https://aave.com/aave-2030-header.svg", }, } } ``` | Parameter | Type | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) initCode string The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymasterAndData string Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas signature string The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :--------------------- | :-------- | :------------------------------------------------------------------------------------------------- | | `paymasterAndData` | `string` | Paymaster address and data required to pass paymaster contract validation | | `preVerificationGas` | `string` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `isFinal` | `boolean` | Indicates that the caller does not need to call pm\_getPaymasterData | | `sponsor` | `object` | Information about the Gas Policy Sponsor | | `sponsor.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsor.description` | `string` | A short description of the sponsor | | `sponsor.url` | `string` | Website of the Sponsor | | `sponsor.icon` | `string` | Logo of the Sponsor to promote their Gas Policy | ### pm\_getPaymasterData[​](#pm_getpaymasterdata-3 "Direct link to pm_getPaymasterData") [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compatible method. Equivalent to Candide's [`pm_sponsorUserOperation`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_sponsoruseroperation-1). Sends a UserOperation to a paymaster for off-chain verification. If approved, it will return the `paymasterAndData`. It will also return gas overrides when the mode for gas sponsorship uses a Gas Policy. * **ERC-20 Gas Payments**: No gas overrides are returned. * **Gas Policies**: Mandatory `callGasLimit`, `verificationGasLimit`, `preVerificationGas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields are returned. Supports two types of Gas Policies: * **Public Gas Policies**: These are gas policies provided by third parties, which do not require a sponsorship policy ID. * **Private Gas Policies**: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation. If the paymaster rejects the UserOperation, it returns a standard JSON-RPC error with the reason. #### Invocation[​](#invocation-17 "Direct link to Invocation") ``` { "method": "pm_getPaymasterData", "params": [userOperation: object, entrypoint: string, chainId: string, context: object] } ``` #### Return[​](#return-17 "Direct link to Return") ``` { "result": { paymasterAndData: string, callGasLimit?: string, verificationGasLimit?: string, preVerificationGas?: string, maxFeePerGas?: string, maxPriorityFeePerGas?: string, } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 0, "method": "pm_getPaymasterData", "params": [ { "sender": "0x2c298CcaFF..c236fCC66dB2", "nonce": "0x17", "initCode": "0x", "callData": "0xf3....0000", "callGasLimit": "0x12a87", "verificationGasLimit": "0x1a332", "preVerificationGas": "0xe95c", "maxFeePerGas": "0x5f4ecdc0", "maxPriorityFeePerGas": "0x59682f00", "paymasterAndData": "0x", "signature": "0x", }, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x01", { "sponsorshipPolicyId" : "1234" } // sponsorshipPolicyId key value field is optional if using a private gas policy. ] } ``` ``` { "jsonrpc": "2.0", "id": 0, "result": { "paymasterAndData": "0x....", "callGasLimit": "0x...", "verificationGasLimit": "0x...", "preVerificationGas": "0x..", "maxFeePerGas": "0x..", "maxPriorityFeePerGas": "0x..", "sponsor": { "name": "Aave", "description": "Open Source Liquidity Protocol", "url": "https://app.aave.com/", "icon": "https://aave.com/aave-2030-header.svg", }, } } ``` | Parameter | Type | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | userOperation | Objectkey type descriptionsender string The account making the operation nonce string Anti-replay parameter (see “Semi-abstracted Nonce Support” ) initCode string The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) callData string The data to pass to the sender during the main execution call callGasLimit bigint The amount of gas to allocate the main execution call verificationGasLimit bigint The amount of gas to allocate for the verification step preVerificationGas bigint The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata maxFeePerGas bigint Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) maxPriorityFeePerGas bigint Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) paymasterAndData string Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction). Revolves to '0x' if not using a paymaster, and a paymasterDummyData when estimating gas signature string The signature for the userOperation. It is the data passed into the account along with the nonce during the verification step. Resolves to '0x' when the user did not provide their signature yet | | entrypoint | Type Descriptionstring Entrypoint address | | chainId | Type Descriptionstring Chain ID in hex string | | context | Object: This argument contains information about the specific paymaster implementation you are using. If use paying gas in ERC-20 Tokens, pass the token address.key type descriptiontoken string? ERC20 token address, if paying gas in erc-20 tokens (optional) sponsorshipPolicyId string? Sponsorship Policy ID if using a private gas policy (optional) signingPhase "commit" \| "finalize"? Opt into the parallel signing two-phase flow (EntryPoint v0.9 only). "commit" on the first call reserves gas limits and returns init paymaster fields so owners can sign in parallel; "finalize" on the second call swaps the placeholder for the real paymaster signature. | | key | type | description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------- | | `paymasterAndData` | `string` | Paymaster address and data required to pass paymaster contract validation | | `preVerificationGas` | `string` | The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata | | `maxFeePerGas` | `string` | Maximum fee per gas (similar to EIP-1559 max\_fee\_per\_gas) | | `maxPriorityFeePerGas` | `string` | Maximum priority fee per gas (similar to EIP-1559 max\_priority\_fee\_per\_gas) | | `sponsor` | `object` | Information about the Gas Policy Sponsor | | `sponsor.name` | `string` | Name of Sponsor who is sponsoring the Gas Policy | | `sponsor.description` | `string` | A short description of the sponsor | | `sponsor.url` | `string` | Website of the Sponsor | | `sponsor.icon` | `string` | Logo of the Sponsor to promote their Gas Policy | --- # Supported Gas Payment Tokens Candide's Paymaster lets users pay gas with supported tokens instead of a network's native token. Visit the [dashboard](https://dashboard.candide.dev) to get an API key. The tables below show the documented baseline. Availability can vary by API key, so use these methods as the source of truth: * On EVM networks, call [`pm_supportedERC20Tokens`](https://docs.candide.dev/wallet/paymaster/rpc-methods.md#pm_supportederc20tokens) or use the SDK's [`fetchSupportedERC20TokensAndPaymasterMetadata`](https://docs.candide.dev/wallet/abstractionkit/safe-account/#fetchsupportederc20tokensandpaymastermetadata) method * On Solana, call [`getSupportedTokens`](https://docs.candide.dev/wallet/solana-paymaster/rpc-methods.md#getsupportedtokens) info If you would like to see a token or network supported, email ## Mainnets[​](#mainnets "Direct link to Mainnets") ### Arbitrum[​](#arbitrum "Direct link to Arbitrum") | Token | Contract / Mint Address | Website | | :-------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | ![](/img/tokens/dai-logo.png)DAI | [0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1](https://arbiscan.io/token/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/EURe.png)EURe (Monerium EUR) | [0x0c06cCF38114ddfc35e07427B9424adcca9F44F8](https://arbiscan.io/token/0x0c06cCF38114ddfc35e07427B9424adcca9F44F8) | [monerium.com](https://monerium.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Native) | [0xaf88d065e77c8cC2239327C5EDb3A432268e5831](https://arbiscan.io/token/0xaf88d065e77c8cC2239327C5EDb3A432268e5831) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9](https://arbiscan.io/token/0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) | [tether.io](https://tether.io/) | | ![](/img/tokens/xaut-tether-gold-logo.svg)XAU₮0 (Tether Gold Bridged) | [0x40461291347e1eCbb09499F3371D3f17f10d7159](https://arbiscan.io/token/0x40461291347e1eCbb09499F3371D3f17f10d7159) | [gold.usdt0.to](https://gold.usdt0.to/) | ### Avalanche[​](#avalanche "Direct link to Avalanche") | Token | Contract / Mint Address | Website | | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------ | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7](https://snowscan.xyz/token/0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7) | [tether.io](https://tether.io/) | ### Base[​](#base "Direct link to Base") | Token | Contract / Mint Address | Website | | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------ | | ![](/img/tokens/dai-logo.png)DAI | [0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb](https://basescan.org/token/0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Native) | [0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913](https://basescan.org/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usds-logo.svg)USDS | [0x820C137fa70C8691f0e44Dc420a5e53c168921Dc](https://basescan.org/token/0x820C137fa70C8691f0e44Dc420a5e53c168921Dc) | [sky.money](https://sky.money/) | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2](https://basescan.org/token/0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2) | [tether.io](https://tether.io/) | ### Binance Smart Chain[​](#binance-smart-chain "Direct link to Binance Smart Chain") | Token | Contract / Mint Address | Website | | :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | ![](/img/tokens/dai-logo.png)DAI (Binance-Peg) | [0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3](https://bscscan.com/token/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/busdt-logo.png)BSC-USD (ex USDT) | [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/token/0x55d398326f99059fF775485246999027B3197955) | [binance.com](https://www.binance.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Binance-Peg) | [0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d](https://bscscan.com/token/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | [circle.com](https://www.circle.com/) | ### Celo[​](#celo "Direct link to Celo") | Token | Contract / Mint Address | Website | | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | ![](/img/tokens/cUSD.png)cUSD | [0x765de816845861e75a25fca122bb6898b8b1282a](https://celoscan.io/token/0x765de816845861e75a25fca122bb6898b8b1282a) | [celo.org](https://celo.org/) | | ![](/img/tokens/cEUR.png)cEUR | [0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73](https://celoscan.io/token/0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73) | [celo.org](https://celo.org/) | | ![](/img/tokens/usdc-logo.svg)USDC (Native) | [0xcebA9300f2b948710d2653dD7B07f33A8B32118C](https://celoscan.io/token/0xcebA9300f2b948710d2653dD7B07f33A8B32118C) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdt-logo.svg)USDT (Native) | [0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e](https://celoscan.io/token/0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) | [tether.io](https://tether.io/) | ### Ethereum[​](#ethereum "Direct link to Ethereum") | Token | Contract / Mint Address | Website | | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :--------------------------------------- | | ![](/img/tokens/dai-logo.png)DAI | [0x6B175474E89094C44Da98b954EedeAC495271d0F](https://etherscan.io/token/0x6B175474E89094C44Da98b954EedeAC495271d0F) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/EURe.png)EURe (Monerium EUR) | [0x39b8B6385416f4cA36a20319F70D28621895279D](https://etherscan.io/token/0x39b8B6385416f4cA36a20319F70D28621895279D) | [monerium.com](https://monerium.com/) | | ![](/img/tokens/usdc-logo.svg)USDC | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usds-logo.svg)USDS | [0xdC035D45d973E3EC169d2276DDab16f1e407384F](https://etherscan.io/token/0xdC035D45d973E3EC169d2276DDab16f1e407384F) | [sky.money](https://sky.money/) | | ![](/img/tokens/usdt-logo.svg)USDT (Native) | [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7) | [tether.io](https://tether.io/) | | ![](/img/tokens/xaut-tether-gold-logo.svg)XAU₮ (Tether Gold) | [0x68749665FF8D2d112Fa859AA293F07A622782F38](https://etherscan.io/token/0x68749665FF8D2d112Fa859AA293F07A622782F38) | [gold.tether.to](https://gold.tether.to) | | ![](/img/tokens/usat-logo.svg)USA₮ | [0x07041776f5007aca2a54844f50503a18a72a8b68](https://etherscan.io/token/0x07041776f5007aca2a54844f50503a18a72a8b68) | [usat.io](https://usat.io/) | ### Gnosis[​](#gnosis "Direct link to Gnosis") | Token | Contract / Mint Address | Website | | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | ![](/img/tokens/EURe.png)EURe (Monerium EUR) | [0x420CA0f9B9b604cE0fd9C18EF134C705e5Fa3430](https://gnosisscan.io/token/0x420CA0f9B9b604cE0fd9C18EF134C705e5Fa3430) | [monerium.com](https://monerium.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Bridged) | [0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83](https://gnosisscan.io/token/0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0x4ECaBa5870353805a9F068101A40E0f32ed605C6](https://gnosisscan.io/token/0x4ECaBa5870353805a9F068101A40E0f32ed605C6) | [tether.io](https://tether.io/) | ### Optimism[​](#optimism "Direct link to Optimism") | Token | Contract / Mint Address | Website | | :------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | ![](/img/tokens/dai-logo.png)DAI | [0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1](https://optimistic.etherscan.io/token/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Bridged) | [0x7F5c764cBc14f9669B88837ca1490cCa17c31607](https://optimistic.etherscan.io/token/0x7F5c764cBc14f9669B88837ca1490cCa17c31607) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Native) | [0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85](https://optimistic.etherscan.io/token/0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0x94b008aA00579c1307B0EF2c499aD98a8ce58e58](https://optimistic.etherscan.io/token/0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) | [tether.io](https://tether.io/) | ### Polygon POS[​](#polygon-pos "Direct link to Polygon POS") | Token | Contract / Mint Address | Website | | :-------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | ![](/img/tokens/dai-logo.png)DAI | [0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063](https://polygonscan.com/token/0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063) | [makerdao.com](https://makerdao.com/) | | ![](/img/tokens/EURe.png)EURe (Monerium EUR) | [0xE0aEa583266584DafBB3f9C3211d5588c73fEa8d](https://polygonscan.com/token/0xE0aEa583266584DafBB3f9C3211d5588c73fEa8d) | [monerium.com](https://monerium.com/) | | ![](/img/tokens/usdc-logo.svg)USDC (Native) | [0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359](https://polygonscan.com/token/0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdc-logo.svg)USDC.e (Bridged) | [0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174](https://polygonscan.com/token/0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) | [circle.com](https://www.circle.com/) | | ![](/img/tokens/usdt-logo.svg)USDT (Bridged) | [0xc2132D05D31c914a87C6611C10748AEb04B58e8F](https://polygonscan.com/token/0xc2132D05D31c914a87C6611C10748AEb04B58e8F) | [tether.io](https://tether.io/) | | ![](/img/tokens/xaut-tether-gold-logo.svg)XAU₮0 (Tether Gold Bridged) | [0xF1815bd50389c46847f0Bda824eC8da914045D14](https://polygonscan.com/token/0xF1815bd50389c46847f0Bda824eC8da914045D14) | [gold.usdt0.to](https://gold.usdt0.to/) | ### Plasma[​](#plasma "Direct link to Plasma") | Token | Contract / Mint Address | Website | | :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | ![](/img/tokens/usdt0.png)USDT0 | [0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb](https://plasmascan.to/token/0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb) | [usdt0.to](https://usdt0.to/) | | ![](/img/tokens/xaut-tether-gold-logo.svg)XAU₮0 (Tether Gold Bridged) | [0x1B64B9025EEbb9A6239575dF9Ea4b9Ac46D4d193](https://plasmascan.to/token/0x1B64B9025EEbb9A6239575dF9Ea4b9Ac46D4d193) | [gold.usdt0.to](https://gold.usdt0.to/) | ### Solana[​](#solana "Direct link to Solana") | Token | Contract / Mint Address | Website | | :--------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ | | ![](/img/tokens/usdt-logo.svg)USDT | [Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB](https://explorer.solana.com/address/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB) | [tether.io](https://tether.io/) | ### Worldchain Mainnet[​](#worldchain-mainnet "Direct link to Worldchain Mainnet") | Token | Contract / Mint Address | Website | | :--------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | ![](/img/tokens/usdc-logo.svg)USDC.e (Bridged) | [0x79A02482A880bCE3F13e09Da970dC34db4CD24d1](https://www.worldscan.org/token/0x79A02482A880bCE3F13e09Da970dC34db4CD24d1) | [circle.com](https://www.circle.com/) | | ![](/img/networks/480.svg)WLD | [0x2cFc85d8E48F8EAB294be644d9E25C3030863003](https://www.worldscan.org/token/0x2cFc85d8E48F8EAB294be644d9E25C3030863003) | [worldcoin.org](https://worldcoin.org/) | ## Testnets[​](#testnets "Direct link to Testnets") info Looking for test tokens? Get ERC-20 faucet tokens (CTT or USDT) from our [dashboard faucet](https://dashboard.candide.dev/faucet). ### Arbitrum Sepolia[​](#arbitrum-sepolia "Direct link to Arbitrum Sepolia") | Token | Contract / Mint Address | Website | | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | ![](/img/tokens/ctt-logo.png)Candide Test Token | [0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1](https://sepolia.arbiscan.io/token/0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1) | [candide.dev](https://candide.dev/) | ### Base Sepolia[​](#base-sepolia "Direct link to Base Sepolia") | Token | Contract / Mint Address | Website | | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | ![](/img/tokens/ctt-logo.png)Candide Test Token | [0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1](https://sepolia.basescan.org/token/0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1) | [candide.dev](https://candide.dev/) | ### Sepolia[​](#sepolia "Direct link to Sepolia") | Token | Contract / Mint Address | Website | | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | ![](/img/tokens/ctt-logo.png)Candide Test Token | [0xFa5854FBf9964330d761961F46565AB7326e5a3b](https://sepolia.etherscan.io/token/0xFa5854FBf9964330d761961F46565AB7326e5a3b) | [candide.dev](https://candide.dev/) | | ![](/img/tokens/usdt-logo.svg)USD₮ | [0xd077A400968890Eacc75cdc901F0356c943e4fDb](https://sepolia.etherscan.io/token/0xd077A400968890Eacc75cdc901F0356c943e4fDb) | [tether.io](https://tether.io/) | ### Optimism Sepolia[​](#optimism-sepolia "Direct link to Optimism Sepolia") | Token | Contract / Mint Address | Website | | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | ![](/img/tokens/ctt-logo.png)Candide Test Token | [0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1](https://sepolia-optimism.etherscan.io/token/0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1) | [candide.dev](https://candide.dev/) | ### Polygon Amoy[​](#polygon-amoy "Direct link to Polygon Amoy") | Token | Contract / Mint Address | Website | | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | ![](/img/tokens/ctt-logo.png)Candide Test Token | [0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1](https://amoy.polygonscan.com/token/0xE0BD422189D77cD1AC77C520B363a7FA649FFdf1) | [candide.dev](https://candide.dev/) | --- # Enable Email / SMS Recovery Integrate [Candide Guardian](https://docs.candide.dev/wallet/recovery/auth-api.md) as a trusted recovery service for user Safe accounts. Candide Guardian provides secure account recovery through email and SMS verification. info The Recovery Service requires access credentials. [Request access here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). > To set up the recovery module, see [Enable Recovery Module and Add Guardians](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md). ## What is Candide Guardian?[​](#what-is-candide-guardian "Direct link to What is Candide Guardian?") Candide Guardian is a managed recovery service acting as a trusted guardian for user Smart Accounts. Unlike personal guardians, Candide Guardian provides: * **Familiar Security**: Email, SMS, or both for recovery verification * **No Key Management**: Users don't manage guardian private keys * **Automated Signing**: Service automatically provides guardian signatures after verification * **Grace Period Protection**: Built-in security delay before recovery execution * **Gas Sponsorship**: Sponsored recovery transactions remove gas barriers ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete code](https://github.com/candidelabs/safe-recovery-service-sdk/blob/main/examples/01-enable-email-sms-recovery/index.ts) and follow along. ### Installation[​](#installation "Direct link to Installation") * npm * yarn ``` npm i abstractionkit safe-recovery-service-sdk viem ``` ``` yarn add abstractionkit safe-recovery-service-sdk viem ``` ### Configure Environment[​](#configure-environment "Direct link to Configure Environment") * .env ``` # Network Configuration CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 NODE_URL=https://ethereum-sepolia-rpc.publicnode.com # Recovery Service URL # Get access here: https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5 RECOVERY_SERVICE_URL= ``` ## Step 1: Initialize Service[​](#step-1-initialize-service "Direct link to Step 1: Initialize Service") ``` import { RecoveryByCustodialGuardian } from "safe-recovery-service-sdk"; const chainId = BigInt(process.env.CHAIN_ID as string); const serviceUrl = process.env.RECOVERY_SERVICE_URL as string; const custodialGuardianService = new RecoveryByCustodialGuardian(serviceUrl, chainId); ``` ## Step 2: Create Registration Statement[​](#step-2-create-registration-statement "Direct link to Step 2: Create Registration Statement") Generate a SIWE (Sign-In with Ethereum) statement for guardian registration. Make sure the Safe account is already deployed. * Email Registration * SMS Registration ``` const siweStatementToSign = await custodialGuardianService.createRegistrationToEmailRecoverySiweStatementToSign( smartAccount.accountAddress, "user@example.com" ); ``` ``` const siweStatementToSign = await custodialGuardianService.createRegistrationToSmsRecoverySiweStatementToSign( smartAccount.accountAddress, "+1234567890" ); ``` ## Step 3: Sign and Submit Registration[​](#step-3-sign-and-submit-registration "Direct link to Step 3: Sign and Submit Registration") Sign the SIWE statement with the Smart Account owner key and submit registration: * Email Registration * SMS Registration ``` import { SafeMultiChainSigAccountV1 as SafeAccount, getSafeMessageEip712Data, SAFE_MESSAGE_PRIMARY_TYPE } from "abstractionkit"; import { TypedDataDomain } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; const ownerAccount = privateKeyToAccount(ownerPrivateKey); const emailSafeTypedData = getSafeMessageEip712Data( smartAccount.accountAddress, chainId, emailRegistrationSiweMessage ); const emailOwnerSignature = await ownerAccount.signTypedData({ domain: emailSafeTypedData.domain as TypedDataDomain, types: emailSafeTypedData.types, primaryType: SAFE_MESSAGE_PRIMARY_TYPE, message: emailSafeTypedData.messageValue }); const emailRegistrationSignature = SafeAccount.buildSignaturesFromSingerSignaturePairs([ { signer: ownerAccount.address, signature: emailOwnerSignature } ]); const emailRegistrationChallengeId = await custodialGuardianService.createRegistrationToEmailRecovery( smartAccount.accountAddress, userEmail, emailRegistrationSiweMessage, emailRegistrationSignature ); ``` ``` import { SafeMultiChainSigAccountV1 as SafeAccount, getSafeMessageEip712Data, SAFE_MESSAGE_PRIMARY_TYPE } from "abstractionkit"; import { TypedDataDomain } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; const ownerAccount = privateKeyToAccount(ownerPrivateKey); const smsSafeTypedData = getSafeMessageEip712Data( smartAccount.accountAddress, chainId, smsRegistrationSiweMessage ); const smsOwnerSignature = await ownerAccount.signTypedData({ domain: smsSafeTypedData.domain as TypedDataDomain, types: smsSafeTypedData.types, primaryType: SAFE_MESSAGE_PRIMARY_TYPE, message: smsSafeTypedData.messageValue }); const smsRegistrationSignature = SafeAccount.buildSignaturesFromSingerSignaturePairs([ { signer: ownerAccount.address, signature: smsOwnerSignature } ]); // Create SMS registration const smsRegistrationChallengeId = await custodialGuardianService.createRegistrationToSmsRecovery( smartAccount.accountAddress, userPhone, smsRegistrationSiweMessage, smsRegistrationSignature ); ``` ## Step 4: Verify OTP Code[​](#step-4-verify-otp-code "Direct link to Step 4: Verify OTP Code") Submit the OTP code received via email or SMS: * Email Verification * SMS Verification ``` // User enters OTP from email const otpCode = "123456"; const verificationResponse = await custodialGuardianService.submitRegistrationChallenge( emailRegistrationChallengeId, // returned by createRegistrationToEmailRecovery() otpCode ); const candideGuardianAddress = verificationResponse.guardianAddress; ``` ``` // User enters OTP from SMS const otpCode = "123456"; const verificationResponse = await custodialGuardianService.submitRegistrationChallenge( smsRegistrationChallengeId, // returned by createRegistrationToSmsRecovery() otpCode ); const candideGuardianAddress = verificationResponse.guardianAddress; ``` ## Step 5: Enable Recovery Module and Add Guardian[​](#step-5-enable-recovery-module-and-add-guardian "Direct link to Step 5: Enable Recovery Module and Add Guardian") Add Candide Guardian to your Safe account. See the complete flow of adding a guardian in the [How to Add a Guardian](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md) guide ``` import { SocialRecoveryModule, SocialRecoveryModuleGracePeriodSelector } from "abstractionkit"; const srm = new SocialRecoveryModule(SocialRecoveryModuleGracePeriodSelector.After3Minutes); // Create transactions to enable module const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); // Add Candide Guardian const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( candideGuardianAddress, 1n // threshold ); // Create UserOp with both transactions let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.NODE_URL, process.env.BUNDLER_URL ); // sponsor gas, sign, and submit userop ``` Recovery Strategy Recommendation For maximum security, recommend Candide Guardian alongside personal guardians in a multi-guardian setup (e.g., 2 of 3: Candide Guardian + 2 personal guardians). That's it! Your Safe account is now protected by Candide Guardian. In case of key loss, you can use it as one of the Guardians setup using your registered email/phone verification to recover your account. ## Complete Working Example[​](#complete-working-example "Direct link to Complete Working Example") Full Working Example examples/01-enable-email-sms-recovery/index.ts ``` loading... ``` [See full example on GitHub]() ## What's Next[​](#whats-next "Direct link to What's Next") * [Recovery Flow](https://docs.candide.dev/wallet/plugins/recover-account-candide-guardian.md): walk through the full recovery flow using your registered email/SMS to regain access to a lost account --- # Allowance Module v1.0.0 Migration Guide The Allowance Module v1.0.0 is a security fix release of the Safe Allowance Module. The SDK API is identical to v0.1.1: same methods, same parameters, same types. The underlying contract has been updated with three security fixes. The new contract address is `0x691f59471Bfd2B7d639DCF74671a2d648ED1E331`, replacing the previous `0xAA46724893dedD72658219405185Fb0Fc91e091C`. Audited by [Certora and Ackee](#audits--references). ## For New Projects[​](#for-new-projects "Direct link to For New Projects") No special steps. Install the latest version of AbstractionKit and use the default constructor: terminal ``` npm install abstractionkit ``` ``` import { AllowanceModule } from "abstractionkit"; const allowanceModule = new AllowanceModule(); // uses v1.0.0 by default ``` Follow the [Allowance Guide](https://docs.candide.dev/wallet/plugins/allowance.md) as-is. The [example script](https://github.com/candidelabs/abstractionkit-examples/blob/main/spend-permission/spend-permission.ts) works without modification. ## For Existing Code[​](#for-existing-code "Direct link to For Existing Code") **If you use the default constructor** (`new AllowanceModule()`): bump `abstractionkit` to the latest version. The new address is picked up automatically. **If you were passing the old address explicitly**, remove it or switch to the default: ``` // Before (explicit old address) const allowanceModule = new AllowanceModule("0xAA46724893dedD72658219405185Fb0Fc91e091C"); // After (use the default) const allowanceModule = new AllowanceModule(); ``` No other code changes are needed. All method signatures and types remain the same. ## For Existing Safes with v0.1.1 Enabled[​](#for-existing-safes-with-v011-enabled "Direct link to For Existing Safes with v0.1.1 Enabled") If your Safe already has the v0.1.1 module enabled, the owner must enable the new v1.0.0 module separately and re-create delegates and allowances on the new contract. The old module can be disabled afterward. ### Step 1: Enable the v1.0.0 Module[​](#step-1-enable-the-v100-module "Direct link to Step 1: Enable the v1.0.0 Module") ``` import { AllowanceModule } from "abstractionkit"; const allowanceModule = new AllowanceModule(); // v1.0.0 const enableModuleMetaTx = allowanceModule.createEnableModuleMetaTransaction( safeAccountAddress, ); ``` ### Step 2: Re-create Delegates[​](#step-2-re-create-delegates "Direct link to Step 2: Re-create Delegates") Each delegate must be registered on the new module. ``` const addDelegateMetaTx = allowanceModule.createAddDelegateMetaTransaction( delegateAddress, ); ``` ### Step 3: Re-create Allowances[​](#step-3-re-create-allowances "Direct link to Step 3: Re-create Allowances") Set up the same allowances on the new contract. ``` // Recurring allowance const setAllowanceMetaTx = allowanceModule.createRecurringAllowanceMetaTransaction( delegateAddress, tokenAddress, allowanceAmount, resetPeriodInMinutes, 0n, // start delay ); ``` ### Step 4: Disable the Old Module[​](#step-4-disable-the-old-module "Direct link to Step 4: Disable the Old Module") Disable the v0.1.1 module so it can no longer be used to execute transfers. ``` const disableOldModuleMetaTx = await safeAccount.createDisableModuleMetaTransaction( nodeUrl, "0xAA46724893dedD72658219405185Fb0Fc91e091C", // v0.1.1 address safeAccountAddress, ); ``` ### Step 5: Submit as a Batched UserOperation[​](#step-5-submit-as-a-batched-useroperation "Direct link to Step 5: Submit as a Batched UserOperation") All four steps can be batched into a single UserOperation: ``` const userOp = await safeAccount.createUserOperation( [ enableModuleMetaTx, addDelegateMetaTx, setAllowanceMetaTx, disableOldModuleMetaTx, ], nodeUrl, bundlerUrl, ); userOp.signature = safeAccount.signUserOperation( userOp, [ownerPrivateKey], chainId, ); const response = await safeAccount.sendUserOperation(userOp, bundlerUrl); const receipt = await response.included(); ``` ## What Changed[​](#what-changed "Direct link to What Changed") The three fixes below are all security improvements. Only the nonce limit introduces a new constraint that could affect application logic. ### Nonce Limit: 65,534 Transfers per Delegate/Token[​](#nonce-limit-65534-transfers-per-delegatetoken "Direct link to Nonce Limit: 65,534 Transfers per Delegate/Token") v1.0.0 adds overflow protection on the `uint16` nonce. Each delegate can execute at most 65,534 `executeAllowanceTransfer` calls for a given token on a given Safe. After that, the contract reverts. Nonce Limit After 65,534 transfers, the delegate can no longer execute transfers for that token. The workaround is to register a new delegate address. This is unlikely to affect most use cases, but high-frequency automated delegates should account for it. ### Failed ERC-20 Transfers Now Revert[​](#failed-erc-20-transfers-now-revert "Direct link to Failed ERC-20 Transfers Now Revert") In v0.1.1, if an ERC-20 token's `transfer()` returned `false` instead of reverting (allowed by the ERC-20 spec), the allowance state was still updated. The delegate's spent amount increased even though no tokens moved. v1.0.0 fixes this by checking the return data. No developer action needed. ### Delegate Collision Safety in `removeDelegate`[​](#delegate-collision-safety-in-removedelegate "Direct link to delegate-collision-safety-in-removedelegate") v1.0.0 adds a `require(current.delegate == delegate)` check in `removeDelegate` to prevent accidentally removing the wrong delegate when two addresses share the same first 6 bytes (`uint48` key collision). No developer action needed. ## Compatibility[​](#compatibility "Direct link to Compatibility") v1.0.0 uses `execTransactionFromModuleReturnData`, which was introduced in Safe v1.1.1. This means v1.0.0 is not compatible with Safe v1.0.0. AbstractionKit targets Safe v1.4.1, so this is not a concern for current users. ## Audits & References[​](#audits--references "Direct link to Audits & References") * [Certora Audit Report](https://github.com/safe-fndn/safe-modules/tree/main/modules/allowances/docs/v1.0.0) * [safe-modules CHANGELOG](https://github.com/safe-global/safe-modules/blob/main/modules/allowances/CHANGELOG.md) * [Allowance Module Guide](https://docs.candide.dev/wallet/plugins/allowance.md) * [Allowance Module SDK Reference](https://docs.candide.dev/wallet/plugins/allowance-reference.md) --- # Allowance Module SDK Reference This page documents all methods available on the `AllowanceModule` class in AbstractionKit. For a step-by-step implementation guide, see [How to Enable Spend Permissions](https://docs.candide.dev/wallet/plugins/allowance.md). AbstractionKit provides two types of methods: 1. **Recommended methods**: Simplified interfaces with clear naming, documented under [Methods](#methods) below. 2. **Advanced methods**: Prefixed with `createBase`, these match exact contract method names and parameters. Documented under [Advanced Methods](#advanced-methods). The recommended methods cover most use cases. Use the advanced methods only when you need direct contract-level control. ## Methods[​](#methods "Direct link to Methods") ### createEnableModuleMetaTransaction[​](#createenablemodulemetatransaction "Direct link to createEnableModuleMetaTransaction") Creates a MetaTransaction to enable the Allowance Module for a Safe account. This only needs to be called once per Safe. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :-------------------------------------------------------- | | `accountAddress` | `string` | The target Safe Account address to enable the module for. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createAddDelegateMetaTransaction[​](#createadddelegatemetatransaction "Direct link to createAddDelegateMetaTransaction") Creates a MetaTransaction that adds a delegate to the account. The delegate can then be granted allowances to spend tokens. * Param Types * Return Types | key | type | description | | :--------- | :------- | :----------------------------------------- | | `delegate` | `string` | The delegate address that should be added. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createOneTimeAllowanceMetaTransaction[​](#createonetimeallowancemetatransaction "Direct link to createOneTimeAllowanceMetaTransaction") Creates a MetaTransaction that sets a one-time allowance for a delegate. The allowance does not reset after use. An optional start delay can be configured. * Param Types * Return Types | key | type | description | | :-------------------- | :------- | :---------------------------------------------------------- | | `delegate` | `string` | The address of the delegate to whom the allowance is given. | | `token` | `string` | The address of the token for which the allowance is set. | | `allowanceAmount` | `bigint` | The amount of the token allowed for the delegate. | | `startAfterInMinutes` | `bigint` | The delay in minutes before the allowance can be used. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createRecurringAllowanceMetaTransaction[​](#createrecurringallowancemetatransaction "Direct link to createRecurringAllowanceMetaTransaction") Creates a MetaTransaction that sets a recurring allowance for a delegate. The allowance automatically resets after the specified validity period. An optional start delay can be configured. * Param Types * Return Types | key | type | description | | :------------------------------------------ | :------- | :-------------------------------------------------------------------- | | `delegate` | `string` | The address of the delegate to whom the recurring allowance is given. | | `token` | `string` | The address of the token for which the allowance is set. | | `allowanceAmount` | `bigint` | The amount of the token allowed for the delegate. | | `recurringAllowanceValidityPeriodInMinutes` | `bigint` | The time period (in minutes) after which the allowance resets. | | `startAfterInMinutes` | `bigint` | The delay in minutes before the allowance can be used. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createAllowanceTransferMetaTransaction[​](#createallowancetransfermetatransaction "Direct link to createAllowanceTransferMetaTransaction") Creates a MetaTransaction that executes a token transfer using the delegate's allowance. The delegate calls this to move tokens from the source Safe to a recipient. * Param Types * Return Types | key | type | description | | :---------------------------- | :------- | :---------------------------------------------------------------------------------------- | | `allowanceSourceSafeAddress` | `string` | The safe address from which the allowance is being transferred. | | `token` | `string` | The token address being transferred. | | `to` | `string` | The recipient address of the allowance transfer. | | `amount` | `bigint` | The amount of tokens to be transferred. | | `delegate` | `string` | The delegate address managing the transfer. | | `overrides` | `object` | Optional overrides including delegate signature, payment token, and payment amount. | | `overrides.delegateSignature` | `string` | The signature of the delegate. Optional. | | `overrides.paymentToken` | `string` | An optional payment token address. Defaults to the zero address. | | `overrides.paymentAmount` | `bigint` | The amount of the payment token to be transferred. Required if paymentToken is specified. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createRenewAllowanceMetaTransaction[​](#createrenewallowancemetatransaction "Direct link to createRenewAllowanceMetaTransaction") Creates a MetaTransaction that resets (renews) the allowance for a specific delegate and token. This lets the account owner extend the delegate's allowance without deleting and recreating it. * Param Types * Return Types | key | type | description | | :--------- | :------- | :----------------------------------------------------------- | | `delegate` | `string` | The address of the delegate whose allowance should be reset. | | `token` | `string` | The address of the token for which the allowance is set. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createDeleteAllowanceMetaTransaction[​](#createdeleteallowancemetatransaction "Direct link to createDeleteAllowanceMetaTransaction") Creates a MetaTransaction that removes the allowance for a specific delegate and token. This sets all values except the `nonce` to 0, revoking the delegate's permission. * Param Types * Return Types | key | type | description | | :--------- | :------- | :------------------------------------------------------------- | | `delegate` | `string` | The address of the delegate whose allowance should be removed. | | `token` | `string` | The address of the token for which the allowance is set. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ## Helpers[​](#helpers "Direct link to Helpers") ### getDelegates[​](#getdelegates "Direct link to getDelegates") Retrieves a list of delegates associated with a Safe account. Supports pagination for large sets of delegates. * Param Types * Return Types | key | type | description | | :----------------------------- | :------- | :--------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | `safeAddress` | `string` | The target account address. | | `overrides` | `object` | Optional parameters for pagination. | | `overrides.start` | `bigint` | The starting point for fetching delegates. Defaults to 0. | | `overrides.maxNumberOfResults` | `bigint` | The maximum number of results to return. If specified, overrides pagination. | | key | type | description | | :------------------- | :--------- | :------------------------------------------------------- | | `Delegate Addresses` | `string[]` | A promise that resolves to a list of delegate addresses. | ### getTokensAllowance[​](#gettokensallowance "Direct link to getTokensAllowance") Retrieves the allowance details for a specific delegate and token. Returns the amount, spent, reset time, last reset time, and nonce. * Param Types * Return Types | key | type | description | | :------------ | :------- | :----------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | `safeAddress` | `string` | The target account address. | | `delegate` | `string` | The address of the target delegate. | | `token` | `string` | The address of the token for which the allowance is being queried. | | key | type | description | | :----------------------- | :------- | :------------------------------------------------------------------------ | | `Allowance` | `object` | A promise that resolves to the allowance details for the specified token. | | `Allowance.amount` | `bigint` | The total allowance amount. | | `Allowance.spent` | `bigint` | The amount spent from the allowance. | | `Allowance.resetTimeMin` | `bigint` | The time in minutes after which the allowance resets. | | `Allowance.lastResetMin` | `bigint` | The time in minutes when the allowance was last reset. | | `Allowance.nonce` | `bigint` | The nonce for the allowance. | ### getTokens[​](#gettokens "Direct link to getTokens") Retrieves the list of delegated token addresses for a specific delegate on a Safe account. * Param Types * Return Types | key | type | description | | :------------ | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | `safeAddress` | `string` | The target account address. | | `delegate` | `string` | The address of the target delegate. | | type | description | key | | :--------- | :-------------------------------------------------------------- | :-- | | `string[]` | A promise that resolves to a list of delegated token addresses. | `` | ### IsModuleEnabled[​](#ismoduleenabled "Direct link to IsModuleEnabled") Checks if a specified module is enabled for a given Safe account. Returns a boolean. See the method [IsModuleEnabled](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#ismoduleenabled) in the Safe Account reference. ### createRemoveDelegateMetaTransaction[​](#createremovedelegatemetatransaction "Direct link to createRemoveDelegateMetaTransaction") Creates a MetaTransaction that removes a delegate from the Safe account. Can also remove all associated allowances if specified. * Param Types * Return Types | key | type | description | | :----------------- | :-------- | :-------------------------------------------------------------------------------------------- | | `delegate` | `string` | The delegate address that should be removed. | | `removeAllowances` | `boolean` | Indicator if allowances should also be removed. Set to true unless this causes an out of gas. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ## Advanced Methods[​](#advanced-methods "Direct link to Advanced Methods") ### createBaseSetAllowanceMetaTransaction[​](#createbasesetallowancemetatransaction "Direct link to createBaseSetAllowanceMetaTransaction") Creates a MetaTransaction to update the allowance for a specified token. This can only be executed via a Safe transaction. Use this when you need direct control over the `resetTimeMin` and `resetBaseMin` parameters. * Param Types * Return Types | key | type | description | | :---------------- | :------- | :------------------------------------------------------------- | | `delegate` | `string` | The address of the delegate whose allowance should be updated. | | `token` | `string` | The address of the token for which the allowance is set. | | `allowanceAmount` | `bigint` | The amount of the token allowed for the delegate. | | `resetTimeMin` | `bigint` | Time in minutes after which the allowance resets. | | `resetBaseMin` | `bigint` | Time interval to increase the reset time. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ### createBaseExecuteAllowanceTransferMetaTransaction[​](#createbaseexecuteallowancetransfermetatransaction "Direct link to createBaseExecuteAllowanceTransferMetaTransaction") Creates a MetaTransaction that executes a transfer using the allowance. This is the low-level version of `createAllowanceTransferMetaTransaction` with explicit payment token and delegate signature parameters. * Param Types * Return Types | key | type | description | | :------------------ | :------- | :---------------------------------------------------------------------- | | `safeAddress` | `string` | The Safe address whose funds should be used. | | `token` | `string` | The token contract address being transferred. | | `to` | `string` | The recipient address of the token transfer. | | `amount` | `bigint` | The amount of tokens to be transferred. | | `paymentToken` | `string` | The token that should be used to pay for the execution of the transfer. | | `payment` | `bigint` | The amount to be paid for executing the transfer. | | `delegate` | `string` | The delegate whose allowance should be updated. | | `delegateSignature` | `string` | Signature generated by the delegate to authorize the transfer. | | key | type | description | | :-------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `MetaTransaction` | `object` | MetaTransaction is the type of transaction to construct a Safe operation. | | `MetaTransaction.to` | `string` | To address, or the the target contract address for the transaction | | `MetaTransaction.value` | `BigNumberish` | Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) | | `MetaTransaction.data` | `BytesLike` | The call data for the transaction | | `MetaTransaction.operation` | `Operation: enum` | Default to 0 for a Call. 1 for a Delegate Call. (Optional) | ## Advanced Helpers[​](#advanced-helpers "Direct link to Advanced Helpers") ### baseGetDelegates[​](#basegetdelegates "Direct link to baseGetDelegates") Retrieves a list of delegates associated with a Safe account. This is the low-level version with explicit pagination parameters (`start` and `pageSize`). * Param Types * Return Types | key | type | description | | :------------ | :------- | :--------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API URL for the target chain. | | `safeAddress` | `string` | The target account address. | | `start` | `bigint` | The starting point for fetching delegates. | | `pageSize` | `bigint` | The number of delegates to return in one call. | | key | description | type | | :-------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :--------- | | `{ results: string[], next: bigint }` | A promise that resolves to an object containing the list of delegate addresses and the next starting point for pagination. | `object` | | `{ results: string[], next: bigint }.results` | A list of delegate addresses. | `string[]` | | `{ results: string[], next: bigint }.next` | The next starting point for pagination. Returns 0 if there are no more delegates. | `bigint` | --- # How to Enable Spend Permissions with the Allowance Module The Safe Allowance Module lets account owners delegate specific token spending rights to other accounts or contracts. Unlike standard ERC-20 approvals, this module enforces on-chain limits per delegate: one-time budgets, recurring allowances that auto-reset, and expiration controls. ## What You'll Build[​](#what-youll-build "Direct link to What You'll Build") By the end of this guide, you'll have: * Enabled the Allowance Module on a Safe Account * Added a delegate with permission to spend tokens * Configured a recurring allowance with auto-reset * Executed a delegated token transfer from the delegate's account ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, make sure you have: * **Node.js 18+** and npm or yarn * **Basic TypeScript knowledge** * **A funded Safe Account** with ERC-20 tokens on your target chain * **Familiarity with Safe Smart Accounts** and UserOperations. See the [Getting Started guide](https://docs.candide.dev/wallet/guides/getting-started.md) if you're new Delegate Trust Delegates have full control over the allowance you grant them, up to the configured amount and frequency. Only delegate to addresses you trust. In production, consider using smart contracts as delegates to enforce additional conditions on transfers. ## How It Works[​](#how-it-works "Direct link to How It Works") ## Use Cases[​](#use-cases "Direct link to Use Cases") **Recurring Transfers**: Automate periodic transactions. Users define the amount, frequency, duration, and recipients. See [createRecurringAllowanceMetaTransaction](https://docs.candide.dev/wallet/plugins/allowance-reference.md#createrecurringallowancemetatransaction). **Subscriptions**: Set up automatic payments for subscriptions, utility bills, or membership fees. Combine with a delegate contract that enforces payment schedules. **Dollar-Cost Averaging (DCA)**: Delegate allowances to swap contracts that execute periodic buys. See [createOneTimeAllowanceMetaTransaction](https://docs.candide.dev/wallet/plugins/allowance-reference.md#createonetimeallowancemetatransaction) for single-use budgets. **Custom Use Cases**: Delegate to smart contracts for application-specific needs. Use [createAllowanceTransferMetaTransaction](https://docs.candide.dev/wallet/plugins/allowance-reference.md#createallowancetransfermetatransaction) to execute transfers. ## Audits[​](#audits "Direct link to Audits") * [Ackee Audit 2024](https://github.com/safe-global/safe-modules/blob/3bf6c45a/modules/allowances/docs/ackee-blockchain-safe-allowance-module-report.pdf) * [GnosisSafe Audit 2020](https://github.com/safe-global/safe-modules/blob/3bf6c45a/modules/allowances/docs/AllowanceModuleAuditOct2020.md) ## Guide[​](#guide "Direct link to Guide") ### Install[​](#install "Direct link to Install") * npm * yarn terminal ``` npm i abstractionkit ``` terminal ``` yarn add abstractionkit ``` ### Step 1: Initialize the Module[​](#step-1-initialize-the-module "Direct link to Step 1: Initialize the Module") ``` import { AllowanceModule } from "abstractionkit"; const allowanceModule = new AllowanceModule(); ``` ### Step 2: Enable the Module on Your Safe[​](#step-2-enable-the-module-on-your-safe "Direct link to Step 2: Enable the Module on Your Safe") The module must be enabled before any allowance operations. This only needs to happen once per Safe Account. ``` const enableModuleMetaTx = allowanceModule.createEnableModuleMetaTransaction( sourceSafeAccount.accountAddress, // your Safe account address ); ``` ### Step 3: Add a Delegate[​](#step-3-add-a-delegate "Direct link to Step 3: Add a Delegate") Register the address that will be allowed to spend tokens on behalf of the Safe. ``` const addDelegateMetaTx = allowanceModule.createAddDelegateMetaTransaction( delegateAddress, // address of the delegate ); ``` ### Step 4: Set an Allowance[​](#step-4-set-an-allowance "Direct link to Step 4: Set an Allowance") Configure how much the delegate can spend and how often the allowance resets. **Recurring allowance** (resets automatically): ``` const setAllowanceMetaTx = allowanceModule.createRecurringAllowanceMetaTransaction( delegateAddress, // delegate address tokenAddress, // ERC-20 token address 1n, // amount per period 3n, // reset period in minutes 0n, // start delay in minutes (0 = immediate) ); ``` **One-time allowance** (does not reset): ``` const setAllowanceMetaTx = allowanceModule.createOneTimeAllowanceMetaTransaction( delegateAddress, tokenAddress, 100n, // total amount 0n, // start delay in minutes ); ``` See the full parameter details in the [SDK Reference](https://docs.candide.dev/wallet/plugins/allowance-reference.md). ### Step 5: Create and Submit the UserOperation[​](#step-5-create-and-submit-the-useroperation "Direct link to Step 5: Create and Submit the UserOperation") Batch all three MetaTransactions into a single UserOperation using [createUserOperation](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createuseroperation), then sign and submit. ``` const userOp = await sourceSafeAccount.createUserOperation( [enableModuleMetaTx, addDelegateMetaTx, setAllowanceMetaTx], nodeUrl, bundlerUrl, ); // Sign the UserOperation userOp.signature = sourceSafeAccount.signUserOperation( userOp, [ownerPrivateKey], chainId, ); // Send to bundler const response = await sourceSafeAccount.sendUserOperation(userOp, bundlerUrl); const receipt = await response.included(); ``` ### Step 6: Delegate Executes the Transfer[​](#step-6-delegate-executes-the-transfer "Direct link to Step 6: Delegate Executes the Transfer") Once the allowance is active, the delegate can transfer tokens from the source Safe. Delegates can be automated services, EOAs, or smart contracts. ``` const transferMetaTx = allowanceModule.createAllowanceTransferMetaTransaction( sourceSafeAddress, // the Safe granting the allowance tokenAddress, // ERC-20 token recipientAddress, // where the tokens go transferAmount, // amount to transfer (must be within allowance) delegateAddress, // the delegate executing the transfer ); ``` The delegate then wraps this in their own UserOperation, signs it, and submits it. Automated Execution Candide offers a secure, automated tool to execute delegated allowances on a schedule. Contact us directly via our channels or email . Full Runnable Example The complete script below ties all the steps together. You can also find it in the [abstractionkit-examples repo](https://github.com/candidelabs/abstractionkit-examples/blob/main/spend-permission/spend-permission.ts). spend-permission.ts ``` loading... ``` [See full example on GitHub]() ## What's Next?[​](#whats-next "Direct link to What's Next?") * **[Allowance Module SDK Reference](https://docs.candide.dev/wallet/plugins/allowance-reference.md)**: Full API reference for all methods and helpers * **[Recovery Module](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md)**: Add account recovery with social guardians * **[Gas Sponsorship](https://docs.candide.dev/wallet/guides/send-gasless-tx.md)**: Sponsor gas fees for your users * **[Getting Started](https://docs.candide.dev/wallet/guides/getting-started.md)**: Send your first UserOperation with a Safe Account --- # Enable Recovery Module and Add Guardians Learn how to enable the [Social Recovery Module](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md) and add trusted guardians for your users' Safe accounts to provide secure account recovery functionality. > If you need help with the basics of Smart Accounts, check out the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md). ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts) and follow along. ### Installation[​](#installation "Direct link to Installation") * `abstractionkit` provides the core functionality for interacting with social recovery module smart contracts and constructing calldata. - npm - yarn ``` npm i abstractionkit ``` ``` yarn add abstractionkit ``` ### Initialize Recovery Module[​](#initialize-recovery-module "Direct link to Initialize Recovery Module") Initialize a SocialRecoveryModule instance. The default grace period for recovery is 3 days. ``` import { SocialRecoveryModule } from "abstractionkit"; const srm = new SocialRecoveryModule(); // 3 days ``` If you need a different grace period, you can override it by providing a [different grace period address](https://docs.candide.dev/wallet/technical-reference/deployments.md#social-recovery-module) during initialization. ``` import { SocialRecoveryModule, SocialRecoveryModuleGracePeriodSelector } from "abstractionkit"; const gracePeriod = SocialRecoveryModuleGracePeriodSelector.After7Days; const srm = new SocialRecoveryModule(gracePeriod); // 7 days ``` ### Setup Recovery Module with Guardian[​](#setup-recovery-module-with-guardian "Direct link to Setup Recovery Module with Guardian") Set up the Social Recovery Module on a Safe account with a designated guardian in a single transaction * enable-module.ts * .env Enable Social Recovery Module and add a guardian ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([process.env.OWNER_PUBLIC_KEY]); // MetaTransaction to enable recovery module const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ); // MetaTransaction to add guardian const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( "0x..", // guardian address 1n, //threshold for recovery ); // Create UserOperation with both operations let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], process.env.NODE_URL, process.env.BUNDLER_URL ); ``` > Learn more: [`createEnableModuleMetaTransaction`](https://docs.candide.dev/wallet/plugins/recovery-module-reference.md#createenablemodulemetatransaction) | [`createAddGuardianWithThresholdMetaTransaction`](https://docs.candide.dev/wallet/plugins/recovery-module-reference.md#createaddguardianwiththresholdmetatransaction) ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 NODE_URL=https://ethereum-sepolia-rpc.publicnode.com OWNER_PUBLIC_KEY=0x.. ``` ## Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example") Below is a complete example that demonstrates enabling the recovery module and adding multiple guardians: Full Working Example * recovery-setup.ts * .env ``` import * as dotenv from 'dotenv' import { SafeMultiChainSigAccountV1 as SafeAccount, SocialRecoveryModule, } from "abstractionkit"; async function main(): Promise { // Load environment variables dotenv.config() const chainId = BigInt(process.env.CHAIN_ID as string) const bundlerUrl = process.env.BUNDLER_URL as string const jsonRpcNodeProvider = process.env.NODE_URL as string const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string const ownerPrivateKey = process.env.PRIVATE_KEY as string const guardianAddress = process.env.GUARDIAN_PUBLIC_ADDRESS as string // Initialize Smart Account const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]) console.log("Smart Account Address:", smartAccount.accountAddress) // Initialize Social Recovery Module const srm = new SocialRecoveryModule() // Step 1: Enable module and add guardian in one UserOperation console.log("Step 1: Enabling Social Recovery Module and adding guardian...") const enableModuleTx = srm.createEnableModuleMetaTransaction( smartAccount.accountAddress ) const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction( guardianAddress, 1n // threshold: 1 guardian needed for recovery ) // Batch both operations let userOperation = await smartAccount.createUserOperation( [enableModuleTx, addGuardianTx], jsonRpcNodeProvider, bundlerUrl ) userOperation.signature = smartAccount.signUserOperation( userOperation, [ownerPrivateKey], chainId ) let response = await smartAccount.sendUserOperation(userOperation, bundlerUrl) let result = await response.included() if (result.success) { console.log("Recovery module enabled and guardian added successfully!") } else { console.log("Failed to setup recovery") return } // Step 2: Verify Guardian console.log("Step 2: Verifying guardian...") const isGuardian = await srm.isGuardian( jsonRpcNodeProvider, smartAccount.accountAddress, guardianAddress ) console.log(`Guardian verification: ${isGuardian ? "Active" : "Not found"}`) // Step 3: Get recovery info const guardiansCount = await srm.guardiansCount( jsonRpcNodeProvider, smartAccount.accountAddress ) const threshold = await srm.threshold( jsonRpcNodeProvider, smartAccount.accountAddress ) const guardiansList = await srm.getGuardians( jsonRpcNodeProvider, smartAccount.accountAddress ) console.log("Recovery Configuration:") console.log(`- Total guardians: ${guardiansCount}`) console.log(`- Recovery threshold: ${threshold}`) console.log(`- Guardian addresses: ${guardiansList.join(", ")}`) console.log("Recovery setup complete! Your account is now protected.") } main().catch(console.error) ``` ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 NODE_URL=https://ethereum-sepolia-rpc.publicnode.com # Your Smart Account credentials PRIVATE_KEY=your_private_key_here PUBLIC_ADDRESS=your_public_address_here # Guardian credentials (trusted person/device) GUARDIAN_PRIVATE_KEY=guardian_private_key_here GUARDIAN_PUBLIC_ADDRESS=guardian_public_address_here ``` ## Guardian Types and Options[​](#guardian-types-and-options "Direct link to Guardian Types and Options") ### Personal Guardians[​](#personal-guardians "Direct link to Personal Guardians") Set up trusted individuals or devices as guardians: * **Family & Friends**: People you trust who understand the responsibility * **Multiple Devices**: Your own hardware wallets or secure devices * **Professional Services**: Trusted custody or recovery services ### Threshold Configuration[​](#threshold-configuration "Direct link to Threshold Configuration") | Setup | Threshold | Use Case | Security Level | | ------- | --------- | --------------------------------- | -------------------------------------- | | **1/1** | 1 of 1 | Single trusted guardian | Low - single point of failure | | **1/2** | 1 of 2 | Backup guardian option | Good - redundancy with convenience | | **2/3** | 2 of 3 | Balanced security & availability | Excellent - prevents single compromise | | **3/5** | 3 of 5 | High security institutional setup | Maximum - requires coordination | Security Recommendations * **Never use 1/1** for high-value accounts (single point of failure) * **2/3 is optimal** for most use cases (prevents single guardian compromise) * **Consider Candide Guardian** as one of multiple guardians for user-friendly recovery * **Keep guardian list updated** as relationships change ## What's Next[​](#whats-next "Direct link to What's Next") * [Recovery Flow Guide](https://docs.candide.dev/wallet/plugins/recovery-flow-guide.md): implement the full recovery flow once guardians are set up * [Add Candide Guardian](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md): use Candide's managed guardian service for email/SMS recovery instead of self-managed keys --- # How to Add Support for Passkeys Authentication Passkeys secure on-chain smart accounts using **fingerprint**, **face recognition**, or device **PIN** codes. Users no longer need to manage complex private keys. Instead, they access their digital wallets using passkey-enabled devices synced via Apple's **iCloud Keychain** or cross-platform password managers like **Proton Pass** and **Bitwarden**. Passkeys replace traditional seed phrase backups in Ethereum wallets. Unlike the secp256k1 curve used for Externally Owned Accounts (EOAs), Passkeys use the **secp256r1** curve. These keys leverage device secure enclave cryptography, built on the WebAuthn standard using public-key cryptography developed by the FIDO Alliance (Apple, Google, Microsoft, and others). Safe Passkeys contracts are developed by the Safe Protocol Team. The contracts and audits are available in the [Safe-Modules repository](https://github.com/safe-global/safe-modules/tree/main/modules/passkey). Deployment addresses can be found on our [contract deployment](https://docs.candide.dev/wallet/technical-reference/deployments.md#safe-passkeys) page. #### What You'll Build[​](#what-youll-build "Direct link to What You'll Build") By the end of this guide, you'll have: * Created WebAuthn passkey credentials for biometric authentication * Initialized a Safe Smart Account with a passkey signer * Signed and submitted a UserOperation using passkeys #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, make sure you have: * **Node.js 18+** and npm or yarn * **Basic TypeScript knowledge** * **Familiarity with Safe Smart Accounts** and UserOperations. See the [Getting Started guide](https://docs.candide.dev/wallet/guides/getting-started.md) if you're new Security: Do Not Ship a 1/1 Passkey-Only Safe to Production This guide demonstrates a 1/1 Safe with a single passkey signer. For production use, consider adding a second access method since passkeys can be tied to specific domains or device ecosystems. A [1/2 multisig setup](#multisig) or the [recovery module](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md) gives users a backup path if they switch devices or lose access to their passkey ## Quick start[​](#quick-start "Direct link to Quick start") [YouTube video player](https://www.youtube-nocookie.com/embed/Xh7ZJ3oeOu8?si=UUARp_pt_cjebiop) ## Demo[​](#demo "Direct link to Demo") These examples showcase Safe Smart Account deployments using ERC-4337 and Passkeys: * **React Demo**: Full browser-based passkeys flow with account creation and transaction signing. * [Live Demo](https://passkeys.candide.dev/) | [Source Code](https://github.com/candidelabs/safe-passkeys-react-example) * **React Native Demo**: Mobile passkeys integration using `react-native-passkey` and `cbor-web`. * [Source Code](https://github.com/candidelabs/passkeys-react-native-demo) * **Node.js Demo**: Client side example with simulated passkeys for testing and CI environments. * [Source Code](https://github.com/candidelabs/abstractionkit-examples/tree/main/passkeys) ## Start Integrating[​](#start-integrating "Direct link to Start Integrating") Install the Candide skills so your AI agent knows how to wire up Safe Passkeys (RP ID setup, public-key extraction, signature assembly, recovery posture). Source: [github.com/candidelabs/skills](https://github.com/candidelabs/skills). * Claude Code * Codex CLI * Other agents Run inside Claude Code: ``` /plugin marketplace add candidelabs/skills /plugin install candide@candide ``` Then ask: *"Integrate Safe Passkeys."* ``` npx -y github:candidelabs/skills ``` Then ask Codex: *"Integrate Safe Passkeys."* Paste the raw skill URL into Cursor, Windsurf, Copilot, or any chat-based agent: ``` https://raw.githubusercontent.com/candidelabs/skills/main/skills/safe-passkeys/SKILL.md ``` ## Create a Passkey-Authenticated Smart Account[​](#create-a-passkey-authenticated-smart-account "Direct link to Create a Passkey-Authenticated Smart Account") ### Step 1: Install Dependencies[​](#step-1-install-dependencies "Direct link to Step 1: Install Dependencies") Install `abstractionkit` for Safe account tooling and `ox` for WebAuthn interactions. * npm * yarn terminal ``` npm i abstractionkit ox ``` terminal ``` yarn add abstractionkit ox ``` **Why ox?**: The `ox` library provides a high-level abstraction over the browser's WebAuthn API. The `ox/WebAuthnP256` module handles credential creation and signing with P-256 keys, removing the need to work with raw WebAuthn responses. ### Step 2: Create WebAuthn Credentials[​](#step-2-create-webauthn-credentials "Direct link to Step 2: Create WebAuthn Credentials") Call `createCredential` from `ox/WebAuthnP256` to trigger the browser's passkey prompt. This returns a credential object containing the public key and credential ID. createPasskey.ts ``` import { createCredential } from 'ox/WebAuthnP256' const passkeyCredential = await createCredential({ name: 'Safe Wallet', // Random challenge to prevent replay attacks challenge: crypto.getRandomValues(new Uint8Array(32)), rp: { // Ties the credential to the current domain id: window.location.hostname, name: 'Safe Wallet' }, authenticatorSelection: { // Use device biometrics (Touch ID, Face ID, Windows Hello) authenticatorAttachment: 'platform', residentKey: 'required', userVerification: 'required', }, timeout: 60000, attestation: 'none', }) ``` ### Step 3: Extract Public Key[​](#step-3-extract-public-key "Direct link to Step 3: Extract Public Key") The `createCredential` function returns the public key coordinates directly. Wrap them in a `WebauthPublicKey` object for use with `abstractionkit`. extractPublicKey.ts ``` import { WebauthPublicKey } from "abstractionkit"; const webauthPublicKey: WebauthPublicKey = { x: passkeyCredential.publicKey.x, y: passkeyCredential.publicKey.y, } ``` **Save Public Credentials**: Store the passkey's public credentials (`x`, `y`, and `passkeyCredential.id`) in a retrievable location before the smart account is deployed. Losing this data prevents users from using their accounts if the account has not been. This information is not sensitive. You can use a simple backend server or leverage [@simplewebauthn/server](https://simplewebauthn.dev/docs/packages/server) for storage. AbstractionKit `v0.3.5` includes bigint-safe JSON helpers for storing and rehydrating passkey public coordinates: storePasskey.ts ``` import { pubkeyCoordinatesFromJson, pubkeyCoordinatesToJson, } from "abstractionkit"; localStorage.setItem("passkeyPubkey", pubkeyCoordinatesToJson(webauthPublicKey)); const stored = localStorage.getItem("passkeyPubkey"); const restoredPubkey = stored ? pubkeyCoordinatesFromJson(stored) : undefined; ``` ### Step 4: Initialize Smart Account[​](#step-4-initialize-smart-account "Direct link to Step 4: Initialize Smart Account") Initialize the Safe Smart Account with the passkey as the signer. New integrations should use `SafeMultiChainSigAccountV1` on EntryPoint v0.9. Use `SafeAccountV0_3_0` for legacy EntryPoint v0.7 passkey accounts or `SafeAccountV0_2_0` for legacy EntryPoint v0.6 accounts. initAccount.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; const smartAccount = SafeAccount.initializeNewAccount([webauthPublicKey]) ``` ## Create and Sign a UserOperation[​](#create-and-sign-a-useroperation "Direct link to Create and Sign a UserOperation") ### Step 5: Create UserOperation[​](#step-5-create-useroperation "Direct link to Step 5: Create UserOperation") Create a UserOperation following the standard Safe flow with [createUserOperation](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createuseroperation). Pass `expectedSigners` so that gas estimation accounts for the passkey signature format. createUserOp.ts ``` let userOperation = await smartAccount.createUserOperation( [transaction], // your MetaTransaction (to, value, data) jsonRpcNodeProvider, // JSON-RPC node endpoint bundlerUrl, // Bundler RPC endpoint { expectedSigners: [webauthPublicKey] }, ) ``` ### Step 6: Sign with Passkeys[​](#step-6-sign-with-passkeys "Direct link to Step 6: Sign with Passkeys") #### Sign with fromSafeWebauthn[​](#sign-with-fromsafewebauthn "Direct link to Sign with fromSafeWebauthn") AbstractionKit `v0.3.5` includes `fromSafeWebauthn`, an `ExternalSigner` adapter for Safe passkey owners. It handles the Safe-specific signer address, contract-signature encoding, and `signUserOperationWithSigners` integration. signUserOp.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, fromSafeWebauthn, webauthnSignatureFromAssertion, } from "abstractionkit"; import { sign } from "ox/WebAuthnP256"; import { Hex as OxHex } from "ox/Hex"; import { Hex } from "ox"; const signer = fromSafeWebauthn({ publicKey: webauthPublicKey, isInit: userOperation.nonce === 0n, accountClass: SafeAccount, getAssertion: async (challenge) => { const { metadata, signature } = await sign({ challenge: Hex.fromBytes(challenge), credentialId: passkeyCredential.id as OxHex, }); return webauthnSignatureFromAssertion({ authenticatorData: metadata.authenticatorData, clientDataJSON: metadata.clientDataJSON, signature, }); }, }); userOperation.signature = await smartAccount.signUserOperationWithSigners( userOperation, [signer], BigInt(chainId), ); ``` Pass the same account class used to initialize the account. Use `SafeMultiChainSigAccountV1` for new Safe passkeys and `SafeAccountV0_3_0` only for legacy EntryPoint v0.7 passkey accounts. Keep passing `expectedSigners: [webauthPublicKey]` to `createUserOperation`; the bundler needs the larger WebAuthn dummy signature during gas estimation. #### Manual signing[​](#manual-signing "Direct link to Manual signing") The lower-level manual path is still available when you need direct control over each signature field. It involves four substeps: hashing, requesting a WebAuthn assertion, building the signature data, and formatting it. #### Step 6a: Calculate the EIP-712 Hash[​](#step-6a-calculate-the-eip-712-hash "Direct link to Step 6a: Calculate the EIP-712 Hash") Compute the Safe EIP-712 hash for the UserOperation. This is the challenge that the passkey will sign. signUserOp.ts ``` const userOpHash = SafeAccount.getUserOperationEip712Hash( userOperation, BigInt(chainId), ); ``` #### Step 6b: Request a WebAuthn Assertion[​](#step-6b-request-a-webauthn-assertion "Direct link to Step 6b: Request a WebAuthn Assertion") Use the `sign` function from `ox/WebAuthnP256` to prompt the user for biometric authentication. Pass the `userOpHash` as the challenge and the credential ID from Step 2. signUserOp.ts ``` import { sign } from 'ox/WebAuthnP256'; import { Hex as OxHex } from 'ox/Hex'; import { Bytes, Hex } from 'ox'; const { metadata, signature } = await sign({ challenge: userOpHash as OxHex, credentialId: passkeyCredential.id as OxHex, }); ``` #### Step 6c: Build the WebauthnSignatureData[​](#step-6c-build-the-webauthnsignaturedata "Direct link to Step 6c: Build the WebauthnSignatureData") Extract the `clientDataFields` from the WebAuthn response metadata and construct the `WebauthnSignatureData` object that `abstractionkit` expects. signUserOp.ts ``` import { WebauthnSignatureData } from "abstractionkit"; // Extract the fields portion of clientDataJSON (everything after the challenge) const clientDataMatch = metadata.clientDataJSON.match( /^\{"type":"webauthn.get","challenge":"[A-Za-z0-9\-_]{43}",(.*)\}$/, ); if (!clientDataMatch) { throw new Error('Invalid clientDataJSON format: challenge not found'); } const [, fields] = clientDataMatch; const webauthnSignatureData: WebauthnSignatureData = { authenticatorData: Bytes.fromHex(metadata.authenticatorData).buffer as ArrayBuffer, clientDataFields: Hex.fromString(fields), rs: [signature.r, signature.s], }; const webauthnSignature: string = SafeAccount.createWebAuthnSignature(webauthnSignatureData) ``` #### Step 6d: Format the Signer Signature Pair[​](#step-6d-format-the-signer-signature-pair "Direct link to Step 6d: Format the Signer Signature Pair") Create a `SignerSignaturePair` linking the public key to its signature, then format it into the UserOperation's expected signature field. signUserOp.ts ``` import { SignerSignaturePair } from "abstractionkit"; const signerSignaturePair: SignerSignaturePair = { signer: webauthPublicKey, signature: webauthnSignature, } userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [signerSignaturePair], { isInit: userOperation.nonce == 0n }, ) ``` ### Step 7: Submit Onchain[​](#step-7-submit-onchain "Direct link to Step 7: Submit Onchain") Send the signed UserOperation to the bundler and wait for it to be included onchain. submitUserOp.ts ``` const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl, ); // Wait for the transaction to be included in a block const userOperationReceiptResult = await sendUserOperationResponse.included(); ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") Here are common errors you may encounter when integrating passkeys: * **"The operation either timed out or was not allowed"**: The user cancelled the WebAuthn prompt, or the browser does not support passkeys. Verify that you are serving over HTTPS (required for WebAuthn) and that the user's browser supports the Web Authentication API. * **"Invalid signature" or gas estimation failure**: This typically means a mismatch between the `expectedSigners` passed to `createUserOperation` and the actual signing key. Ensure you are using the same `webauthPublicKey` in both account initialization and UserOperation creation. * **Domain mismatch**: The `rp.id` value passed during credential creation must match the domain where the signing occurs. If you created credentials on `localhost` but are signing on a deployed domain (or vice versa), the browser will reject the assertion. * **Passkey prompt does not appear**: Ensure `authenticatorAttachment` is set to `'platform'` for built-in biometrics, or `'cross-platform'` for security keys. Some browsers require a user gesture (like a button click) before the WebAuthn prompt can appear. #### Getting Help[​](#getting-help "Direct link to Getting Help") If you're still stuck: * Ask questions in abstractionkit's [GitHub Discussions](https://github.com/candidelabs/abstractionkit/discussions) * Join our [Discord](https://discord.gg/KJSzy2Rqtg) ## Advanced[​](#advanced "Direct link to Advanced") ### Multisig[​](#multisig "Direct link to Multisig") #### New Account[​](#new-account "Direct link to New Account") To initialize a smart account with multiple signer types, provide both a WebAuthn public key and an EOA public key to the initialization function, with the WebAuthn public key listed first. To add two Passkey signers, initialize and deploy the account with a single Passkey signer first, then use [`addOwnerWithThreshold`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createaddownerwiththresholdmetatransactions) to add the second Passkey signer. multisigInit.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { Wallet } from 'ethers' const webauthPublicKey = .. // see Step 3 // EOA Signer const eoaSigner = Wallet.createRandom(); const eoaPublicKey = eoaSigner.address; let smartAccount = SafeAccount.initializeNewAccount( [webauthPublicKey, eoaPublicKey], { threshold: 2 } ) ``` #### Existing account[​](#existing-account "Direct link to Existing account") * Add a Passkeys owner to an existing account using [`createAddOwnerWithThresholdMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createaddownerwiththresholdmetatransactions) addPasskeyOwner.ts ``` import { MetaTransaction } from "abstractionkit" const addPasskeysOwner: MetaTransaction = await smartAccount.createAddOwnerWithThresholdMetaTransactions( webauthPublicKey, // the x and y webAuthn publickey 1, // threshold { nodeRpcUrl: nodeUrl } ); ``` * Swap an existing owner to a Passkeys owner using [`createSwapOwnerMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createswapownermetatransactions) swapToPasskeyOwner.ts ``` import { MetaTransaction } from "abstractionkit" const swapOwnerWithPasskeys: MetaTransaction = await smartAccount.createSwapOwnerMetaTransactions( nodeUrl, webauthPublicKey, // the x and y webAuthn publickey oldOwnerPublicKey, // the old owner to replace ); ``` #### Create UserOp[​](#create-userop "Direct link to Create UserOp") To obtain accurate gas estimates, pass the expected signers who will sign the UserOperation in the `createUserOperation` overrides. multisigUserOp.ts ``` let userOperation = await smartAccount.createUserOperation( [metaTransaction], jsonRpcNodeProvider, bundlerUrl, { expectedSigners:[webauthPublicKey, eoaPublicKey], } ) ``` #### Signature[​](#signature "Direct link to Signature") To sign a transaction with multiple signers, pass the signer signature pairs to `formatSignaturesToUseroperationSignature`. multisigSignature.ts ``` const eoaSignature = eoaSigner.signingKey.sign(userOpHash).serialized; const eoaSignerSignaturePair: SignerSignaturePair = { signer: eoaPublicKey, signature: eoaSignature, } userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [webAuthnSignerSignaturePair, eoaSignerSignaturePair], { isInit: userOperation.nonce == 0n } ); ``` ### Gas Savings with Precompiles[​](#gas-savings-with-precompiles "Direct link to Gas Savings with Precompiles") Leverage Native Passkeys with [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) when supported for optimal gas efficiency. Import the default precompile address and pass it in the overrides. Verify that your chain has adopted the same precompile address specified in the standard. **Save Gas with RIP-7212**: Chains that support the RIP-7212 precompile for secp256r1 verification can reduce passkey signature validation costs significantly. Check your target chain's documentation to confirm support before enabling. #### New Account[​](#new-account-1 "Direct link to New Account") precompileInit.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, DEFAULT_SECP256R1_PRECOMPILE_ADDRESS } from "abstractionkit"; let smartAccount = SafeAccount.initializeNewAccount( [webauthPublicKey], { eip7212WebAuthnPrecompileVerifierForSharedSigner: DEFAULT_SECP256R1_PRECOMPILE_ADDRESS } ) ``` #### Create UserOp[​](#create-userop-1 "Direct link to Create UserOp") precompileUserOp.ts ``` let userOperation = await smartAccount.createUserOperation( [metaTransaction], nodeRPC, bundlerURL, { expectedSigners:[webauthPublicKey], eip7212WebAuthnPrecompileVerifier: DEFAULT_SECP256R1_PRECOMPILE_ADDRESS } ); ``` #### Signature[​](#signature-1 "Direct link to Signature") precompileSignature.ts ``` userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature( [webauthnSignerSignaturePair], { isInit: userOperation.nonce == 0n, eip7212WebAuthnPrecompileVerifier: DEFAULT_SECP256R1_PRECOMPILE_ADDRESS, } ); ``` ### Verifying a WebAuthn Signature[​](#verifying-a-webauthn-signature "Direct link to Verifying a WebAuthn Signature") Validate WebAuthn signatures to verify whether a signature on behalf of a given Safe Account is valid, similar to EOA owner verification. * Sign a message hash using the standard process: signMessage.ts ``` import { hashMessage } from "ethers"; const messageHashed = hashMessage("Hello World"); const assertion = navigator.credentials.get({ publicKey: { challenge: ethers.getBytes(messageHashed), rpId: "candide.dev", allowCredentials: [ { type: "public-key", id: new Uint8Array(credential.rawId) }, ], userVerification: UserVerificationRequirement.required, }, }); const webauthSignatureData: WebauthnSignatureData = { authenticatorData: assertion.response.authenticatorData, clientDataFields: extractClientDataFields(assertion.response), rs: extractSignature(assertion.response), }; const webauthnSignature: string = SafeAccount.createWebAuthnSignature(webauthSignatureData); ``` * Validating a signed webAuthn message [`verifyWebAuthnSignatureForMessageHashParam`](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#verifywebauthnsignatureformessagehash) verifyMessage.ts ``` const isSignatureValid: boolean = await SafeAccount.verifyWebAuthnSignatureForMessageHash( nodeURL, // node url from a json rpc provider webauthPublicKey, // the x and y webAuthn publickey messageHashed, webauthnSignature ); ``` For a complete example to sign and verify message, run the repo [safe-passkeys-sign-and-verify-message](https://github.com/Sednaoui/safe-passkeys-sign-and-verify-message) ## Additional Notes[​](#additional-notes "Direct link to Additional Notes") ### WebAuthn / Passkeys API[​](#webauthn--passkeys-api "Direct link to WebAuthn / Passkeys API") The WebAuthn API is a web standard that enables passwordless authentication, allowing users to sign in to websites and applications using biometric factors (e.g., fingerprint, face recognition) or security keys. This API is supported by most major browsers, including Google Chrome, Mozilla Firefox, Microsoft Edge, Apple Safari, Brave, and Opera. For more information on browser support, Mozilla has created great [documentation on WebAuthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API). In this guide, we use the `ox` library, which provides a high-level abstraction over the WebAuthn API via the `ox/WebAuthnP256` module. React Native Integration For React Native applications, teams have used the following libraries to integrate WebAuthn/Passkeys: * [**react-native-passkey**](https://www.npmjs.com/package/react-native-passkey): A React Native wrapper around the platform-specific WebAuthn/Passkeys APIs. * [**cbor-web**](https://www.npmjs.com/package/cbor-web): Used in conjunction with `react-native-passkey` to handle the CBOR (Concise Binary Object Representation) data format used by the WebAuthn API. * [**React Native demo**](https://github.com/candidelabs/passkeys-react-native-demo) by Adrian, the lead developer from Unit-e, using `abstractionkit`, `react-native-passkey`, and `cbor-web`. ### Sync & Recovery[​](#sync--recovery "Direct link to Sync & Recovery") #### Apple[​](#apple "Direct link to Apple") Passkey recovery on Apple devices uses iCloud Keychain escrow. In case of device loss, users authenticate through their iCloud account using standard procedures, then enter their device passcode. Apple users can also add an account recovery contact for additional support. Learn more about [Apple Passkeys security](https://support.apple.com/en-us/102195). #### Google[​](#google "Direct link to Google") Google Password Manager seamlessly syncs passkeys across devices, with plans to extend syncing support to additional operating systems. Learn more about [Google Passkeys security](https://developers.google.com/identity/passkeys). #### YubiKey[​](#yubikey "Direct link to YubiKey") YubiKey supports passkeys through its authentication protocol implementation. Passkeys can be protected and managed using YubiKey's hardware-based security features. Learn more at [Yubico](https://www.yubico.com/blog/a-yubico-faq-about-passkeys/). #### Password Managers[​](#password-managers "Direct link to Password Managers") Passkey backups extend beyond hardware manufacturers. They are supported across various password managers including [Windows Hello](https://support.microsoft.com/en-us/windows/passkeys-in-windows-301c8944-5ea2-452b-9886-97e4d2ef4422), [Bitwarden](https://bitwarden.com/passwordless-passkeys/), [Proton Pass](https://proton.me/blog/proton-pass-passkeys), [1Password](https://1password.com/product/passkeys), [LastPass](https://www.lastpass.com/features/passwordless-authentication), and others. ### Device Support[​](#device-support "Direct link to Device Support") Passkeys are widely available across devices such as: * Apple Devices: iPhones & iPads (iOS 16+), Mac (macOS 13+) * Android Devices: Phones and tablets (Android 9+) * Windows (10/11/+): Supported on Chrome, Brave, Edge, and Firefox browsers * Linux: Supported on Chrome, Firefox, Edge, and Brave browsers For a comprehensive list of supported systems, please visit [passkeys.dev/device-support](https://passkeys.dev/device-support/) ## What's Next?[​](#whats-next "Direct link to What's Next?") Now that you've integrated passkeys authentication, explore these features to harden your setup: #### Recommended Next Steps[​](#recommended-next-steps "Direct link to Recommended Next Steps") 1. **[Recovery Module](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md)**: Protect passkey-only accounts with social recovery guardians 2. **[Gas Sponsorship](https://docs.candide.dev/wallet/guides/getting-started.md)**: Sponsor gas fees for your users with a Paymaster 3. **[Multisig Setup](#multisig)**: Add a second signer for production-grade security (recommended for any 1/1 passkey account) 4. **[Pay Gas in ERC-20](https://docs.candide.dev/wallet/guides/pay-gas-in-erc20.md)**: Let users pay transaction fees with stablecoins or other tokens --- # Recover via Email / SMS Recover your Safe account using [Candide Guardian](https://docs.candide.dev/wallet/recovery/auth-api.md) when you've lost access to your owner keys. This guide covers the complete recovery process using email and SMS verification. info The Recovery Service requires access credentials. [Request access here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). > If you haven't set up Candide Guardian yet, see the [Add Candide Guardian](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md) guide first. ## What You'll Need[​](#what-youll-need "Direct link to What You'll Need") Before starting recovery, ensure you have: * **Access to registered recovery methods** (email or SMS) * **Safe account address** to recover * **New private key** to replace the lost key * **Recovery service access** (Candide Guardian enabled) ## Recovery Overview[​](#recovery-overview "Direct link to Recovery Overview") The recovery process consists of five key steps: 1. **Generate New Owner Key** - Create a replacement for the lost key 2. **Request Guardian Signature** - Initiate recovery through the Guardian service 3. **Verify All Channels** - Confirm identity through ALL registered methods 4. **Execute Recovery** - Submit the recovery transaction 5. **Finalize Recovery** - Complete ownership transfer after the grace period ## Quickstart[​](#quickstart "Direct link to Quickstart") > You can also [fork the complete code](https://github.com/candidelabs/safe-recovery-service-sdk/blob/main/examples/03-recovery-flow/index.ts) and follow along. ### Installation[​](#installation "Direct link to Installation") * npm * yarn ``` npm i abstractionkit safe-recovery-service-sdk viem ``` ``` yarn add abstractionkit safe-recovery-service-sdk viem ``` ### Configure Environment[​](#configure-environment "Direct link to Configure Environment") * .env ``` # Network Configuration CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 NODE_URL=https://ethereum-sepolia-rpc.publicnode.com # Recovery Service URL # Get access here: https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5 RECOVERY_SERVICE_URL= ``` ## Step 1: Initialize Recovery Service[​](#step-1-initialize-recovery-service "Direct link to Step 1: Initialize Recovery Service") ``` import { RecoveryByCustodialGuardian } from "safe-recovery-service-sdk"; import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'; const chainId = BigInt(process.env.CHAIN_ID as string); const serviceUrl = process.env.RECOVERY_SERVICE_URL as string; // Initialize recovery service const custodialGuardianService = new RecoveryByCustodialGuardian(serviceUrl, chainId); // Generate new owner key to replace the lost one const newOwnerPrivateKey = generatePrivateKey(); const newOwner = privateKeyToAccount(newOwnerPrivateKey); ``` ## Step 2: Request Guardian Signature Challenge[​](#step-2-request-guardian-signature-challenge "Direct link to Step 2: Request Guardian Signature Challenge") Request a recovery signature from Candide Guardian: ``` // Request Candide Guardian signature for recovery const signatureRequest = await custodialGuardianService.requestCustodialGuardianSignatureChallenge( safeAccountAddress, [newOwner.address], // New owner array 1 // New threshold ); ``` ## Step 3: Verify All Recovery Channels[​](#step-3-verify-all-recovery-channels "Direct link to Step 3: Verify All Recovery Channels") You must verify **ALL** registered channels to complete recovery. Submit the OTP codes received: * Email Verification * SMS Verification * Verify All Channels ``` // Find the email authentication challenge const emailAuth = signatureRequest.auths.find(auth => auth.channel === 'email'); // Enter OTP from email const emailOtp = "123456"; // Replace with actual OTP const verificationResult = await custodialGuardianService.submitCustodialGuardianSignatureChallenge( signatureRequest.requestId, emailAuth.challengeId, emailOtp ); ``` ``` // Find the SMS authentication challenge const smsAuth = signatureRequest.auths.find(auth => auth.channel === 'sms'); // Enter OTP from SMS const smsOtp = "654321"; // Replace with actual OTP const verificationResult = await custodialGuardianService.submitCustodialGuardianSignatureChallenge( signatureRequest.requestId, smsAuth.challengeId, smsOtp ); ``` ``` // Verify ALL registered channels (required for recovery) let verificationResult; for (const auth of signatureRequest.auths) { // Enter OTP for current channel const recoveryOtpCode = "123456"; // Replace with actual OTP from respective channel verificationResult = await custodialGuardianService.submitCustodialGuardianSignatureChallenge( signatureRequest.requestId, auth.challengeId, recoveryOtpCode ); } ``` ## Step 4: Execute Recovery Transaction[​](#step-4-execute-recovery-transaction "Direct link to Step 4: Execute Recovery Transaction") After verifying all channels, execute the recovery with the obtained guardian signature: ``` // Execute recovery using the service const recoveryRequest = await custodialGuardianService.createAndExecuteRecoveryRequest( safeAccountAddress, [newOwner.address], // New owner array 1, // New threshold verificationResult.custodianGuardianAddress as string, verificationResult.custodianGuardianSignature as string ); ``` ## Step 5: Finalize Recovery After Grace Period[​](#step-5-finalize-recovery-after-grace-period "Direct link to Step 5: Finalize Recovery After Grace Period") Wait for the grace period to expire, then finalize the recovery: ``` import { SocialRecoveryModuleGracePeriodSelector } from "abstractionkit"; import { RecoveryByGuardian } from "safe-recovery-service-sdk"; // Wait for grace period (3 minutes) await new Promise(resolve => setTimeout(resolve, 3 * 60 * 1000)); // Initialize recovery service for finalization const recoveryService = new RecoveryByGuardian( serviceUrl, chainId, SocialRecoveryModuleGracePeriodSelector.After3Minutes ); // Finalize the recovery const finalizationResult = await recoveryService.finalizeRecoveryRequest(recoveryRequest.id); ``` ## Complete Working Example[​](#complete-working-example "Direct link to Complete Working Example") Full Recovery Flow Example examples/03-recovery-flow/index.ts ``` loading... ``` [See full example on GitHub]() ## What's Next[​](#whats-next "Direct link to What's Next") * [Recovery Alerts Guide](https://docs.candide.dev/wallet/plugins/recovery-alerts-guide.md): set up email and SMS notifications so account owners are alerted if a recovery is ever initiated --- # Recovery Alert Subscription Guide Set up email and SMS notifications for recovery requests to keep Safe account owners informed of recovery attempts. This security feature provides an essential early warning system during the recovery process. info The Recovery Service requires access credentials. [Request access here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). > To set up the recovery module first, see the [Enable Recovery Module and Add Guardians](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md) guide. ## Overview[​](#overview "Direct link to Overview") The Recovery Alert system provides: 1. **Instant Notifications**: Immediate email and SMS alerts when recovery requests are initiated 2. **On-Chain Monitoring**: Tracks on-chain recoveries across all supported networks 3. **Grace Period Warnings**: Alerts during the grace period enabling owners to respond to unauthorized attempts 4. **Signature Verification**: Only verified account owners can subscribe to alerts To learn more, visit the [Safe Recovery Service API](https://docs.candide.dev/wallet/recovery/overview.md) section. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Installation[​](#installation "Direct link to Installation") * npm * yarn ``` npm i safe-recovery-service-sdk abstractionkit viem ``` ``` yarn add safe-recovery-service-sdk abstractionkit viem ``` * `safe-recovery-service-sdk` provides the alert subscription functionality * `abstractionkit` provides Safe account creation and management * `viem` is used for message signing and wallet operations ### Environment Setup[​](#environment-setup "Direct link to Environment Setup") .env ``` # Network Configuration CHAIN_ID=11155111 # Sepolia testnet chain ID NODE_URL=https://ethereum-sepolia-rpc.publicnode.com # Bundler and Paymaster URLs BUNDLER_URL=https://api.candide.dev/public/v3/11155111 PAYMASTER_URL=https://api.candide.dev/public/v3/11155111 # Recovery Service URL # Get access here: https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5 RECOVERY_SERVICE_URL= # Safe Owner OWNER_PRIVATE_KEY=0x.. # Alert Configuration USER_EMAIL=owner@example.com USER_PHONE=+1234567890 ``` ## Alert Subscription Steps[​](#alert-subscription-steps "Direct link to Alert Subscription Steps") > Fork the [complete code](https://github.com/candidelabs/safe-recovery-service-sdk/tree/main/examples/02-alerts-setup) to follow along. ### Step 1: Initialize Alert Service[​](#step-1-initialize-alert-service "Direct link to Step 1: Initialize Alert Service") Set up the recovery service client and account credentials. initialize-alert-service.ts ``` import { Alerts } from "safe-recovery-service-sdk"; import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit"; import { privateKeyToAccount } from "viem/accounts"; import * as dotenv from 'dotenv'; dotenv.config(); const recoveryServiceURL = process.env.RECOVERY_SERVICE_URL as string; const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY as `0x${string}`; const ownerEmail = process.env.USER_EMAIL as string; const ownerPhone = process.env.USER_PHONE as string; const chainId = BigInt(process.env.CHAIN_ID as string); const ownerAccount = privateKeyToAccount(ownerPrivateKey); const smartAccount = SafeAccount.initializeNewAccount([ownerAccount.address]); const safeAccountAddress = smartAccount.accountAddress; const alertsService = new Alerts(recoveryServiceURL, chainId); ``` ### Step 2: Create SIWE Messages[​](#step-2-create-siwe-messages "Direct link to Step 2: Create SIWE Messages") Generate Sign-in with Ethereum (EIP-4361) messages for subscription verification using the SDK helper methods. * Email Subscription * SMS Subscription create-email-siwe-message.ts ``` const emailSiweMessage = alertsService.createEmailSubscriptionSiweStatementToSign( safeAccountAddress, ownerAccount.address, ownerEmail ); ``` create-sms-siwe-message.ts ``` const smsSiweMessage = alertsService.createSubscriptionSiweStatementToSign( safeAccountAddress, ownerAccount.address, "sms", ownerPhone ); ``` ### Step 3: Sign Messages and Create Subscriptions[​](#step-3-sign-messages-and-create-subscriptions "Direct link to Step 3: Sign Messages and Create Subscriptions") Sign the SIWE messages and submit subscription requests for both email and SMS. * Email Subscription * SMS Subscription subscribe-email-alerts.ts ``` const emailSignature = await ownerAccount.signMessage({ message: emailSiweMessage }); const emailSubscriptionId = await alertsService.createEmailSubscription( safeAccountAddress, ownerAccount.address, ownerEmail, emailSiweMessage, emailSignature ); ``` subscribe-sms-alerts.ts ``` const smsSignature = await ownerAccount.signMessage({ message: smsSiweMessage }); const smsSubscriptionId = await alertsService.createSubscription( safeAccountAddress, ownerAccount.address, "sms", ownerPhone, smsSiweMessage, smsSignature ); ``` ### Step 4: Activate Subscriptions[​](#step-4-activate-subscriptions "Direct link to Step 4: Activate Subscriptions") Enter the verification codes received via email and SMS to activate both subscriptions. * Email Activation * SMS Activation activate-email-subscription.ts ``` const emailVerificationCode = "123456"; await alertsService.activateSubscription( emailSubscriptionId, emailVerificationCode ); ``` activate-sms-subscription.ts ``` const smsVerificationCode = "654321"; await alertsService.activateSubscription( smsSubscriptionId, smsVerificationCode ); ``` ### Step 5: Verify Active Subscriptions[​](#step-5-verify-active-subscriptions "Direct link to Step 5: Verify Active Subscriptions") Check your current active alert subscriptions. verify-subscriptions.ts ``` const getSubscriptionsSiweMessage = alertsService.getSubscriptionsSiweStatementToSign( ownerAccount.address ); const getSubscriptionsSignature = await ownerAccount.signMessage({ message: getSubscriptionsSiweMessage }); const activeSubscriptions = await alertsService.getActiveSubscriptions( safeAccountAddress, ownerAccount.address, getSubscriptionsSiweMessage, getSubscriptionsSignature ); ``` ## Complete Working Example[​](#complete-working-example "Direct link to Complete Working Example") Full Alert Subscription Example examples/02-alerts-setup/index.ts ``` loading... ``` [See full example on GitHub]() ## What's Next[​](#whats-next "Direct link to What's Next") * [Enable Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md): add Candide's managed guardian service so users can recover via email or SMS without managing guardian keys * [Recovery API Reference](https://docs.candide.dev/wallet/recovery/ux-api.md): explore the full alert and recovery API surface ## Managing Subscriptions[​](#managing-subscriptions "Direct link to Managing Subscriptions") ### Update Contact Information[​](#update-contact-information "Direct link to Update Contact Information") To change your alert email or phone number: 1. **Unsubscribe from current alerts** for that channel using the unsubscribe method with SIWE authentication 2. **Subscribe with new contact information** using the subscription flow 3. **Verify the new subscription** by entering the OTP code sent to your new contact ### Unsubscribe from Specific Channel[​](#unsubscribe-from-specific-channel "Direct link to Unsubscribe from Specific Channel") To stop receiving alerts on one channel: ``` // Generate SIWE message for unsubscribing const unsubscribeSiweMessage = alertsService.unsubscribeSiweStatementToSign( ownerAccount.address ); // Sign the SIWE message const unsubscribeSignature = await ownerAccount.signMessage({ message: unsubscribeSiweMessage }); // Unsubscribe from email only const emailUnsubscribeSuccess = await alertsService.unsubscribe( "your-email-subscription-id", ownerAccount.address, unsubscribeSiweMessage, unsubscribeSignature ); if (emailUnsubscribeSuccess) { console.log("Successfully unsubscribed from email alerts"); // SMS subscription remains active } ``` ### Unsubscribe from All Channels[​](#unsubscribe-from-all-channels "Direct link to Unsubscribe from All Channels") To stop all recovery alerts: ``` // Generate SIWE message for unsubscribing const unsubscribeSiweMessage = alertsService.unsubscribeSiweStatementToSign( ownerAccount.address ); // Sign the SIWE message const unsubscribeSignature = await ownerAccount.signMessage({ message: unsubscribeSiweMessage }); // Unsubscribe from all channels const emailUnsubscribeSuccess = await alertsService.unsubscribe( "email-subscription-id", ownerAccount.address, unsubscribeSiweMessage, unsubscribeSignature ); const smsUnsubscribeSuccess = await alertsService.unsubscribe( "sms-subscription-id", ownerAccount.address, unsubscribeSiweMessage, unsubscribeSignature ); if (emailUnsubscribeSuccess && smsUnsubscribeSuccess) { console.log("Successfully unsubscribed from all alerts"); } ``` --- # Complete Recovery Flow Guide Learn how to implement a complete account recovery flow using guardians and the Safe Recovery Service. This guide assumes you have a Safe account with the recovery module enabled and guardians configured. info The Recovery Service requires access credentials. [Request access here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). > To set up the recovery module, see [Enable Recovery Module and Add Guardians](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md). ## Overview[​](#overview "Direct link to Overview") The recovery flow consists of four key steps: 1. **Recovery Request**: Create and submit a recovery request 2. **Guardian Approval**: Collect required guardian signatures 3. **Execution**: Execute recovery after collecting all signatures 4. **Finalization**: Complete ownership transfer after the grace period expires ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Installation[​](#installation "Direct link to Installation") * npm * yarn ``` npm i abstractionkit safe-recovery-service-sdk viem ``` ``` yarn add abstractionkit safe-recovery-service-sdk ``` 1. `abstractionkit` provides the core functionality for interacting with social recovery module smart contracts and constructing calldata. 2. `safe-recovery-service-sdk` adds optional API services including alerts, recovery via email/SMS, and additional recovery features. 3. `viem` provides the ethereum library utils to generate private keys and sign transactions. You can also use `ethers`. ### Environment Setup[​](#environment-setup "Direct link to Environment Setup") .env ``` CHAIN_ID=11155111 BUNDLER_URL=https://api.candide.dev/public/v3/11155111 NODE_URL=https://ethereum-sepolia-rpc.publicnode.com RECOVERY_SERVICE_URL= #optional GUARDIAN_1_PRIVATE_KEY= GUARDIAN_2_PRIVATE_KEY= ``` ## Recovery Steps[​](#recovery-steps "Direct link to Recovery Steps") > Fork the [recovery example](https://github.com/candidelabs/safe-recovery-service-sdk/tree/main/examples/03-recovery-flow) to follow along. ### Step 1: Initialize Services[​](#step-1-initialize-services "Direct link to Step 1: Initialize Services") Initialize the recovery service and guardian references. About the Recovery Service The [Candide Recovery Service](https://docs.candide.dev/wallet/recovery/overview.md) is an optional hosted service that streamlines recovery by automatically executing recovery after collecting required guardian signatures and finalizing it after the grace period expires. Request access to the recovery service [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). **Alternative**: Anyone can [execute](https://docs.candide.dev/blog/making-accounts-recoverable#multi-confirm-recovery) and [finalize](https://docs.candide.dev/blog/making-accounts-recoverable#finalize-recovery) recovery transactions directly using Social Recovery Module methods, which are public and callable once guardian signatures are collected and grace period requirements are met. initialize-services.ts ``` import { SafeMultiChainSigAccountV1 as SafeAccount, SocialRecoveryModule, SocialRecoveryModuleGracePeriodSelector, } from "abstractionkit"; import { RecoveryByGuardian } from "safe-recovery-service-sdk"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; const recoveryServiceURL = process.env.RECOVERY_SERVICE_URL as string; const chainId = BigInt(process.env.CHAIN_ID as string); const smartAccountAddress = "0x..."; const newOwnerAddress = "0x..."; const guardian1PrivateKey = process.env.GUARDIAN_1_PRIVATE_KEY as `0x${string}`; const guardian1Account = privateKeyToAccount(guardian1PrivateKey); const guardian2PrivateKey = process.env.GUARDIAN_2_PRIVATE_KEY as `0x${string}`; const guardian2Account = privateKeyToAccount(guardian2PrivateKey); const srm = new SocialRecoveryModule(SocialRecoveryModuleGracePeriodSelector.After3Minutes); const recoveryService = new RecoveryByGuardian( recoveryServiceURL, chainId, SocialRecoveryModuleGracePeriodSelector.After3Minutes ); ``` ### Step 2: Create Recovery Request[​](#step-2-create-recovery-request "Direct link to Step 2: Create Recovery Request") * First guardian signs EIP-712 recovery data * Guardian 1 submits the recovery request with their signature (on-chain or through the service) * Service generates a unique emoji sequence for this recovery request recovery-request.ts ``` import { TypedDataDomain } from 'viem'; import { EXECUTE_RECOVERY_PRIMARY_TYPE } from "abstractionkit" const nodeUrl = process.env.NODE_URL as string; const recoveryRequestEip712Data = await srm.getRecoveryRequestEip712Data( nodeUrl, chainId, smartAccountAddress, [newOwnerAddress], 1n ); const guardian1Signature = await guardian1Account.signTypedData({ primaryType: EXECUTE_RECOVERY_PRIMARY_TYPE, domain: recoveryRequestEip712Data.domain as TypedDataDomain, types: recoveryRequestEip712Data.types, message: recoveryRequestEip712Data.messageValue }); const recoveryRequest = await recoveryService.createRecoveryRequest( smartAccountAddress, [newOwnerAddress], 1n, guardian1Account.address, guardian1Signature ); ``` ### Step 3: Second Guardian Signature[​](#step-3-second-guardian-signature "Direct link to Step 3: Second Guardian Signature") The second guardian and each consecutive guardian must sign the recovery request using EIP-712 signatures: guardian-signatures.ts ``` const guardian2Signature = await guardian2Account.signTypedData({ primaryType: EXECUTE_RECOVERY_PRIMARY_TYPE, domain: recoveryRequestEip712Data.domain as TypedDataDomain, types: recoveryRequestEip712Data.types, message: recoveryRequestEip712Data.messageValue }); await recoveryService.submitGuardianSignatureForRecoveryRequest( recoveryRequest.id, guardian2Account.address, guardian2Signature ); ``` ### Step 4: Execute Recovery[​](#step-4-execute-recovery "Direct link to Step 4: Execute Recovery") Once all guardian signatures are collected or the threshold is met, the service executes the recovery to initiate the grace period: recovery-execution.ts ``` await recoveryService.executeRecoveryRequest(recoveryRequest.id); await new Promise(resolve => setTimeout(resolve, 1 * 30 * 1000)); // wait for tx to be included const executedRequest = await recoveryService.getExecutedRecoveryRequestForLatestNonce( nodeUrl, smartAccountAddress ); if (executedRequest && executedRequest.status === "EXECUTED") { console.log("Transaction hash:", executedRequest.executeData.transactionHash); } ``` ### Step 5: Finalize Recovery[​](#step-5-finalize-recovery "Direct link to Step 5: Finalize Recovery") After the grace period has elapsed, anyone can finalize the recovery. Here's how to use the recovery service: recovery-finalization.ts ``` const finalizationResult = await recoveryService.finalizeRecoveryRequest( recoveryRequest.id ); const pendingRequests = await recoveryService.getPendingRecoveryRequestsForLatestNonce( nodeUrl, smartAccountAddress ); ``` ## Complete Working Example[​](#complete-working-example "Direct link to Complete Working Example") Full Recovery Flow Example examples/03-recovery-flow/index.ts ``` loading... ``` [See full example on GitHub]() ## What's Next[​](#whats-next "Direct link to What's Next") * [Recovery Alerts Guide](https://docs.candide.dev/wallet/plugins/recovery-alerts-guide.md): set up email and SMS notifications so account owners are alerted if a recovery is ever initiated * [Enable Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md): use Candide's managed guardian service instead of self-managed guardian keys ## Security Considerations[​](#security-considerations "Direct link to Security Considerations") ### Emoji Verification[​](#emoji-verification "Direct link to Emoji Verification") Candide Recovery Service provides an emoji-based communication system enabling guardians to verify and approve legitimate recovery requests. * **Unique Verification**: Each recovery request generates a unique emoji sequence * **Anti-Phishing**: Prevents malicious recovery attempts through social engineering * **Guardian Verification**: Guardians must verify the emoji with the account owner before signing * **Secure Communication**: Share emojis only through trusted, encrypted channels ### Signatures[​](#signatures "Direct link to Signatures") * **EIP-712**: All signatures use typed data for replay protection * **Domain Separation**: Signatures are chain-specific * **Nonce Protection**: Each recovery request includes a unique nonce ### Grace Period[​](#grace-period "Direct link to Grace Period") * **Sufficient Duration**: Allow adequate time for legitimate owners to cancel unauthorized recovery attempts * **Monitoring**: Implement notification systems to alert owners of recovery attempts, such as [Candide's Alert System](https://docs.candide.dev/wallet/recovery/ux-api.md#alerts) ## Utility Methods[​](#utility-methods "Direct link to Utility Methods") Common utilities you might find helpful: ``` // Check guardian status const isGuardian = await srm.isGuardian( nodeUrl, smartAccountAddress, guardianAddress ); if (!isGuardian) { throw new Error("Address is not a configured guardian"); } // Verify threshold const threshold = await srm.threshold( nodeUrl, smartAccountAddress ); console.log(`Required guardian signatures: ${threshold}`); ``` --- # SocialRecoveryModule SDK Reference ## Install[​](#install "Direct link to Install") tip Prefer to run a simple example? Fork this repo and add your first guardian [here](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts). * npm * yarn ``` npm i abstractionkit ``` ``` yarn add abstractionkit ``` ## Import[​](#import "Direct link to Import") ``` import { SocialRecoveryModule } from "abstractionkit"; ``` ## Initialize[​](#initialize "Direct link to Initialize") Initialize a SocialRecoveryModule instance. By default, the grace period for recovery is set to 3 days. ``` const srm = new SocialRecoveryModule(); // 3 days ``` If you need a different grace period, you can override it by providing a [different grace period address](https://docs.candide.dev/wallet/technical-reference/deployments.md#social-recovery-module) during initialization. ``` import { SocialRecoveryModule, SocialRecoveryModuleGracePeriodSelector } from "abstractionkit"; const gracePeriod = SocialRecoveryModuleGracePeriodSelector.After7Days; const srm = new SocialRecovery(gracePeriod); // 7 days ``` Then consume the module methods like the following: ``` const accountAddress = "0x1.." const guardianAddress= "0x2.." const metaTransaction1 = srm.createEnableModuleMetaTransaction(accountAddress); const metaTransaction2 = srm.createAddGuardianWithThresholdMetaTransaction( guardianAddress, 1n //threshold ); ``` ## Setup Guardians[​](#setup-guardians "Direct link to Setup Guardians") ### createEnableModuleMetaTransaction[​](#createenablemodulemetatransaction "Direct link to createEnableModuleMetaTransaction") Active the recovery plugin for the smart account * Param Types * Return Types | key | type | description | | :--------------- | :------- | :-------------------------- | | `accountAddress` | `string` | The target account address. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ### createAddGuardianWithThresholdMetaTransaction[​](#createaddguardianwiththresholdmetatransaction "Direct link to createAddGuardianWithThresholdMetaTransaction") Creates a MetaTransaction that lets the owner add a guardian for its account. * Param Types * Return Types | key | type | description | | :---------------- | :------- | :------------------------------------------------- | | `guardianAddress` | `string` | The guardian to add. | | `threshold` | `bigint` | The new threshold that will be set after addition. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ## Change Guardians Setup[​](#change-guardians-setup "Direct link to Change Guardians Setup") ### createRevokeGuardianWithThresholdMetaTransaction[​](#createrevokeguardianwiththresholdmetatransaction "Direct link to createRevokeGuardianWithThresholdMetaTransaction") Creates a MetaTransaction that lets the owner revoke a guardian from its account. This async version auto-detects the previous guardian by querying the node. * Param Types * Return Types | key | type | description | | :---------------- | :------- | :----------------------------------------------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain (to get the prevGuardian parameter). | | `accountAddress` | `string` | The target account. | | `guardianAddress` | `string` | The guardian to revoke. | | `threshold` | `bigint` | The new threshold that will be set after execution of revocation. | | `overrides` | `object` | Optional overrides object with prevGuardianAddress. | | Param Name | Param Type | Description | | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | Promise\ | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ### createStandardRevokeGuardianWithThresholdMetaTransaction[​](#createstandardrevokeguardianwiththresholdmetatransaction "Direct link to createStandardRevokeGuardianWithThresholdMetaTransaction") Creates a MetaTransaction that lets the owner revoke a guardian from its account. This synchronous version requires the previous guardian address to be provided manually. * Param Types * Return Types | key | type | description | | :-------------------- | :------- | :---------------------------------------------------------------- | | `prevGuardianAddress` | `string` | The previous guardian linking to the guardian in the linked list. | | `guardianAddress` | `string` | The guardian to revoke. | | `threshold` | `bigint` | The new threshold that will be set after execution of revocation. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ### createChangeThresholdMetaTransaction[​](#createchangethresholdmetatransaction "Direct link to createChangeThresholdMetaTransaction") Creates a MetaTransaction that lets the owner change the guardian threshold required to initiate a recovery. * Param Types * Return Types | key | type | description | | :---------- | :------- | :---------------------------------------------------------------- | | `threshold` | `bigint` | The new threshold that will be set after execution of revokation. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ## Recover[​](#recover "Direct link to Recover") ### createConfirmRecoveryMetaTransaction[​](#createconfirmrecoverymetatransaction "Direct link to createConfirmRecoveryMetaTransaction") Creates a MetaTransaction that lets a single guardian confirm the execution of the recovery request. It can also trigger the start of the execution by passing true to 'execute' parameter. Once triggered, the recovery will started the recovery period delay, before it can be finalised. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :------------------------------------------- | | `accountAddress` | `string` | The target account address. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | | `execute` | `boolean` | Whether to auto-start execution of recovery. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation | ### createMultiConfirmRecoveryMetaTransaction[​](#createmulticonfirmrecoverymetatransaction "Direct link to createMultiConfirmRecoveryMetaTransaction") Creates a MetaTransaction that lets multiple guardians confirm the execution of the recovery request. It can also trigger the start of the execution by passing true to 'execute' parameter. Once triggered, the recovery will start the recovery period delay, before it can be finalised. * Param Types * Return Types | key | type | description | | :------------------ | :------------------------ | :---------------------------------------------- | | `accountAddress` | `string` | The target account address. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | | `signaturePairList` | `RecoverySignaturePair[]` | The guardians signers and signatures pair list. | | `execute` | `boolean` | Whether to auto-start execution of recovery. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation | ### createExecuteRecoveryMetaTransaction[​](#createexecuterecoverymetatransaction "Direct link to createExecuteRecoveryMetaTransaction") Creates a MetaTransaction that lets the guardians start the execution of the recovery request. Once triggered, the recovery will be pending for the recovery period before it can be finalized. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :------------------------------ | | `accountAddress` | `string` | The target account address. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ### createFinalizeRecoveryMetaTransaction[​](#createfinalizerecoverymetatransaction "Direct link to createFinalizeRecoveryMetaTransaction") Creates a MetaTransaction that finalizes an ongoing recovery request if the recovery period is over. The method is public and callable by anyone to enable orchestration. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :-------------------------- | | `accountAddress` | `string` | The target account address. | | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ## Cancel a Recovery[​](#cancel-a-recovery "Direct link to Cancel a Recovery") ### createCancelRecoveryMetaTransaction[​](#createcancelrecoverymetatransaction "Direct link to createCancelRecoveryMetaTransaction") Creates a MetaTransaction that lets the account cancel an ongoing recovery request. * Param Types * Return Types | Param Name | Param Type | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | MetaTransaction | MetaTransactionkey type descriptionMetaTransaction object MetaTransaction is the type of transaction to construct a Safe operation. MetaTransaction.to string To address, or the the target contract address for the transaction MetaTransaction.value BigNumberish Value transfered if making a native token transfer. (usually 0n for contract interaction with non-native tokens like erc-20 tokens) MetaTransaction.data BytesLike The call data for the transaction MetaTransaction.operation Operation: enum Default to 0 for a Call. 1 for a Delegate Call. (Optional) | MetaTransaction is the type of a transaction to construct a Safe operation. | ## Helpers[​](#helpers "Direct link to Helpers") ### hasGuardianApproved[​](#hasguardianapproved "Direct link to hasGuardianApproved") Retrieves specific guardian approval status for a particular recovery request at the current nonce. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :----------------------------------------- | | `nodeURL` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | | `guardian` | `string` | The guardian. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | Boolean ### isGuardian[​](#isguardian "Direct link to isGuardian") Checks if an address is a guardian for an account. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | | `guardian` | `string` | The address to check. | Boolean ### guardiansCount[​](#guardianscount "Direct link to guardiansCount") Counts the number of active guardians for an account. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | BigInt ### threshold[​](#threshold "Direct link to threshold") Retrieves the guardians threshold for the account. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeURL` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | BigInt ### getGuardians[​](#getguardians "Direct link to getGuardians") Get the active guardians for an account. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | String\[] ### getRecoveryRequest[​](#getrecoveryrequest "Direct link to getRecoveryRequest") Retrieves the account's current ongoing recovery request. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | | key | type | description | | :----------------------- | :--------- | :-------------------------------------------------- | | `guardiansApprovalCount` | `bigint` | The number of guardian approvals. | | `newThreshold` | `bigint` | The new threshold for the safe. | | `executeAfter` | `bigint` | Timestamp indicating when the execution can happen. | | `newOwners` | `string[]` | Array of new owners' addresses. | ### getRecoveryApprovals[​](#getrecoveryapprovals "Direct link to getRecoveryApprovals") Retrieves the guardian approval count for this particular recovery request at the current nonce. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :----------------------------------------- | | `nodeUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | BigInt ### nonce[​](#nonce "Direct link to nonce") Get the module nonce for an account. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The Node URL of the Ethereum RPC endpoint. | | `accountAddress` | `string` | The target account address. | BigInt ### getRecoveryHash[​](#getrecoveryhash "Direct link to getRecoveryHash") Generates the recovery hash that should be signed by the guardian to authorize a recovery. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :----------------------------------------- | | `nodeRpcUrl` | `string` | The JSON-RPC API url for the target chain. | | `accountAddress` | `string` | The target account address. | | `newOwners` | `string[]` | The new owners' addresses. | | `newThreshold` | `number` | The new threshold for the safe. | | `nonce` | `bigint` | Recovery nonce. | Promise\ ### getRecoveryRequestEip712Data[​](#getrecoveryrequesteip712data "Direct link to getRecoveryRequestEip712Data") Creates recovery request EIP-712 data for signing by guardians. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :------------------------------------- | | `rpcNode` | `string` | Node to fetch the recovery nonce. | | `chainId` | `bigint` | Chain ID of the target network. | | `accountAddress` | `string` | Address of account to recover. | | `newOwners` | `string[]` | New owners to recover to. | | `newThreshold` | `bigint` | New threshold. | | `overrides` | `object` | Optional overrides with recoveryNonce. | Promise\ Object containing domain, types, and messageValue for EIP-712 signing. ## Types[​](#types "Direct link to Types") ### RecoverySignaturePair[​](#recoverysignaturepair "Direct link to RecoverySignaturePair") A guardian address paired with its EIP-712 signature authorizing a recovery. | key | type | description | | :---------- | :------- | :-------------------------------------------- | | `signer` | `string` | Guardian address that produced the signature. | | `signature` | `string` | Hex-encoded signature bytes. | --- # Recovery Service SDK Reference The Safe Recovery Service SDK provides TypeScript classes for interacting with the [Safe Recovery Service](https://docs.candide.dev/wallet/recovery/overview.md). It covers three main workflows: self-managed guardian recovery, custodial guardian recovery (email/SMS), and recovery alert notifications. info The Recovery Service requires access credentials. [Request access here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). tip Looking for the on-chain Social Recovery Module SDK? See the [Module SDK Reference](https://docs.candide.dev/wallet/plugins/recovery-module-reference.md). ## Install[​](#install "Direct link to Install") * npm * yarn ``` npm i safe-recovery-service-sdk ``` ``` yarn add safe-recovery-service-sdk ``` ## Import[​](#import "Direct link to Import") ``` import { RecoveryByGuardian, RecoveryByCustodialGuardian, Alerts, generateSIWEMessage, SafeRecoveryServiceSdkError, SocialRecoveryModuleGracePeriodSelector, } from "safe-recovery-service-sdk"; ``` *** ## RecoveryByGuardian[​](#recoverybyguardian "Direct link to RecoveryByGuardian") Service client for managing recovery flows using self-managed guardians. Handles creating recovery requests, collecting guardian signatures, and executing/finalizing recovery. ### Initialize[​](#initialize "Direct link to Initialize") ``` import { RecoveryByGuardian, SocialRecoveryModuleGracePeriodSelector } from "safe-recovery-service-sdk"; const recoveryService = new RecoveryByGuardian( "https://safe-recovery.candide.dev", 1n, // chainId SocialRecoveryModuleGracePeriodSelector.After3Days ); ``` * Param Types | key | type | description | | :---------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | `serviceEndpoint` | `string` | The URL of the Safe Recovery Service endpoint | | `chainId` | `bigint` | The chain ID of the network (e.g. 1n for Ethereum mainnet) | | `recoveryModuleAddress` | `SocialRecoveryModuleGracePeriodSelector` | The address of the Social Recovery Module with the desired grace period. Options: After3Minutes, After3Days, After7Days, After14Days | ### createRecoveryRequest[​](#createrecoveryrequest "Direct link to createRecoveryRequest") Creates a new recovery request with the specified new owners and threshold. Requires a guardian signature authorizing the recovery. * Param Types * Return Types | key | type | description | | :------------------ | :--------- | :--------------------------------------------------------------- | | `accountAddress` | `string` | The Safe account address to recover | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `guardianAddress` | `string` | The guardian address initiating the recovery request | | `guardianSignature` | `string` | The EIP-712 signature from the guardian authorizing the recovery | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### submitGuardianSignatureForRecoveryRequest[​](#submitguardiansignatureforrecoveryrequest "Direct link to submitGuardianSignatureForRecoveryRequest") Submits an additional guardian signature for an existing recovery request. Used when multiple guardian approvals are required. * Param Types * Return Types | key | type | description | | :------------------ | :------- | :-------------------------------------------- | | `id` | `string` | The recovery request ID | | `guardianAddress` | `string` | The guardian address submitting the signature | | `guardianSignature` | `string` | The EIP-712 signature from the guardian | Promise\ Returns true if the signature was accepted. ### executeRecoveryRequest[​](#executerecoveryrequest "Direct link to executeRecoveryRequest") Triggers the execution of a recovery request. Once executed, the grace period countdown begins before the recovery can be finalized. * Param Types * Return Types | key | type | description | | :--- | :------- | :--------------------------------- | | `id` | `string` | The recovery request ID to execute | Promise\ Returns true once the execution transaction is submitted. ### finalizeRecoveryRequest[​](#finalizerecoveryrequest "Direct link to finalizeRecoveryRequest") Finalizes a recovery request after the grace period has elapsed. This completes the ownership transfer. * Param Types * Return Types | key | type | description | | :--- | :------- | :---------------------------------- | | `id` | `string` | The recovery request ID to finalize | Promise\ Returns true once the finalization transaction is submitted. ### getRecoveryRequests[​](#getrecoveryrequests "Direct link to getRecoveryRequests") Queries recovery requests for a specific account and nonce. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :--------------------------------- | | `accountAddress` | `string` | The Safe account address | | `recoveryNonce` | `bigint` | The recovery module nonce to query | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### getRecoveryRequestsForLatestNonce[​](#getrecoveryrequestsforlatestnonce "Direct link to getRecoveryRequestsForLatestNonce") Fetches recovery requests for the latest on-chain nonce, filtered by status. Automatically queries the RPC node for the current nonce. * Param Types * Return Types | key | type | description | | :--------------- | :--------------------------------------- | :------------------------------------------- | | `rpcNode` | `string` | The RPC node URL to query the on-chain nonce | | `accountAddress` | `string` | The Safe account address | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Filter recovery requests by status | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### getPendingRecoveryRequestsForLatestNonce[​](#getpendingrecoveryrequestsforlatestnonce "Direct link to getPendingRecoveryRequestsForLatestNonce") Convenience method that fetches pending recovery requests for the latest nonce. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :------------------------------------------- | | `rpcNode` | `string` | The RPC node URL to query the on-chain nonce | | `accountAddress` | `string` | The Safe account address | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### getExecutedRecoveryRequestForLatestNonce[​](#getexecutedrecoveryrequestforlatestnonce "Direct link to getExecutedRecoveryRequestForLatestNonce") Fetches the executed recovery request for the latest nonce, if one exists. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :------------------------------------------- | | `rpcNode` | `string` | The RPC node URL to query the on-chain nonce | | `accountAddress` | `string` | The Safe account address | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### getFinalizedRecoveryRequestForLatestNonce[​](#getfinalizedrecoveryrequestforlatestnonce "Direct link to getFinalizedRecoveryRequestForLatestNonce") Fetches the finalized recovery request for the latest nonce, if one exists. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :------------------------------------------- | | `rpcNode` | `string` | The RPC node URL to query the on-chain nonce | | `accountAddress` | `string` | The Safe account address | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | *** ## RecoveryByCustodialGuardian[​](#recoverybycustodialguardian "Direct link to RecoveryByCustodialGuardian") Interface for managing recovery using custodial guardians via email or SMS. Handles channel registration, OTP verification, and recovery execution through Candide's custodial guardian service. ### Initialize[​](#initialize-1 "Direct link to Initialize") ``` import { RecoveryByCustodialGuardian } from "safe-recovery-service-sdk"; const custodialRecovery = new RecoveryByCustodialGuardian( "https://safe-recovery.candide.dev", 1n, // chainId ); ``` You can optionally override the SIWE domain and URI: ``` const custodialRecovery = new RecoveryByCustodialGuardian( "https://safe-recovery.candide.dev", 1n, { siweDomain: "myapp.com", siweUri: "https://myapp.com" } ); ``` * Param Types | key | type | description | | :---------------- | :-------------------- | :------------------------------------------------------------------------------------------------ | | `serviceEndpoint` | `string` | The URL of the Safe Recovery Service endpoint | | `chainId` | `bigint` | The chain ID of the network (e.g. 1n for Ethereum mainnet) | | `overrides` | `object \| undefined` | Optional overrides for SIWE message generation. Contains siweDomain (string) and siweUri (string) | ### Registration Management[​](#registration-management "Direct link to Registration Management") #### getRegistrationsSiweStatementToSign[​](#getregistrationssiwestatementtosign "Direct link to getRegistrationsSiweStatementToSign") Returns the SIWE statement string that needs to be signed to fetch registrations. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------- | | `accountAddress` | `string` | The Safe account address | string The SIWE statement to sign. #### getRegistrations[​](#getregistrations "Direct link to getRegistrations") Fetches active email/SMS recovery registrations for a Safe account. * Param Types * Return Types | key | type | description | | :----------------------------- | :------- | :--------------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `eip1271SiweContractSignature` | `string` | The EIP-1271 contract signature for the SIWE message | Promise\ | key | type | description | | :-------- | :------- | :------------------------------------------- | | `id` | `string` | Unique registration ID | | `channel` | `string` | The recovery channel: "email" or "sms" | | `target` | `string` | The email address or phone number registered | #### createRegistrationToEmailRecoverySiweStatementToSign[​](#createregistrationtoemailrecoverysiwestatementtosign "Direct link to createRegistrationToEmailRecoverySiweStatementToSign") Returns the SIWE statement for registering an email recovery method. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :----------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `email` | `string` | The email address to register for recovery | string The SIWE statement to sign. #### createRegistrationToEmailRecovery[​](#createregistrationtoemailrecovery "Direct link to createRegistrationToEmailRecovery") Registers an email address as a recovery channel. Returns a challenge ID for OTP verification. * Param Types * Return Types | key | type | description | | :----------------------------- | :------- | :--------------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `email` | `string` | The email address to register for recovery | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `eip1271SiweContractSignature` | `string` | The EIP-1271 contract signature for the SIWE message | Promise\ The challenge ID to use with submitRegistrationChallenge. #### createRegistrationToSmsRecoverySiweStatementToSign[​](#createregistrationtosmsrecoverysiwestatementtosign "Direct link to createRegistrationToSmsRecoverySiweStatementToSign") Returns the SIWE statement for registering an SMS recovery method. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :---------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `phoneNumber` | `string` | The phone number to register for recovery | string The SIWE statement to sign. #### createRegistrationToSmsRecovery[​](#createregistrationtosmsrecovery "Direct link to createRegistrationToSmsRecovery") Registers a phone number as a recovery channel. Returns a challenge ID for OTP verification. * Param Types * Return Types | key | type | description | | :----------------------------- | :------- | :--------------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `phoneNumber` | `string` | The phone number to register for recovery | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `eip1271SiweContractSignature` | `string` | The EIP-1271 contract signature for the SIWE message | Promise\ The challenge ID to use with submitRegistrationChallenge. #### createRegistrationToRecoverySiweStatementToSign[​](#createregistrationtorecoverysiwestatementtosign "Direct link to createRegistrationToRecoverySiweStatementToSign") Generic method that returns the SIWE statement for registering any recovery channel. * Param Types * Return Types | key | type | description | | :--------------- | :----------------- | :-------------------------------- | | `accountAddress` | `string` | The Safe account address | | `channel` | `"sms" \| "email"` | The recovery channel type | | `channelTarget` | `string` | The email address or phone number | string The SIWE statement to sign. #### createRegistrationToRecovery[​](#createregistrationtorecovery "Direct link to createRegistrationToRecovery") Generic method that registers a recovery channel. Returns a challenge ID for OTP verification. * Param Types * Return Types | key | type | description | | :----------------------------- | :----------------- | :--------------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `channel` | `"sms" \| "email"` | The recovery channel type | | `channelTarget` | `string` | The email address or phone number | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `eip1271SiweContractSignature` | `string` | The EIP-1271 contract signature for the SIWE message | Promise\ The challenge ID to use with submitRegistrationChallenge. #### submitRegistrationChallenge[​](#submitregistrationchallenge "Direct link to submitRegistrationChallenge") Submits the OTP code to verify a registration. Returns the registration ID and the assigned custodial guardian address. * Param Types * Return Types | key | type | description | | :------------- | :------- | :--------------------------------------------------- | | `challengeId` | `string` | The challenge ID received from the registration step | | `otpChallenge` | `string` | The OTP code received via email or SMS | Promise\ | key | type | description | | :---------------- | :------- | :----------------------------------------------------------- | | `registrationId` | `string` | The unique registration ID for the verified channel | | `guardianAddress` | `string` | The custodial guardian address assigned to this registration | #### deleteRegistrationSiweStatementToSign[​](#deleteregistrationsiwestatementtosign "Direct link to deleteRegistrationSiweStatementToSign") Returns the SIWE statement for deleting a registration. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :---------------------------- | | `accountAddress` | `string` | The Safe account address | | `registrationId` | `string` | The registration ID to delete | string The SIWE statement to sign. #### deleteRegistration[​](#deleteregistration "Direct link to deleteRegistration") Removes an email or SMS recovery registration. * Param Types * Return Types | key | type | description | | :----------------------------- | :------- | :--------------------------------------------------- | | `registrationId` | `string` | The registration ID to delete | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `eip1271SiweContractSignature` | `string` | The EIP-1271 contract signature for the SIWE message | Promise\ Returns true if the deletion was successful. ### Recovery Flow[​](#recovery-flow "Direct link to Recovery Flow") #### requestCustodialGuardianSignatureChallenge[​](#requestcustodialguardiansignaturechallenge "Direct link to requestCustodialGuardianSignatureChallenge") Initiates a recovery request through the custodial guardian. Sends OTP challenges to all registered recovery channels. * Param Types * Return Types | key | type | description | | :--------------- | :--------- | :------------------------------------- | | `accountAddress` | `string` | The Safe account address to recover | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | Promise\ | key | type | description | | :---------------------- | :------- | :------------------------------------------------------------------------- | | `requestId` | `string` | Unique signature request ID | | `requiredVerifications` | `number` | Minimum number of OTP challenges required to obtain the guardian signature | | `auths` | `object` | List of authentication methods that can be used for verification | | `auths.challengeId` | `string` | The challenge ID for this authentication method | | `auths.channel` | `string` | The channel type: "email" or "sms" | | `auths.target` | `string` | The email address or phone number to verify | #### submitCustodialGuardianSignatureChallenge[​](#submitcustodialguardiansignaturechallenge "Direct link to submitCustodialGuardianSignatureChallenge") Submits an OTP code for a specific authentication challenge. When all required verifications are collected, returns the custodial guardian address and signature. * Param Types * Return Types | key | type | description | | :------------- | :------- | :----------------------------------------------------------------------- | | `requestId` | `string` | The signature request ID from requestCustodialGuardianSignatureChallenge | | `challengeId` | `string` | The challenge ID for the specific authentication method | | `otpChallenge` | `string` | The OTP code received via email or SMS | Promise\ | key | type | description | | :--------------------------- | :-------------------- | :---------------------------------------------------------------------------------------------------------- | | `success` | `boolean` | Whether the OTP verification was successful | | `custodianGuardianAddress` | `string \| undefined` | The custodial guardian address (returned when all required verifications are collected) | | `custodianGuardianSignature` | `string \| undefined` | The recovery signature from the custodial guardian (returned when all required verifications are collected) | #### createAndExecuteRecoveryRequest[​](#createandexecuterecoveryrequest "Direct link to createAndExecuteRecoveryRequest") Creates a recovery request and immediately triggers execution using the custodial guardian's signature. Combines createRecoveryRequest and executeRecoveryRequest into a single call. * Param Types * Return Types | key | type | description | | :--------------------------- | :--------- | :--------------------------------------------------------------- | | `accountAddress` | `string` | The Safe account address to recover | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `custodianGuardianAddress` | `string` | The custodial guardian address obtained from signature challenge | | `custodianGuardianSignature` | `string` | The recovery signature from the custodial guardian | Promise\ | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | *** ## Alerts[​](#alerts "Direct link to Alerts") Service class for managing email and SMS alert subscriptions for recovery events on Safe accounts. Account owners receive notifications when recovery actions are initiated. ### Initialize[​](#initialize-2 "Direct link to Initialize") ``` import { Alerts } from "safe-recovery-service-sdk"; const alerts = new Alerts( "https://safe-recovery.candide.dev", 1n, // chainId ); ``` * Param Types | key | type | description | | :---------------- | :-------------------- | :------------------------------------------------------------------------------------------------ | | `serviceEndpoint` | `string` | The URL of the Safe Recovery Service endpoint | | `chainId` | `bigint` | The chain ID of the network (e.g. 1n for Ethereum mainnet) | | `overrides` | `object \| undefined` | Optional overrides for SIWE message generation. Contains siweDomain (string) and siweUri (string) | ### createEmailSubscriptionSiweStatementToSign[​](#createemailsubscriptionsiwestatementtosign "Direct link to createEmailSubscriptionSiweStatementToSign") Returns the SIWE statement for creating an email alert subscription. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :---------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `ownerAddress` | `string` | The owner address of the Safe account | | `email` | `string` | The email address for alert notifications | string The SIWE statement to sign. ### createEmailSubscription[​](#createemailsubscription "Direct link to createEmailSubscription") Creates an email alert subscription. Returns a subscription ID for OTP activation. * Param Types * Return Types | key | type | description | | :------------------- | :------- | :------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `ownerAddress` | `string` | The owner address of the Safe account | | `email` | `string` | The email address for alert notifications | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `siweOwnerSignature` | `string` | The owner's signature for the SIWE message | Promise\ The subscription ID to use with activateSubscription. ### createSubscriptionSiweStatementToSign[​](#createsubscriptionsiwestatementtosign "Direct link to createSubscriptionSiweStatementToSign") Generic method that returns the SIWE statement for creating an alert subscription on any channel. * Param Types * Return Types | key | type | description | | :--------------- | :----------------- | :------------------------------------ | | `accountAddress` | `string` | The Safe account address | | `ownerAddress` | `string` | The owner address of the Safe account | | `channel` | `"sms" \| "email"` | The notification channel type | | `channelTarget` | `string` | The email address or phone number | string The SIWE statement to sign. ### createSubscription[​](#createsubscription "Direct link to createSubscription") Generic method that creates an alert subscription on any channel. Returns a subscription ID for OTP activation. * Param Types * Return Types | key | type | description | | :------------------- | :----------------- | :------------------------------------------- | | `accountAddress` | `string` | The Safe account address | | `ownerAddress` | `string` | The owner address of the Safe account | | `channel` | `"sms" \| "email"` | The notification channel type | | `channelTarget` | `string` | The email address or phone number | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `siweOwnerSignature` | `string` | The owner's signature for the SIWE message | Promise\ The subscription ID to use with activateSubscription. ### activateSubscription[​](#activatesubscription "Direct link to activateSubscription") Verifies and activates a subscription using the OTP code sent to the email or phone number. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :------------------------------------- | | `subscriptionId` | `string` | The subscription ID to activate | | `otpChallenge` | `string` | The OTP code received via email or SMS | Promise\ Returns true if the subscription was activated. ### getSubscriptionsSiweStatementToSign[​](#getsubscriptionssiwestatementtosign "Direct link to getSubscriptionsSiweStatementToSign") Returns the SIWE statement for fetching active subscriptions. * Param Types * Return Types | key | type | description | | :------------- | :------- | :------------------------------------ | | `ownerAddress` | `string` | The owner address of the Safe account | string The SIWE statement to sign. ### getActiveSubscriptions[​](#getactivesubscriptions "Direct link to getActiveSubscriptions") Fetches all active alert subscriptions for a Safe account. * Param Types * Return Types | key | type | description | | :------------------- | :------- | :-------------------------------------------------- | | `accountAddress` | `string` | The Safe account address to query subscriptions for | | `ownerAddress` | `string` | The owner address of the Safe account | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `siweOwnerSignature` | `string` | The owner's signature for the SIWE message | Promise\ | key | type | description | | :-------- | :----------------- | :-------------------------------------------------------- | | `id` | `string` | Unique subscription ID | | `channel` | `"email" \| "sms"` | The notification delivery channel | | `target` | `string` | The email address or phone number receiving notifications | ### unsubscribeSiweStatementToSign[​](#unsubscribesiwestatementtosign "Direct link to unsubscribeSiweStatementToSign") Returns the SIWE statement for removing a subscription. * Param Types * Return Types | key | type | description | | :------------- | :------- | :------------------------------------ | | `ownerAddress` | `string` | The owner address of the Safe account | string The SIWE statement to sign. ### unsubscribe[​](#unsubscribe "Direct link to unsubscribe") Removes an alert subscription. * Param Types * Return Types | key | type | description | | :------------------- | :------- | :------------------------------------------- | | `subscriptionId` | `string` | The subscription ID to remove | | `ownerAddress` | `string` | The owner address of the Safe account | | `siweMessage` | `string` | The SIWE message to authenticate the request | | `siweOwnerSignature` | `string` | The owner's signature for the SIWE message | Promise\ Returns true if the unsubscription was successful. *** ## Utility Functions[​](#utility-functions "Direct link to Utility Functions") ### generateSIWEMessage[​](#generatesiwemessage "Direct link to generateSIWEMessage") Generates a Sign-In with Ethereum (EIP-4361) message. Used internally by `RecoveryByCustodialGuardian` and `Alerts`, but also available for custom integrations. * Param Types * Return Types | key | type | description | | :--------------- | :------- | :--------------------------------------------------------- | | `accountAddress` | `string` | The account address for the SIWE message | | `statement` | `string` | The human-readable statement for the SIWE message | | `chainId` | `bigint` | The chain ID of the network | | `siweDomain` | `string` | The domain for the SIWE message (e.g. 'example.com') | | `siweUri` | `string` | The URI for the SIWE message (e.g. 'https\://example.com') | string The formatted SIWE message ready for signing. *** ## Types[​](#types "Direct link to Types") ### RecoveryByGuardianRequest[​](#recoverybyguardianrequest "Direct link to RecoveryByGuardianRequest") Represents a recovery request with its current state and metadata. | key | type | description | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------ | | `id` | `string` | Unique recovery request ID | | `emoji` | `string` | Emoji identifier associated with the recovery request | | `account` | `string` | The Safe account address being recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | The chain ID of the network | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `string[]` | Guardian signatures collected for this recovery request | | `executeData` | `object` | Data about the recovery execution transaction | | `executeData.sponsored` | `boolean` | Whether the execution transaction is gas-sponsored | | `executeData.transactionHash` | `string \| undefined` | The transaction hash of the execution (if available) | | `finalizeData` | `object` | Data about the recovery finalization transaction | | `finalizeData.sponsored` | `boolean` | Whether the finalization transaction is gas-sponsored | | `finalizeData.transactionHash` | `string \| undefined` | The transaction hash of the finalization (if available) | | `status` | `"PENDING" \| "EXECUTED" \| "FINALIZED"` | Current status of the recovery request | | `discoverable` | `boolean` | Whether the recovery request is publicly discoverable | | `createdAt` | `string` | ISO 8601 timestamp of when the request was created | | `updatedAt` | `string` | ISO 8601 timestamp of the last update | ### Registration[​](#registration "Direct link to Registration") Represents an email or SMS recovery channel registration. | key | type | description | | :-------- | :------- | :------------------------------------------- | | `id` | `string` | Unique registration ID | | `channel` | `string` | The recovery channel: "email" or "sms" | | `target` | `string` | The email address or phone number registered | ### SignatureRequest[​](#signaturerequest "Direct link to SignatureRequest") Represents a custodial guardian signature request with its OTP challenges. | key | type | description | | :---------------------- | :------- | :------------------------------------------------------------------------- | | `requestId` | `string` | Unique signature request ID | | `requiredVerifications` | `number` | Minimum number of OTP challenges required to obtain the guardian signature | | `auths` | `object` | List of authentication methods that can be used for verification | | `auths.challengeId` | `string` | The challenge ID for this authentication method | | `auths.channel` | `string` | The channel type: "email" or "sms" | | `auths.target` | `string` | The email address or phone number to verify | ### AlertsSubscription[​](#alertssubscription "Direct link to AlertsSubscription") Represents an active alert subscription. | key | type | description | | :-------- | :----------------- | :-------------------------------------------------------- | | `id` | `string` | Unique subscription ID | | `channel` | `"email" \| "sms"` | The notification delivery channel | | `target` | `string` | The email address or phone number receiving notifications | ### SocialRecoveryModuleGracePeriodSelector[​](#socialrecoverymodulegraceperiodselector "Direct link to SocialRecoveryModuleGracePeriodSelector") Enum of deployed Social Recovery Module addresses with different grace periods. | key | type | description | | :-------------- | :------- | :-------------------------------------------------------------- | | `After3Minutes` | `string` | 0x949d01d424bE050D09C16025dd007CB59b3A8c66 (useful for testing) | | `After3Days` | `string` | 0x38275826E1933303E508433dD5f289315Da2541c | | `After7Days` | `string` | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | | `After14Days` | `string` | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | *** ## Error Handling[​](#error-handling "Direct link to Error Handling") ### SafeRecoveryServiceSdkError[​](#saferecoveryservicesdkerror "Direct link to SafeRecoveryServiceSdkError") All SDK methods throw `SafeRecoveryServiceSdkError` on failure. The error includes a code, message, and optional context for debugging. ``` import { SafeRecoveryServiceSdkError } from "safe-recovery-service-sdk"; try { await recoveryService.createRecoveryRequest(/* ... */); } catch (error) { if (error instanceof SafeRecoveryServiceSdkError) { console.log(error.code); // e.g. "HTTP_BAD_REQUEST" console.log(error.message); // Human-readable message console.log(error.context); // Additional context console.log(error.stringify()); // Serialized error (useful for React Native) } } ``` | key | type | description | | :-------- | :---------------------- | :-------------------------------------------------------------------------------------------------- | | `code` | `BasicErrorCode` | The error code identifying the type of error (e.g. HTTP\_BAD\_REQUEST, SIWE\_ERROR, UNKNOWN\_ERROR) | | `message` | `string` | Human-readable error message | | `context` | `Jsonable \| undefined` | Additional context about the error (e.g. URL, request options) | | `errno` | `number \| undefined` | Numeric error code (typically the HTTP status code) | **Error Codes:** | Code | Description | | -------------------------- | ------------------------------------------- | | `UNKNOWN_ERROR` | An unexpected error occurred | | `TIMEOUT` | The request timed out | | `SIWE_ERROR` | Error generating or validating SIWE message | | `BAD_DATA` | Invalid data provided | | `HTTP_BAD_REQUEST` | 400: Invalid request parameters | | `HTTP_UNAUTHORIZED` | 401: Authentication required | | `HTTP_FORBIDDEN` | 403: Insufficient permissions | | `HTTP_NOT_FOUND` | 404: Resource not found | | `HTTP_CONFLICT` | 409: Conflicting request | | `HTTP_TOO_MANY_REQUESTS` | 429: Rate limit exceeded | | `HTTP_INTERNAL_ERROR` | 500: Server error | | `HTTP_BAD_GATEWAY` | 502: Bad gateway | | `HTTP_SERVICE_UNAVAILABLE` | 503: Service temporarily unavailable | | `HTTP_GATEWAY_TIMEOUT` | 504: Gateway timeout | --- # Account Recovery Social Recovery allows users to regain control of their accounts by replacing the owner key through a set of trusted Guardians, with a built-in time delay so owners can cancel unauthorized attempts. The Candide Social Recovery Module accepts any Ethereum address as a guardian: family and friends' wallets, hardware wallets, institutions, or email/SMS cloud wallets. ## How Recovery Works[​](#how-recovery-works "Direct link to How Recovery Works") Recovery replaces a lost or compromised owner key with a new one, authorized by a quorum of trusted guardians. ### Guardians and Thresholds[​](#guardians-and-thresholds "Direct link to Guardians and Thresholds") The account owner chooses their guardians and sets a threshold: the minimum number of guardians required to approve a recovery. A typical setup is 3 guardians with a threshold of 2, ensuring no single guardian can take over the account. Guardians can be any Ethereum address. They do not need to hold any funds, and they have no control over the account during normal operations. ### The Recovery Flow[​](#the-recovery-flow "Direct link to The Recovery Flow") 1. A guardian (or the recovery service) submits a recovery request specifying new owner(s) 2. Other guardians sign to approve the request until the threshold is met 3. The recovery is executed, starting a **grace period** 4. After the grace period, anyone can finalize the recovery to complete the ownership transfer ### Grace Period[​](#grace-period "Direct link to Grace Period") The grace period is a time delay between execution and finalization. It gives the current owner a window to cancel unauthorized recovery attempts. If the owner still controls the account, they can call `cancelRecovery` at any point during this period. ### Privacy[​](#privacy "Direct link to Privacy") Guardians are encouraged to use fresh Ethereum addresses. This keeps their identity private and prevents malicious guardians from coordinating against the owner. Since guardians never need to hold funds or submit on-chain transactions during normal operation, fresh addresses have no cost. ## Recovery Methods[​](#recovery-methods "Direct link to Recovery Methods") Both recovery methods can be used independently or combined. For maximum security, use both. For example, a 2-of-3 setup with two personal guardians and email/SMS recovery as the third. | | Personal Guardians | Email / SMS Recovery | | ------------------ | ------------------------------------- | ----------------------------------------- | | **Guardian type** | Users add their own guardians | Candide signs after email/SMS OTP | | **Key management** | Required (guardian private keys) | Not required | | **Best for** | Trusted contacts, hardware wallets | Consumer apps, users who prefer email/SMS | | **Setup guides** | Add Guardian → Recovery Flow → Alerts | Enable Email/SMS → Recovery Flow | ## Getting Started[​](#getting-started "Direct link to Getting Started") ### Personal Guardians[​](#personal-guardians "Direct link to Personal Guardians") Start with [Add a Guardian](https://docs.candide.dev/wallet/plugins/how-to-add-a-guardian.md), then follow the [Recovery Flow Guide](https://docs.candide.dev/wallet/plugins/recovery-flow-guide.md) and [Recovery Alerts Guide](https://docs.candide.dev/wallet/plugins/recovery-alerts-guide.md). ### Email / SMS Recovery[​](#email--sms-recovery "Direct link to Email / SMS Recovery") Start with [Enable Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md), then follow the [Recovery Flow](https://docs.candide.dev/wallet/plugins/recover-account-candide-guardian.md) guide. ## Resources[​](#resources "Direct link to Resources") * **How it works**: [Making Accounts Recoverable](https://docs.candide.dev/blog/making-accounts-recoverable) * **Contracts, audits & formal verification**: [candide-contracts](https://github.com/candidelabs/candide-contracts) * **Deployment addresses**: [Social Recovery Module](https://docs.candide.dev/wallet/technical-reference/deployments.md#social-recovery-module) * **Recovery frontend (SafeCover)**: [recovery.candide.dev](https://recovery.candide.dev) ([source](https://github.com/candidelabs/safecover)) * **Recovery Service API**: [Safe Recovery Services](https://docs.candide.dev/wallet/recovery/overview.md) * **SDK Reference**: [SocialRecoveryModule](https://docs.candide.dev/wallet/plugins/recovery-module-reference.md) --- # The Fastest Path to Production. From your first line of code to your millionth transaction, we give you the infrastructure and support to ship fast and scale confidently. Start building for free. The Starter tier includes the complete AbstractionKit SDK, all account features (Passkeys, Social Recovery, Spend Permissions), and unlimited testnet access, no credit card required. Mainnet access on the Starter plan is trial based: you get 2,500 UserOps across a 90 day window for both the API (Bundler and Paymaster endpoints) and gas policies, limited to 2 mainnet chains. Upgrade when you're ready to go to production. Starter Free Development & Testing *** * ✓2,500 mainnet UserOps — 90 day trial * ✓Unlimited testnet, forever * ✓Full AbstractionKit SDK * ✓All account features (Passkeys, Social Recovery, Spend Permission) * ✓2 mainnet chains * ✓Gas policies limited to 2 mainnets * ✓Community support (Discord) [Start Building Free](https://dashboard.candide.dev) Launch $399/mo Going to Production *** * ✓100,000 UserOps/mo * ✓7 mainnet chains * ✓3 API key environments * ✓10 gas policies, 3 team seats * ✓1M API requests/mo * ✓Slack/Telegram support (24h) [Get Started](https://dashboard.candide.dev/team-settings/manage-plan) Most Popular Grow $899/mo Scaling *** * ✓500,000 UserOps/mo * ✓All mainnet chains * ✓Unlimited gas policies + custom rules * ✓15 team seats, 5 environments * ✓1 Account Plugin included\* * ✓99.0% uptime SLA * ✓Slack/Telegram support (4h) [Get Started](https://dashboard.candide.dev/team-settings/manage-plan) Enterprise $3,000+/mo Dedicated Infrastructure *** * ✓Unlimited UserOps * ✓All chains + custom networks * ✓Dedicated bundler * ✓99.9% uptime SLA (w/ penalties) * ✓Unlimited team seats & environments * ✓Custom plugins * ✓Slack/Telegram (1h) + 24/7 incident response [Contact Us](mailto:team@candidelabs.com) > Billing is based on **API Requests**. A **UserOp (User Operation)** is the standard unit for Smart Account transactions and typically requires ≈ 10 API Requests to complete (estimating gas, sponsoring, signing, submitting, and confirming). *** ## Full Feature Comparison[​](#full-feature-comparison "Direct link to Full Feature Comparison") | Feature | StarterFree | Launch$399/mo | Most PopularGrow$899/mo | Enterprise$3,000+/mo | | -------------------- | ------------------------ | ------------------------- | ----------------------------------------- | -------------------------- | | Usage | | | | | | Mainnet UserOps | 2,500 Ops — 90 Day Trial | 100,000/mo | 500,000/mo | Unlimited | | Testnet Usage | Unlimited | Unlimited | Unlimited | Unlimited | | Mainnet Chains | 2 | 7 | All | All | | Extended Networks | — | Add-on | 1 Included | Custom | | Infrastructure | | | | | | Bundler | Public | Shared | Priority | Isolated (Dedicated) | | Uptime SLA | Best Effort | Best Effort | 99.0% | 99.9% (w/ penalties) | | Throughput | 10 req/sec | 50 req/sec | 200 req/sec | Custom / Uncapped | | Developer Experience | | | | | | API Key Environments | 1 | 3 (Prod + Staging + Test) | 5 | Unlimited | | Gas Policies | 2 (2 mainnets, trial) | 10 | Unlimited + Custom Rules | Unlimited + Custom Rules | | Team Seats | 1 | 3 | 15 | Unlimited | | Included Capacity | 25,000 API Req (total) | 1M API Req/mo | 5M API Req/mo | Unlimited / Dedicated | | Account Plugins | SDK | Add-on | 1 Included (Small tier, $300/mo or under) | Custom | | Gas Sponsorship | | | | | | Included Ops | 1,000 Ops | 5,000 Ops/mo | 25,000 Ops/mo | Unlimited | | Fee After Included | $0.02/op | $0.01/op | $0.005/op | Zero | | Support | | | | | | Channel & Response | Community (Discord) | Slack/Telegram (24h) | Slack/Telegram (4h) | Slack/Telegram (1h) + 24/7 | | Monthly Calls | — | 1 | 2 | Custom | | | | | | | *** ## Gas Sponsorship[​](#gas-sponsorship "Direct link to Gas Sponsorship") Gas is always passed through at cost — we never mark up gas prices. The included ops per month are free. After the included amount, a small service fee applies. | Plan | Included Free | Fee After Included | | ---------------- | ------------- | ------------------ | | Starter | 1,000 Ops | $0.02 per op | | Launch | 5,000 Ops/mo | $0.01 per op | | GrowMost Popular | 25,000 Ops/mo | $0.005 per op | | Enterprise | Unlimited | Zero | *** ## FAQ[​](#faq "Direct link to FAQ") ### Usage[​](#usage "Direct link to Usage") #### When does the 90-day timer start?[​](#when-does-the-90-day-timer-start "Direct link to When does the 90-day timer start?") On your first mainnet UserOperation. Testnet usage doesn't start the clock. #### What counts as a "Mainnet UserOp"?[​](#what-counts-as-a-mainnet-userop "Direct link to What counts as a \"Mainnet UserOp\"?") Any transaction on a production network (Ethereum, Arbitrum, Optimism, Base, Polygon, etc.). Testnet transactions don't count. #### What happens when the Starter trial ends?[​](#what-happens-when-the-starter-trial-ends "Direct link to What happens when the Starter trial ends?") Mainnet access pauses for both the API (Bundler and Paymaster endpoints) and gas policies. They can no longer be used on mainnet until you upgrade. Testnet continues working without any limits. Upgrade to Launch to restore mainnet access immediately. #### Can I switch between tiers?[​](#can-i-switch-between-tiers "Direct link to Can I switch between tiers?") Yes. Upgrade anytime. Downgrade takes effect at the end of your current billing cycle. #### Do I need a credit card for the free tier?[​](#do-i-need-a-credit-card-for-the-free-tier "Direct link to Do I need a credit card for the free tier?") No. Start building immediately. Add payment when you upgrade. #### What if I outgrow Launch but don't need all of Grow?[​](#what-if-i-outgrow-launch-but-dont-need-all-of-grow "Direct link to What if I outgrow Launch but don't need all of Grow?") Reach out for custom quotes or add-ons. *** ### Extended Networks[​](#extended-networks "Direct link to Extended Networks") #### What is an Extended Network?[​](#what-is-an-extended-network "Direct link to What is an Extended Network?") Networks not available by default at Candide that can be supported on request. Any EVM-equivalent chain is supported — including your own L2 (Arbitrum Orbit, OP Stack, Polygon CDK, ZK Stack, etc.). We deploy all smart contracts and bundler infrastructure. *** ### Support[​](#support "Direct link to Support") #### What support do I get with each tier?[​](#what-support-do-i-get-with-each-tier "Direct link to What support do I get with each tier?") * Starter: Community Discord (best-effort) * Launch: Slack/Telegram (24h response) — reactive support. You ask, we answer any question * Grow: Slack/Telegram (4h business hours / 6h nights & weekends) + dedicated account team who proactively monitors your account * Enterprise: Slack/Telegram (1h business hours / 4h nights & weekends) + dedicated account team with 24/7 incident response #### What is Flash Support?[​](#what-is-flash-support "Direct link to What is Flash Support?") Pay-per-incident emergency support ($149 per incident, max 2/year). When production breaks, activate Flash Support and get a 1-hour response (business hours) or 4-hour response (nights & weekends) for that specific incident. Not a subscription — pay only when you need it. Available as an add-on on the Launch plan. *** ## Why Teams Choose Candide[​](#why-teams-choose-candide "Direct link to Why Teams Choose Candide") Best Safe UX SDK Purpose-built for Safe smart accounts, the most battle-tested AA wallet in the ecosystem. Flat Monthly Pricing No usage-based surprises. Unlike other providers, you always know exactly what you'll pay. Reliable Infrastructure 99%+ uptime with fewer errors than competitors. Your users deserve infrastructure that just works. Launch Support We help you go from testnet to production without hiring a blockchain engineer. Standards Based No vendor lock-in. Built on ERC-4337 standards. Switch bundlers anytime — we earn your business daily. Native Plugins Passkeys, social recovery, unified accounts, spend permissions — powerful features built into the SDK. Zero Gas Markup Gas always at cost. Included sponsored ops free every month. No hidden fees, ever. *** ## Ready to get started? No credit card required. Deploy your first transaction in minutes. [Start Building Free](https://dashboard.candide.dev)[Contact Sales](mailto:team@candidelabs.com) --- # Authentication-Based Recovery API (Email / SMS) A Safe guardian service that uses email and phone verification to facilitate account recovery. It can be used as a default recovery method or combined with other guardians (such as hardware wallets or trusted contacts) to create a customized recovery threshold. info To get started, request access [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). ## Authentication[​](#authentication "Direct link to Authentication") All API requests require a Bearer token in the Authorization header: ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/register \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -d '{...}' ``` **/auth** (Registration) * **/auth/register** * **POST**: Submit a registration request * **/auth/submit** * **POST**: Submits the receive OTP * **/auth/registrations** * **GET**: Fetch active registration * **/auth/delete** * **POST**: Delete a registration **/auth/signature** (Recovery) * **/auth/signature/request** * **POST**: Request to recover an account * **/auth/signature/submit** * **POST**: Confirm recovery with OTP challenge ## Auth Registration[​](#auth-registration "Direct link to Auth Registration") ### Email / SMS[​](#email--sms "Direct link to Email / SMS") Submit a registration request with the target smart account to protect using the choice of channel (email or SMS). The user will then receive an OTP code to later submit the challenge in `/auth/submit`. #### `POST /auth/register`[​](#post-authregister "Direct link to post-authregister") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/register \ -H 'Content-Type: application/json' \ -d '{ "account":"0x...", "chainId": 1, "channel":"email", "target":"user@example.com", "message": "siwe(chainId, statement(channel, target))", "signature": "sign(message)" }' ``` ``` { "challengeId":"unique-challenge-id", } ``` | key | type | description | | :---------- | :------- | :---------------------------------------------------------- | | `account` | `string` | The smart account address requesting registration | | `chainId` | `number` | The chain ID where the account resides | | `channel` | `string` | Authentication channel: 'email' or 'sms' | | `target` | `string` | The email or phone number for authentication | | `message` | `string` | SIWE (EIP-4361) message signed by the account | | `signature` | `string` | Signature proving the request is initiated from the account | | key | type | description | | :------------ | :------- | :------------------------------------------------------------- | | `challengeId` | `string` | Unique challenge ID for the registration, used in /auth/submit | * `account`: The smart account address requesting registration. * `chainId`: The chain id in which your account resides (for multi-chain wallets, users will need to register per chain) * `channel`: Either `"email"` or `"sms"` (defines the authentication type). * `target`: The email or phone number for authentication. * `message`: SIWE (EIP-4361) message statement. Statement: ``` I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} via {{channel}} ``` * `signature`: signature proving the request is initiated from the account See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign-In with Ethereum (SIWE). ### Submit Confirmation Using OTP[​](#submit-confirmation-using-otp "Direct link to Submit Confirmation Using OTP") Submit the received OTP code to confirm ownership of the target channel. #### `POST /auth/submit`[​](#post-authsubmit "Direct link to post-authsubmit") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/submit \ -H 'Content-Type: application/json' \ -d '{ "challengeId":"unique-challenge-id", "challenge":"123456" }' ``` ``` { "registrationId": "unique-registration-id", "guardianAddress": "0x...", } ``` | key | type | description | | :------------ | :------- | :--------------------------------------------------- | | `challengeId` | `string` | The unique challenge ID received from /auth/register | | `challenge` | `string` | The OTP code received via email/SMS | | key | type | description | | :---------------- | :------- | :--------------------------------------------------- | | `registrationId` | `string` | Unique registration ID for the authenticated channel | | `guardianAddress` | `string` | The guardian address added to the Safe account | ### Get active registration[​](#get-active-registration "Direct link to Get active registration") Fetch the registration of the protected smart account #### `GET /auth/registrations`[​](#get-authregistrations "Direct link to get-authregistrations") * Example Request * Example Response * Query Parameters * Response Body ``` curl -G "https://yourcompany.recovery.candide.dev/auth/registrations" \ --data-urlencode "account=0x...", --data-urlencode "chainId=0x1", --data-urlencode "message=siwe(chainId, statement)", --data-urlencode "signature=sign(message)" ``` ``` { "registrations": [ { "id": "unique-registration-id", "channel": "email", "target": "user@example.com", } ] } ``` | key | type | description | | :---------- | :------- | :------------------------------------- | | `account` | `string` | The Safe account address | | `chainId` | `number` | The chain ID where the account resides | | `message` | `string` | SIWE message signed by the account | | `signature` | `string` | Signature proving the request | | key | type | description | | :-------------- | :------ | :--------------------------- | | `registrations` | `array` | List of active registrations | See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) ### Delete[​](#delete "Direct link to Delete") Deletes a registration #### `POST /auth/delete`[​](#post-authdelete "Direct link to post-authdelete") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/delete \ -H 'Content-Type: application/json' \ -d '{ "registrationId":"unique-registration-id", "message": "siwe(chainId, statement(registrationId))", "signature": "sign(registrationId, timestamp)" }' ``` ``` { "success": "true" } ``` | key | type | description | | :--------------- | :------- | :--------------------------------- | | `registrationId` | `string` | The registration ID to delete | | `message` | `string` | SIWE message signed by the account | | `signature` | `string` | Signature proving the request | | key | type | description | | :-------- | :-------- | :------------------------------ | | `success` | `boolean` | True if deletion was successful | See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) ## Auth Recovery[​](#auth-recovery "Direct link to Auth Recovery") ### Request to recover an account[​](#request-to-recover-an-account "Direct link to Request to recover an account") Request a signature from the service to recover an account given the new owners and threshold #### `POST /auth/signature/request`[​](#post-authsignaturerequest "Direct link to post-authsignaturerequest") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/signature/request \ -H 'Content-Type: application/json' \ -d '{ "account":"0x...", "newOwners": ["0x...", "0x..."], "newThreshold": 2, "chainId": 1 }' ``` ``` { "requestId":"unique-signature-request-id", "requiredVerifications": 1, "auths": [ { "challengeId": "unique-challenge-id", "channel": "email", "target": "us**@exa****.com" } ] } ``` | key | type | description | | :------------- | :--------- | :---------------------------------------- | | `account` | `string` | The smart account address to be recovered | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `chainId` | `number` | Chain ID for the recovery request | | key | type | description | | :---------------------- | :------- | :---------------------------------------- | | `requestId` | `string` | Unique signature request ID | | `requiredVerifications` | `number` | Minimum number of OTP challenges required | | `auths` | `array` | List of authentication methods to verify | ### Confirm recovery with OTP challenge[​](#confirm-recovery-with-otp-challenge "Direct link to Confirm recovery with OTP challenge") Request to submits the signature with the provided OTP code challenge and id #### `POST /auth/signature/submit`[​](#post-authsignaturesubmit "Direct link to post-authsignaturesubmit") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/auth/signature/submit \ -H 'Content-Type: application/json' \ -d '{ "requestId": "unique-signature-request-id", "challengeId": "unique-challenge-id", "challenge": "123456" }' ``` ``` { "success": true, "signer": "0x...", "signature": "0x..." } ``` | key | type | description | | :------------ | :------- | :----------------------------------------------------- | | `requestId` | `string` | The unique ID from /auth/signature/request | | `challengeId` | `string` | The challenge ID specific to the authentication method | | `challenge` | `string` | The OTP code received via email/SMS | | key | type | description | | :---------- | :-------- | :------------------------------------------------------------------------ | | `success` | `boolean` | True if verification was successful | | `signer` | `string` | Guardian address (available only if sufficient verifications collected) | | `signature` | `string` | Recovery signature (available only if sufficient verifications collected) | ## How to Sign Messages (SIWE EIP-4361)[​](#how-to-sign-messages-siwe-eip-4361 "Direct link to How to Sign Messages (SIWE EIP-4361)") * SIWE * safe-utils * example of what users will see ``` import { SiweMessage } from "siwe"; import { hexlify, randomBytes } from "ethers"; import { personalSign, getMessageHashForSafe } from "./safe-utils" function generateSIWEMessageSignaturePair(safeAccountAddress: string, statement: string, chainId: string): [string, string] { const siweMessage = new SiweMessage({ version: "1", address: ethers.getAddress(accountAddress), domain: "service://safe-recovery-safeAccountAddress", uri: "service://safe-recovery-service", statement, chainId: Number(chainId), nonce: hexlify(randomBytes(24)), }); const message = siweMessage.prepareMessage(); const signature = personalSign(safeAccountAddress, message, BigInt(chainId)); return [message, signature]; } ``` ``` import { hashMessage, Wallet } from "ethers"; export function getMessageHashForSafe(safeAccountAddress: string, message: string, chainId: BigInt) { const SAFE_MSG_TYPEHASH = "0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca"; const DOMAIN_SEPARATOR_TYPEHASH = "0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"; const domainSeparator = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode( ["bytes32", "uint256", "address"], [DOMAIN_SEPARATOR_TYPEHASH, chainId, safeAccountAddress] )); const encodedMessage = ethers.AbiCoder.defaultAbiCoder().encode( ["bytes32", "bytes32"], [SAFE_MSG_TYPEHASH, ethers.keccak256(message)] ); const messageHash = ethers.keccak256(ethers.solidityPacked( ["bytes1", "bytes1", "bytes32", "bytes32",], [Uint8Array.from([0x19]), Uint8Array.from([0x01]), domainSeparator, ethers.keccak256(encodedMessage)] )); return messageHash; } export function personalSign(safeAccountAddress: string, message: string, chainId: BigInt){ const hash = hashMessage(message); const safeMessageHash = await getMessageHashForSafe(safeAccountAddress, message, chainId); const signer = new Wallet(process.env.privateKey) return signer.signingKey.sign(messageHash).serialized; } ``` ``` service://safe-recovery-service wants you to sign in with your Ethereum account: 0x13D6D891307758afc45EE42C90bFE7636C32088b I request to retrieve all Social Recovery Module alert subscriptions linked to my account URI: service://safe-recovery-service Version: 1 Chain ID: 11155420 Nonce: 0x95e25544f0f05b90c12b92d5a0d29666b99c77a47b00e854 Issued At: 2025-03-13T15:47:08.746Z ``` ## Error Handling[​](#error-handling "Direct link to Error Handling") The API uses standard HTTP status codes to indicate the success or failure of a request. Error responses include a JSON object with the following structure: ``` { "error": { "code": 404, "message": "Registration not found" } } ``` ### HTTP Status Codes[​](#http-status-codes "Direct link to HTTP Status Codes") | Code | Description | | ---- | ----------------------------------------------------------- | | 200 | Success | | 400 | Bad Request - Invalid parameters or missing required fields | | 401 | Unauthorized - Invalid or missing Bearer token | | 404 | Not Found - Resource not found | | 429 | Too Many Requests - Rate limit exceeded | | 500 | Internal Server Error - Something went wrong on the server | ### Common Error Messages[​](#common-error-messages "Direct link to Common Error Messages") | Message | Description | | ---------------------- | -------------------------------------------- | | Registration not found | The requested registration ID does not exist | | Invalid signature | The SIWE signature verification failed | | Challenge expired | The OTP challenge has expired | | Invalid challenge | The OTP code provided is incorrect | | Rate limit exceeded | Too many requests, please try again later | --- # An overview of Safe Recovery Service API Secure Safe Accounts with diverse recovery options including trusted contacts, email/SMS verification, Passkeys, secondary devices, and more. The Safe Recovery service consists of two main components: [Recovery UX](#recovery-ux-api) and [Email/SMS Recovery](#emailsms-recovery-api). ## Who is this for[​](#who-is-this-for "Direct link to Who is this for") Wallets implementing the Safe [Social Recovery Module](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md) that want to provide a seamless and secure recovery experience for their end users. ## Recovery UX API[​](#recovery-ux-api "Direct link to Recovery UX API") ### Automatic Execution[​](#automatic-execution "Direct link to Automatic Execution") The service provides automatic execution options to minimize UX friction and enhance privacy for recovery contacts and guardians. Configure the service to automatically execute: 1. The recovery confirmation transaction once the signature threshold is met, eliminating the need for guardians to pay gas fees. 2. The recovery finalization transaction after the grace period expires, removing the need for recovery contacts to return and complete the process. ### Signature Aggregation and Storage[​](#signature-aggregation-and-storage "Direct link to Signature Aggregation and Storage") Guardian signatures can be submitted to the service for off-chain collection. Once all required signatures are collected, the service automatically executes the recovery confirmation and finalization. ### Gas Sponsorship Relayer[​](#gas-sponsorship-relayer "Direct link to Gas Sponsorship Relayer") The service includes a gas sponsorship relayer that covers gas costs for both confirmation and finalization transactions. ### Social Engineering Protection[​](#social-engineering-protection "Direct link to Social Engineering Protection") The API provides an emoji-based communication system that enables guardians to verify and approve legitimate recovery requests, effectively preventing social engineering attacks by malicious actors attempting to manipulate the recovery process. ### Alerts and Notifications[​](#alerts-and-notifications "Direct link to Alerts and Notifications") Account owners can subscribe to email or SMS notifications when a recovery request is initiated, whether onchain or through the service, ensuring they remain informed throughout the recovery process. The service monitors transactions sent through the Safe Recovery Module, using events and tracing to index transactions and deliver timely alerts. ## Email/SMS Recovery API[​](#emailsms-recovery-api "Direct link to Email/SMS Recovery API") A secure and user-friendly solution that uses email and phone verification for account recovery. Use it as a standalone recovery method or combine it with other guardians (hardware wallets, trusted contacts) to create a customized recovery threshold. Features include: ### Email Recovery[​](#email-recovery "Direct link to Email Recovery") Supports SMTP and OAuth2-based protocols. To verify email ownership, a confirmation code is sent to the user's email address. The user must enter this code to enable the guardian service. ### SMS Recovery[​](#sms-recovery "Direct link to SMS Recovery") Supports SMS OTP verification. To verify phone number ownership, a confirmation code is sent to the user. The user must enter this code to enable the guardian service. ### Multi-Factor Authentication[​](#multi-factor-authentication "Direct link to Multi-Factor Authentication") Supports MFA across multiple channels, including combinations of email and SMS. ### Custom Channels[​](#custom-channels "Direct link to Custom Channels") Supports additional channels including WhatsApp and Telegram. Contact us to request support for your preferred channel. ## How it works[​](#how-it-works "Direct link to How it works") ## Reference Links[​](#reference-links "Direct link to Reference Links") * [Recovery explainer](https://docs.candide.dev/blog/making-accounts-recoverable) * [Module SDK](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md) * [Module contracts and audits](https://github.com/candidelabs/candide-contracts) * [Source code](https://github.com/candidelabs/safe-recovery-service) * [Recovery Service SDK](https://github.com/candidelabs/safe-recovery-service-sdk) ## Recovery Request States[​](#recovery-request-states "Direct link to Recovery Request States") A recovery request goes through the following states: | State | Description | | -------------------------- | ------------------------------------------------------------ | | `PENDING` | Recovery request created, waiting for guardian signatures | | `EXECUTION-IN-PROGRESS` | All signatures collected, executing on-chain | | `EXECUTED` | Recovery confirmed, new owners set. Waiting for grace period | | `FINALIZATION-IN-PROGRESS` | Grace period ended, finalizing on-chain | | `FINALIZED` | Recovery complete, new owners take control | --- # Safe Recovery UX API ## Benefits[​](#benefits "Direct link to Benefits") | Feature | Description | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Alerts and Notifications | Account owners subscribe to receive notifications via email or SMS when a recovery request is initiated on-chain. | | Guardians Sign Once | Off-chain signature collection eliminating the need for guardians to share links with one another. | | Privacy Guaranteed | Guardians sign only off-chain and do not need to maintain a balance in their accounts, allowing them to preserve their pseudonymity with fresh accounts. | | Social Engineering Protection | A communication system using emojis that allows guardians to verify and approve legitimate recovery requests from their rightful owners. | | Auto Finalization After Grace Period | A built-in relayer automatically submits signed transactions on behalf of guardians for confirmation and finalization once the grace period has elapsed. | info To get started, request access [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). ## Authentication[​](#authentication "Direct link to Authentication") All API requests require a Bearer token in the Authorization header: ``` curl -X POST \ https://yourcompany.recovery.candide.dev/recoveries/create \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -d '{...}' ``` **/recoveries** * **/recoveries/create** * **POST**: Creates a new recovery request * **/recoveries/fetchByAddress** * **GET**: Fetches recovery requests * **/recoveries/listByAddress** * **GET**: Lists all recovery requests with filtering and pagination * **/recoveries/fetchById** * **GET**: Fetch a recovery request by ID * **/recoveries/sign** * **POST**: Collects a guardian signature * **/recoveries/execute** * **POST**: Execute a recovery request by ID * **/recoveries/finalize** * **POST**: Finalize a recovery request by ID **/alerts** * **/alerts/subscribe** * **POST**: Creates an inactive alerts subscription for an account * **/alerts/activate** * **POST**: Activate subscription to recovery requests * **/alerts/subscriptions** * **GET**: Fetches active alerts subscriptions for an account. * **/alerts/unsubscribe** * **POST**: Unsubscribes from an active alerts subscription. ## Recoveries[​](#recoveries "Direct link to Recoveries") ### Create Recovery Request[​](#create-recovery-request "Direct link to Create Recovery Request") Creates a new recovery request by a guardian with a lost signer of a Safe account. Can only be initiated by guardians of the account. #### `POST /recoveries/create`[​](#post-recoveriescreate "Direct link to post-recoveriescreate") * Example Request * Example Response * Request Body * Response Body ``` curl -X POST \ https://yourcompany.recovery.candide.dev/create \ -H 'Content-Type: application/json' \ -d '{ "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "newOwners": ["0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D"], "newThreshold": 2, "chainId": 1, "signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", "signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" }' ``` ``` { "id": "123456789", "emoji": "🤖😅🥵👻🖖", "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "chainId": 1, "newOwners": [ "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D" ], "newThreshold": 2, "nonce": "1234567890", "signatures": [], "executeData": { "sponsored": true, "transactionHash": "" }, "finalizeData": { "sponsored": true, "transactionHash": "" }, "status": "PENDING", "discoverable": true, "createdAt": "2023-04-18T12:34:56.789Z", "updatedAt": "2023-04-18T12:34:56.789Z" } ``` | key | type | description | | :------------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | `account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `newOwners` | `string[]` | The new owners to the Safe account. Must be a valid Ethereum addresses | | `newThreshold` | `number` | The new threshold to the Safe account | | `chainId` | `number` | The chainId of the network where the Safe account resides | | `signer` | `string` | The guardian public address initiating the recovery request | | `signature` | `string` | A signature from the guardian for a message containing the address of the safe, new owners of the Safe, chainId, module address, and a nonce | | key | type | description | | :----------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------- | | `id` | `string` | The ID associated with the Recovery Request | | `emoji` | `string` | The emojis associated with the recovery request | | `account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `chainId` | `number` | The chainId of the network where the Safe account resides | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `json` | The signatures for the recovery request | | `executeData` | `object` | An object field representing the finalization recovery transaction | | `executeData.sponsored` | `boolean` | If the recovery execution tx is gas-sponsored or not | | `executeData.transactionHash` | `string` | The transaction hash of the recovery execution | | `finalizeData` | `object` | An object field representing the finalization recovery transaction | | `finalizeData.sponsored` | `boolean` | If the recovery finalization tx after the grace period ends is gas-sponsored or not | | `finalizeData.transactionHash` | `string` | The transaction hash of the finalization execution | | `status` | `string` | The status of the recovery request: PENDING \| EXECUTED \| FINALIZED \| FINALIZATION-IN-PROGRESS \| FAILED | | `discoverable` | `boolean` | Whether the recovery request is discoverable | | `createdAt` | `datetime` | The date the recovery request was created | | `updatedAt` | `datetime` | The date and time of the recovery request that was created | ### Fetch recovery by address[​](#fetch-recovery-by-address "Direct link to Fetch recovery by address") Fetches a recovery request by Safe account address and nonce. Requires `account`, `chainId`, and `nonce` parameters. #### `GET /recoveries/fetchByAddress`[​](#get-recoveriesfetchbyaddress "Direct link to get-recoveriesfetchbyaddress") * Example Request * Example Response * Request Body * Response ``` curl -G "https://yourcompany.recovery.candide.dev/fetchByAddress" \ --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ --data-urlencode "chainId=11155111" \ --data-urlencode "nonce=0x1" ``` ``` [ { "id": "123456789", "emoji": "🤖😅🥵👻🖖", "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "chainId": 1, "newOwners": [ "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D" ], "newThreshold": 2, "nonce": "1234567890", "signatures": [], "executeData": { "sponsored": true, "transactionHash": "" }, "finalizeData": { "sponsored": true, "transactionHash": "" }, "status": "PENDING", "discoverable": true, "createdAt": "2023-04-18T12:34:56.789Z", "updatedAt": "2023-04-18T12:34:56.789Z" }, { "id": "72682373", "emoji": "💳📿🪣📥📹", "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "chainId": 1, "newOwners": [ "0x73f7b1184B5cD361cC0f7654998953E2a251dd58", "0x7Cb027917b27BCb5963C548657a008BF45b25BDc" ], "newThreshold": 2, "nonce": "1234567890", "signatures": [], "executeData": { "sponsored": true, "transactionHash": "" }, "finalizeData": { "sponsored": true, "transactionHash": "" }, "status": "PENDING", "discoverable": true, "createdAt": "2023-04-18T12:34:56.789Z", "updatedAt": "2023-04-18T12:34:56.789Z" } ] ``` | key | type | description | | :-------- | :------- | :----------------------------------------------------------------------------------------- | | `account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `chainId` | `number` | The chainId of the network where the Safe account resides | | `nonce` | `number` | Recovery module contract nonce | | key | type | description | | :----------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------- | | `RecoveryRequest[]` | `object` | A list of ongoing Recovery Requests for a Safe account address | | `RecoveryRequest[].id` | `string` | The ID associated with the Recovery Request | | `RecoveryRequest[].emoji` | `string` | The emojis associated with the recovery request | | `RecoveryRequest[].account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `RecoveryRequest[].chainId` | `number` | The chainId of the network where the Safe account resides | | `RecoveryRequest[].newOwners` | `string[]` | The new owners for the Safe account | | `RecoveryRequest[].newThreshold` | `number` | The new threshold for the Safe account | | `RecoveryRequest[].nonce` | `bigint` | Recovery module contract nonce | | `RecoveryRequest[].signatures` | `json` | The signatures for the recovery request | | `RecoveryRequest[].executeData` | `object` | An object field representing the finalization recovery transaction | | `RecoveryRequest[].executeData.sponsored` | `boolean` | If the recovery execution tx is gas-sponsored or not | | `RecoveryRequest[].executeData.transactionHash` | `string` | The transaction hash of the recovery execution | | `RecoveryRequest[].finalizeData` | `object` | An object field representing the finalization recovery transaction | | `RecoveryRequest[].finalizeData.sponsored` | `boolean` | If the recovery finalization tx after the grace period ends is gas-sponsored or not | | `RecoveryRequest[].finalizeData.transactionHash` | `string` | The transaction hash of the finalization execution | | `RecoveryRequest[].status` | `string` | The status of the recovery request: PENDING \| EXECUTED \| FINALIZED \| FINALIZATION-IN-PROGRESS \| FAILED | | `RecoveryRequest[].discoverable` | `boolean` | Whether the recovery request is discoverable | | `RecoveryRequest[].createdAt` | `datetime` | The date the recovery request was created | | `RecoveryRequest[].updatedAt` | `datetime` | The date and time of the recovery request that was created | ### List all recoveries by address[​](#list-all-recoveries-by-address "Direct link to List all recoveries by address") Lists all recovery requests for a Safe account with advanced filtering, pagination, and ordering. The `executed` and `finalized` filters are cross-checked with indexed on-chain data for accuracy. #### `GET /recoveries/listByAddress`[​](#get-recoverieslistbyaddress "Direct link to get-recoverieslistbyaddress") * Example Request * Example Response * Query Parameters * Response ``` curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \ --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ --data-urlencode "chainId=11155111" \ ``` ``` { "recoveries": [ { "id": "123456789", "emoji": "🤖😅🥵👻🖖", "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "chainId": 1, "newOwners": [ "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D" ], "newThreshold": 2, "nonce": "1234567890", "signatures": [], "executeData": { "sponsored": true, "transactionHash": "" }, "finalizeData": { "sponsored": true, "transactionHash": "" }, "status": "PENDING", "discoverable": true, "createdAt": "2023-04-18T12:34:56.789Z", "updatedAt": "2023-04-18T12:34:56.789Z" } ], "total": 1 } ``` | key | type | description | | :---------- | :-------- | :--------------------------------------------------------------------------------------------------------- | | `account` | `string` | The Safe account address. Must be a valid Ethereum address | | `chainId` | `number` | The chainId of the network where the Safe account resides | | `status` | `string` | (Optional) Filter by status: PENDING, EXECUTED, FINALIZED, EXECUTION-IN-PROGRESS, FINALIZATION-IN-PROGRESS | | `executed` | `boolean` | (Optional) Filter by executed status. Cross-checked with indexed data | | `finalized` | `boolean` | (Optional) Filter by finalized status. Cross-checked with indexed data | | `orderBy` | `string` | (Optional) Field to order by (e.g., 'createdAt', 'nonce') | | `order` | `string` | (Optional) Order direction: 'asc' or 'desc'. Defaults to 'desc' | | key | type | description | | :---------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------- | | `recoveries` | `object` | A list of Recovery Requests matching the filters | | `recoveries.id` | `string` | The ID associated with the Recovery Request | | `recoveries.emoji` | `string` | The emojis associated with the recovery request | | `recoveries.account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `recoveries.chainId` | `number` | The chainId of the network where the Safe account resides | | `recoveries.newOwners` | `string[]` | The new owners for the Safe account | | `recoveries.newThreshold` | `number` | The new threshold for the Safe account | | `recoveries.nonce` | `bigint` | Recovery module contract nonce | | `recoveries.signatures` | `json` | The signatures for the recovery request | | `recoveries.executeData` | `object` | An object field representing the finalization recovery transaction | | `recoveries.executeData.sponsored` | `boolean` | If the recovery execution tx is gas-sponsored or not | | `recoveries.executeData.transactionHash` | `string` | The transaction hash of the recovery execution | | `recoveries.finalizeData` | `object` | An object field representing the finalization recovery transaction | | `recoveries.finalizeData.sponsored` | `boolean` | If the recovery finalization tx after the grace period ends is gas-sponsored or not | | `recoveries.finalizeData.transactionHash` | `string` | The transaction hash of the finalization execution | | `recoveries.status` | `string` | The status of the recovery request: PENDING \| EXECUTED \| FINALIZED \| FINALIZATION-IN-PROGRESS \| FAILED | | `recoveries.discoverable` | `boolean` | Whether the recovery request is discoverable | | `recoveries.createdAt` | `datetime` | The date the recovery request was created | | `recoveries.updatedAt` | `datetime` | The date and time of the recovery request that was created | | `total` | `number` | Total number of recovery requests matching the filters | When to use fetchByAddress vs listByAddress * **`/recoveries/fetchByAddress`**: Use when you know the exact nonce. Ideal for checking a specific pending request or looking up the current recovery state. * **`/recoveries/listByAddress`**: Use when you need to query multiple requests with filtering, pagination, or sorting. Better for dashboard UIs showing recovery history. ### Fetch recovery by ID[​](#fetch-recovery-by-id "Direct link to Fetch recovery by ID") Fetch a recovery request by ID #### `GET /recoveries/fetchById`[​](#get-recoveriesfetchbyid "Direct link to get-recoveriesfetchbyid") * Example Request * Example Response * Request Body * Response Body ``` curl -G "https://yourcompany.recovery.candide.dev/fetchById" \ --data-urlencode "id=0x123" ``` ``` { "id": "123456789", "emoji": "🤖😅🥵👻🖖", "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", "chainId": 1, "newOwners": [ "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D" ], "newThreshold": 2, "nonce": "1234567890", "signatures": [], "executeData": { "sponsored": true, "transactionHash": "", }, "finalizeData": { "sponsored": true, "transactionHash": "", }, "status": "PENDING", "discoverable": true, "createdAt": "2023-04-18T12:34:56.789Z", "updatedAt": "2023-04-18T12:34:56.789Z" } ``` | key | type | description | | :--- | :------- | :------------------ | | `id` | `string` | Recovery request ID | | key | type | description | | :----------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------- | | `id` | `string` | The ID associated with the Recovery Request | | `emoji` | `string` | The emojis associated with the recovery request | | `account` | `string` | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | | `chainId` | `number` | The chainId of the network where the Safe account resides | | `newOwners` | `string[]` | The new owners for the Safe account | | `newThreshold` | `number` | The new threshold for the Safe account | | `nonce` | `bigint` | Recovery module contract nonce | | `signatures` | `json` | The signatures for the recovery request | | `executeData` | `object` | An object field representing the finalization recovery transaction | | `executeData.sponsored` | `boolean` | If the recovery execution tx is gas-sponsored or not | | `executeData.transactionHash` | `string` | The transaction hash of the recovery execution | | `finalizeData` | `object` | An object field representing the finalization recovery transaction | | `finalizeData.sponsored` | `boolean` | If the recovery finalization tx after the grace period ends is gas-sponsored or not | | `finalizeData.transactionHash` | `string` | The transaction hash of the finalization execution | | `status` | `string` | The status of the recovery request: PENDING \| EXECUTED \| FINALIZED \| FINALIZATION-IN-PROGRESS \| FAILED | | `discoverable` | `boolean` | Whether the recovery request is discoverable | | `createdAt` | `datetime` | The date the recovery request was created | | `updatedAt` | `datetime` | The date and time of the recovery request that was created | ### Collect a guardian signature[​](#collect-a-guardian-signature "Direct link to Collect a guardian signature") Collects a guardian signature to store for later confirmation and finalization #### `POST /recoveries/sign`[​](#post-recoveriessign "Direct link to post-recoveriessign") * Example Request * Example Response * Request Body * Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/sign \ -H 'Content-Type: application/json' \ -d '{ "id": "123456789", "signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", "signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" }' ``` ``` { "success": "true" } ``` | key | type | description | | :---------- | :------- | :-------------------------------------------------------------------------- | | `id` | `string` | Recovery request ID | | `signer` | `string` | The guardian public address | | `signature` | `string` | The signature for the message hash of the guardian confirmation of recovery | | key | type | description | | :------- | :----- | :------------------------ | | `status` | `true` | true = signature is valid | ### Execute a recovery by ID[​](#execute-a-recovery-by-id "Direct link to Execute a recovery by ID") #### `POST /recoveries/execute`[​](#post-recoveriesexecute "Direct link to post-recoveriesexecute") Execute a recovery request by ID * Example Request * Example Response * Request Body * Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/execute \ -H 'Content-Type: application/json' \ -d '{ "id": 123456789 }' ``` ``` { "success": "true" } ``` | key | type | description | | :--- | :------- | :------------------ | | `id` | `string` | Recovery request ID | | key | type | description | | :-------- | :----- | :--------------------------------------------- | | `success` | `true` | Return true once finilized. Else returns error | ### Finalize recovery by ID[​](#finalize-recovery-by-id "Direct link to Finalize recovery by ID") #### `POST /recoveries/finalize`[​](#post-recoveriesfinalize "Direct link to post-recoveriesfinalize") Finalize a recovery request by ID * Example Request * Example Response * Request Body * Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/finalize \ -H 'Content-Type: application/json' \ -d '{ "id": 123456789 }' ``` ``` { "success": "true" } ``` | key | type | description | | :--- | :------- | :------------------ | | `id` | `string` | Recovery request ID | | key | type | description | | :-------- | :----- | :--------------------------------------------- | | `success` | `true` | Return true once finilized. Else returns error | ## Alerts[​](#alerts "Direct link to Alerts") ### Alerts: Subscribe to recovery requests[​](#alerts-subscribe-to-recovery-requests "Direct link to Alerts: Subscribe to recovery requests") #### `POST /alerts/subscribe`[​](#post-alertssubscribe "Direct link to post-alertssubscribe") Creates an inactive alerts subscription for an account, both onchain and offchain. It then needs to be activated through challenge submission using `POST /alerts/activate`. * Example Request * Example Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/subscribe \ -H 'Content-Type: application/json' \ -d '{ "account": "0x...", "chainId": 1, "channel": "email", "target": "user@example.com", "message": "siwe(chainId, statement(channel, target))", "signature": "sign(message)" }' ``` ``` { "subscriptionId": "unique-subscription-id", // please note that this alerts subscription needs to be activated using the next endpoint } ``` * `account`: The smart account address requesting registration. * `chainId`: The chain id in which the account resides (this is used to verify the signature field only, the alert will trigger for any action for this account across any chain) * `channel`: Either `"email"` or `"sms"` (defines the delivery channel). * `target`: The email or phone number for authentication. * `message`: SIWE (EIP-4361) message statement. Statement: ``` I agree to receive Social Recovery Module alert notifications for my account address on all supported chains sent to {{target}} ``` * `signature`: signature proving the request is initiated from the account See [example guide](https://docs.candide.dev/wallet/recovery/auth-api.md#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) ### Alerts: Activate subscription to recovery requests[​](#alerts-activate-subscription-to-recovery-requests "Direct link to Alerts: Activate subscription to recovery requests") #### `POST /alerts/activate`[​](#post-alertsactivate "Direct link to post-alertsactivate") Verifies submitted challenge and activates alerts subscription. * Example Request * Example Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/activate \ -H 'Content-Type: application/json' \ -d '{ "subscriptionId": "unique-subscription-id", "challenge": "123456" }' ``` ``` { "success": true, } ``` * `subscriptionId`: The unique ID received in the subscription response. * `challenge`: The code received via email/SMS. ### Alerts: Get active subscription[​](#alerts-get-active-subscription "Direct link to Alerts: Get active subscription") Fetches active alerts subscriptions for an account. #### `GET /alerts/subscriptions`[​](#get-alertssubscriptions "Direct link to get-alertssubscriptions") * Example Request * Example Response ``` curl -G "https://yourcompany.recovery.candide.dev/alerts/subscriptions" \ --data-urlencode "account=0x...", --data-urlencode "chainId=0x1", --data-urlencode "message=siwe(chainId, statement)", --data-urlencode "signature=sign(message)" ``` ``` { "subscriptions": [ { "id": "unique-subscription-id", "channel": "email", "target": "user@example.com" } ] } ``` * `account`: The smart account address. * `chainId`: The chain id in which the account resides (this is used to verify the signature field only, the alerts are global for this account accross all supported chains that have alerts enabled) * `message`: SIWE (EIP-4361) message statement. Statement: ``` I request to retrieve all Social Recovery Module alert subscriptions linked to my account ``` * `signature`: signature proving the request is initiated from the account See [example guide](https://docs.candide.dev/wallet/recovery/auth-api.md#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) ### Alerts: Unsubscribe[​](#alerts-unsubscribe "Direct link to Alerts: Unsubscribe") Unsubscribes from an active alerts subscription. #### `POST /alerts/unsubscribe`[​](#post-alertsunsubscribe "Direct link to post-alertsunsubscribe") * Example Request * Example Response ``` curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/unsubscribe \ -H 'Content-Type: application/json' \ -d '{ "subscriptionId": "unique-subscription-id" }' ``` ``` { "success": true } ``` * `subscriptionId`: The unique ID received in the subscription response. ## Error Handling[​](#error-handling "Direct link to Error Handling") The API uses standard HTTP status codes to indicate the success or failure of a request. Error responses include a JSON object with the following structure: ``` { "error": { "code": 404, "message": "Recovery request not found" } } ``` ### HTTP Status Codes[​](#http-status-codes "Direct link to HTTP Status Codes") | Code | Description | | ---- | ----------------------------------------------------------- | | 200 | Success | | 400 | Bad Request - Invalid parameters or missing required fields | | 401 | Unauthorized - Invalid or missing Bearer token | | 404 | Not Found - Resource not found | | 429 | Too Many Requests - Rate limit exceeded | | 500 | Internal Server Error - Something went wrong on the server | ### Common Error Messages[​](#common-error-messages "Direct link to Common Error Messages") | Message | Description | | -------------------------- | ----------------------------------------- | | Recovery request not found | The requested recovery ID does not exist | | Invalid signature | The signature verification failed | | Guardian not found | The signer is not a registered guardian | | Insufficient signatures | Not enough guardian signatures collected | | Rate limit exceeded | Too many requests, please try again later | --- # Solana Paymaster RPC Methods JSON-RPC API reference for Candide's **Solana Paymaster**, a hosted [Kora](https://solana.com/docs/tools/kora) endpoint. The paymaster acts as the transaction fee payer, so your users can transact from accounts that hold zero SOL and pay the network fee in USDT instead. Kora is the Solana Foundation's open fee-payer standard: any Kora client, such as [`@solana/kora`](https://www.npmjs.com/package/@solana/kora), works against this endpoint unchanged. Visit the [dashboard](https://dashboard.candide.dev) to get an API key. The endpoint URL is: ``` https://api.candide.dev/api/v3/solana/YOUR_API_KEY ``` You can also keep the key out of the URL by sending it in an `x-api-key` header against `https://api.candide.dev/api/v3/solana`. To check connectivity and discover the fee payer address in one call: ``` curl https://api.candide.dev/api/v3/solana/YOUR_API_KEY \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "getPayerSigner", "params": []}' ``` Every example request and response on this page was captured against the live endpoint. Long base64 strings are shortened with `...` for readability; everything else is verbatim, including the [mainnet transaction](https://explorer.solana.com/tx/1QdRg6ThqLHa776Dq7FHjjzJTS3ZG8Me6FPgqTAa7mND1Wfc86Qk9B39i2p1dy5tvG9AuVHYLbHNTqdVUsJk1UZ) in `signAndSendTransaction`. ## Conventions[​](#conventions "Direct link to Conventions") These differ from Candide's [EVM Paymaster API](https://docs.candide.dev/wallet/paymaster/rpc-methods.md), so read them before reusing patterns from there: * Transactions are the **base64-encoded serialized wire format** (what `transaction.serialize()` returns in `@solana/web3.js`), not hex. * Amounts and fees are **plain decimal integers in base units** (USDT has 6 decimals), not hex strings. * Methods that take arguments use a **named params object** (`"params": {...}`); methods without arguments take `"params": []`. * Method names are camelCase; **response fields are snake\_case** (`signer_address`, `fee_in_token`). ## Transaction Flow[​](#transaction-flow "Direct link to Transaction Flow") A gasless transaction goes through the API like this: 1. Call [`getPayerSigner`](#getpayersigner) and set `signer_address` as the transaction's fee payer. 2. Build the transaction, or have the paymaster build a token transfer for you with [`transferTransaction`](#transfertransaction). 3. Call [`estimateTransactionFee`](#estimatetransactionfee) to get the fee in the fee token (`fee_in_token`). 4. Add an SPL transfer of `fee_in_token` base units from the user to `payment_address` as the last instruction. Adding it changes the transaction, so re-estimate and adjust until the quoted fee stops changing (it converges in one step). 5. Have the user sign, then call [`signAndSendTransaction`](#signandsendtransaction) to co-sign and broadcast, or [`signTransaction`](#signtransaction) if you want to broadcast yourself. The [Pay Gas in USDT on Solana guide](https://docs.candide.dev/wallet/guides/pay-gas-in-usdt-solana.md) walks through the same flow with Tether's WDK handling steps 2 to 5 behind one `transfer()` call. getPaymentInstruction is not available Newer Kora versions describe a `getPaymentInstruction` method that builds the fee payment instruction of step 4 for you. It is not enabled on this endpoint and returns error `-32600`, `"Unsupported method - getPaymentInstruction not supported"`. Build the payment instruction as a plain SPL transfer instead. ## Supported Tokens[​](#supported-tokens "Direct link to Supported Tokens") | Token | Mint Address | Decimals | Network | Availability | | ----- | ---------------------------------------------- | -------- | -------------- | ------------ | | USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | 6 | Solana Mainnet | Default | | USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | 6 | Solana Mainnet | On request | USDT is enabled on every endpoint. USDC is enabled on request, and any other SPL token can be evaluated on demand: [contact us](https://www.candide.dev/contact) to request it for your API key. Query [`getSupportedTokens`](#getsupportedtokens) for the live list enabled on your endpoint. ## Sponsorship Modes[​](#sponsorship-modes "Direct link to Sponsorship Modes") * **Token gas payments** (default): the user pays the network fee in a supported SPL token inside the same transaction, and the paymaster fronts the SOL. Every transaction must include the fee payment; this is the mode this API implements. It is the Solana counterpart of [ERC-20 gas payments](https://docs.candide.dev/wallet/paymaster/rpc-methods.md) on Candide's EVM Paymaster. * **Full gas sponsorship** (on request): the app covers the fee so the user pays nothing, like [InstaGas gas policies](https://docs.candide.dev/instagas/overview.md) on EVM. [Contact us](https://www.candide.dev/contact) to enable it for your API key. ## Current Policy[​](#current-policy "Direct link to Current Policy") The endpoint validates every transaction against the policy in [`getConfig`](#getconfig). As of this writing, the rules that most often reject a transaction are: * **Only the tokens enabled for your endpoint are accepted.** USDT is enabled by default; [USDC and other tokens are enabled on request](#supported-tokens). A mint that is not enabled fails with `"Token ... is not supported"`, both as `fee_token` and as a transferred token. * **The fee payment must be included.** `signTransaction` and `signAndSendTransaction` reject transactions that do not pay at least the quoted fee to the payment address, with `"Insufficient token payment. Required ... lamports"`. * **Only five programs may be invoked**: System, SPL Token, Associated Token Account, Compute Budget, and Address Lookup Table. Token-2022 is not among them, so Token-2022 mints are rejected. * **The fee payer spends at most 9,000,000 lamports per transaction** (fees plus rent), transactions are capped at 10 signatures, and durable nonce transactions are not accepted. ## Methods[​](#methods "Direct link to Methods") ### getPayerSigner[​](#getpayersigner "Direct link to getPayerSigner") Returns the paymaster's fee payer address and the address that fee payments must be sent to. Set `signer_address` as the transaction fee payer; send the token fee to `payment_address`. #### Invocation[​](#invocation "Direct link to Invocation") ``` { "method": "getPayerSigner", "params": [] } ``` #### Return[​](#return "Direct link to Return") ``` { "result": { signer_address: string, payment_address: string } } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "getPayerSigner", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "signer_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz", "payment_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz" } } ``` | key | type | description | | :---------------- | :------- | :-------------------------------------------------------------------------------------------------------------- | | `signer_address` | `string` | The paymaster's fee payer address. Set this as the transaction fee payer | | `payment_address` | `string` | Address that receives fee payments. The fee is paid to this address's associated token account of the fee token | ### getSupportedTokens[​](#getsupportedtokens "Direct link to getSupportedTokens") Returns the mint addresses of the SPL tokens accepted for fee payment. #### Invocation[​](#invocation-1 "Direct link to Invocation") ``` { "method": "getSupportedTokens", "params": [] } ``` #### Return[​](#return-1 "Direct link to Return") ``` { "result": { tokens: string[] } } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "getSupportedTokens", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"] } } ``` | key | type | description | | :------- | :--------- | :-------------------------------------------------------- | | `tokens` | `string[]` | Mint addresses of the SPL tokens accepted for fee payment | ### estimateTransactionFee[​](#estimatetransactionfee "Direct link to estimateTransactionFee") Prices a transaction. Returns the total fee in lamports and in the requested fee token, including rent for any accounts the transaction creates and the paymaster margin. The transaction must already name the paymaster's fee payer; it does not need to be signed. #### Invocation[​](#invocation-2 "Direct link to Invocation") ``` { "method": "estimateTransactionFee", "params": { transaction: string, fee_token: string } } ``` #### Return[​](#return-2 "Direct link to Return") ``` { "result": { fee_in_lamports: number, fee_in_token: number, signer_pubkey: string, payment_address: string } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "estimateTransactionFee", "params": { "transaction": "AmkNdD6EK2jjAaPzHhOfmyTqxYGGM0cemhhypHFRe8Wy...", "fee_token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" } } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "fee_in_lamports": 10553, "fee_in_token": 818, "signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz", "payment_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz" } } ``` `fee_in_token` is in base units of `fee_token`: 818 is 0.000818 USDT. | key | type | description | | :------------ | :------- | :---------------------------------------------------------------------------------------------------------- | | `transaction` | `string` | Base64-encoded serialized transaction, with the paymaster's signer address set as the fee payer | | `fee_token` | `string` | Mint address of the SPL token to price the fee in. Must be one of the tokens returned by getSupportedTokens | | key | type | description | | :---------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | `fee_in_lamports` | `number` | Total fee in lamports, including the network fee, any rent for accounts the transaction creates, and the paymaster margin | | `fee_in_token` | `number` | The same fee denominated in base units of fee\_token (USDT has 6 decimals). This is the amount the transaction must transfer to payment\_address | | `signer_pubkey` | `string` | The paymaster's fee payer address | | `payment_address` | `string` | Address the fee payment must be sent to | ### transferTransaction[​](#transfertransaction "Direct link to transferTransaction") Builds an unsigned SPL token transfer with the paymaster set as fee payer. Use it when you do not want to construct the transaction yourself. The returned transaction does **not** include the fee payment: estimate it, append the payment transfer, and re-sign before submitting. `source` and `destination` are wallet (owner) addresses; the paymaster resolves the associated token accounts. If the destination's token account does not exist, the transaction includes its creation and the rent shows up in the fee quote. #### Invocation[​](#invocation-3 "Direct link to Invocation") ``` { "method": "transferTransaction", "params": { amount: number, token: string, source: string, destination: string } } ``` #### Return[​](#return-3 "Direct link to Return") ``` { "result": { transaction: string, message: string, blockhash: string, signer_pubkey: string } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "transferTransaction", "params": { "amount": 100000, "token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", "source": "A3scQhn4rbkybHyCd1C5rcAQ3ZKHTLwsCrXw7fTSo2zN", "destination": "A3scQhn4rbkybHyCd1C5rcAQ3ZKHTLwsCrXw7fTSo2zN" } } ``` `amount` is in base units: 100000 is 0.1 USDT. ``` { "jsonrpc": "2.0", "id": 1, "result": { "transaction": "AmkNdD6EK2jjAaPzHhOfmyTqxYGGM0cemhhypHFRe8Wy...", "message": "AgECBbHnR3w2nftr0eljJIxb7sUoC7kqYxLzxCJsijx6...", "blockhash": "FFkAKnzBcUvKv3erAh3KoGsSMv4zmJSip6e1fduDWb3B", "signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz" } } ``` | key | type | description | | :------------ | :------- | :--------------------------------------------------------------------- | | `amount` | `number` | Transfer amount in base units of the token (USDT has 6 decimals) | | `token` | `string` | Mint address of the SPL token to transfer | | `source` | `string` | Wallet address of the sender (owner address, not the token account) | | `destination` | `string` | Wallet address of the recipient (owner address, not the token account) | | key | type | description | | :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------- | | `transaction` | `string` | Base64-encoded unsigned transaction with the paymaster set as fee payer. It does not include the fee payment instruction | | `message` | `string` | Base64-encoded transaction message | | `blockhash` | `string` | The recent blockhash the transaction was built with | | `signer_pubkey` | `string` | The paymaster's fee payer address | ### signTransaction[​](#signtransaction "Direct link to signTransaction") Validates a transaction against the policy and, if it passes, signs it with the fee payer key. The transaction is **not** broadcast: submit the returned `signed_transaction` yourself. The transaction must include the fee payment to the payment address, or it is rejected with `"Insufficient token payment"`. #### Invocation[​](#invocation-4 "Direct link to Invocation") ``` { "method": "signTransaction", "params": { transaction: string } } ``` #### Return[​](#return-4 "Direct link to Return") ``` { "result": { signed_transaction: string, signer_pubkey: string } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "signTransaction", "params": { "transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..." } } ``` The user has already signed; the fee payer's signature slot is still empty (the leading zero bytes). ``` { "jsonrpc": "2.0", "id": 1, "result": { "signed_transaction": "AgBZ71/kM9Ccb3i4Arc1npZnyHlETph7p7SY747zb7gt...", "signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz" } } ``` | key | type | description | | :------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transaction` | `string` | Base64-encoded transaction, signed by every required signer except the fee payer, and containing an SPL transfer of at least fee\_in\_token to the payment address | | key | type | description | | :------------------- | :------- | :----------------------------------------------------------------------------------------------------------- | | `signed_transaction` | `string` | Base64-encoded transaction with the paymaster's fee payer signature added. Not broadcast; submit it yourself | | `signer_pubkey` | `string` | The paymaster's fee payer address that signed | ### signAndSendTransaction[​](#signandsendtransaction "Direct link to signAndSendTransaction") Same validation and signing as [`signTransaction`](#signtransaction), then broadcasts the transaction to the network and returns its signature. This is the last call of the [transaction flow](#transaction-flow). It returns as soon as the transaction is submitted; poll `getSignatureStatuses` on your Solana RPC to wait for confirmation. #### Invocation[​](#invocation-5 "Direct link to Invocation") ``` { "method": "signAndSendTransaction", "params": { transaction: string } } ``` #### Return[​](#return-5 "Direct link to Return") ``` { "result": { signature: string, signed_transaction: string, signer_pubkey: string } } ``` * Example Request * Example Response * Request Types * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "signAndSendTransaction", "params": { "transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..." } } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "signature": "1QdRg6ThqLHa776Dq7FHjjzJTS3ZG8Me6FPgqTAa7mND1Wfc86Qk9B39i2p1dy5tvG9AuVHYLbHNTqdVUsJk1UZ", "signed_transaction": "AgBZ71/kM9Ccb3i4Arc1npZnyHlETph7p7SY747zb7gt...", "signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz" } } ``` This response is a real mainnet transaction: [view it on the explorer](https://explorer.solana.com/tx/1QdRg6ThqLHa776Dq7FHjjzJTS3ZG8Me6FPgqTAa7mND1Wfc86Qk9B39i2p1dy5tvG9AuVHYLbHNTqdVUsJk1UZ). | key | type | description | | :------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transaction` | `string` | Base64-encoded transaction, signed by every required signer except the fee payer, and containing an SPL transfer of at least fee\_in\_token to the payment address | | key | type | description | | :------------------- | :------- | :----------------------------------------------------------------------------------------- | | `signature` | `string` | Transaction signature (base58). Use it to track confirmation on any Solana RPC or explorer | | `signed_transaction` | `string` | Base64-encoded fully signed transaction as broadcast to the network | | `signer_pubkey` | `string` | The paymaster's fee payer address that signed | ### getBlockhash[​](#getblockhash "Direct link to getBlockhash") Returns a recent blockhash from the Solana RPC node the paymaster is connected to. Useful when your app does not have its own RPC connection at transaction-build time. #### Invocation[​](#invocation-6 "Direct link to Invocation") ``` { "method": "getBlockhash", "params": [] } ``` #### Return[​](#return-6 "Direct link to Return") ``` { "result": { blockhash: string } } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "getBlockhash", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "blockhash": "kZEkyRwERAqSXNULXRtg2rcnvUgKJPXyXGgXqFsPErk" } } ``` | key | type | description | | :---------- | :------- | :------------------------------------------------------------------------ | | `blockhash` | `string` | A recent blockhash from the Solana RPC node the paymaster is connected to | ### getConfig[​](#getconfig "Direct link to getConfig") Returns the paymaster's full configuration: fee payer addresses, the validation policy every transaction is checked against, and which RPC methods are enabled. Use it to discover limits programmatically instead of hardcoding the [current policy](#current-policy). #### Invocation[​](#invocation-7 "Direct link to Invocation") ``` { "method": "getConfig", "params": [] } ``` #### Return[​](#return-7 "Direct link to Return") ``` { "result": { fee_payers: string[], validation_config: object, enabled_methods: object } } ``` * Example Request * Example Response * Response Type ``` { "jsonrpc": "2.0", "id": 1, "method": "getConfig", "params": [] } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "fee_payers": ["CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"], "validation_config": { "max_allowed_lamports": 9000000, "max_signatures": 10, "allowed_programs": [ "11111111111111111111111111111111", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", "ComputeBudget111111111111111111111111111111", "AddressLookupTab1e1111111111111111111111111" ], "allowed_tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"], "allowed_spl_paid_tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"], "disallowed_accounts": [], "price_source": "Jupiter", "fee_payer_policy": { "system": { "allow_transfer": false, "allow_assign": false, "allow_create_account": true, "allow_allocate": false, "nonce": { "allow_initialize": false, "allow_advance": false, "allow_withdraw": false, "allow_authorize": false } }, "spl_token": { "allow_transfer": false, "allow_burn": false, "allow_close_account": false, "allow_approve": false, "allow_revoke": false, "allow_set_authority": false, "allow_mint_to": false, "allow_initialize_mint": false, "allow_initialize_account": false, "allow_initialize_multisig": false, "allow_freeze_account": false, "allow_thaw_account": false }, "token_2022": { "allow_transfer": false, "allow_burn": false, "allow_close_account": false, "allow_approve": false, "allow_revoke": false, "allow_set_authority": false, "allow_mint_to": false, "allow_initialize_mint": false, "allow_initialize_account": false, "allow_initialize_multisig": false, "allow_freeze_account": false, "allow_thaw_account": false } }, "price": { "type": "margin", "margin": 0.05 }, "token_2022": { "blocked_mint_extensions": ["permanent_delegate"], "blocked_account_extensions": [] }, "allow_durable_transactions": false }, "enabled_methods": { "liveness": true, "estimate_transaction_fee": true, "get_supported_tokens": true, "get_payer_signer": true, "sign_transaction": true, "sign_and_send_transaction": true, "transfer_transaction": true, "get_blockhash": true, "get_config": true } } } ``` | key | type | description | | :--------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------- | | `fee_payers` | `string[]` | The paymaster's fee payer addresses | | `validation_config` | `object` | The policy every transaction is validated against | | `validation_config.max_allowed_lamports` | `number` | Maximum lamports the fee payer will spend on a single transaction (fees plus rent) | | `validation_config.max_signatures` | `number` | Maximum number of signatures allowed in a transaction | | `validation_config.allowed_programs` | `string[]` | Program IDs a transaction may invoke. Transactions touching any other program are rejected | | `validation_config.allowed_tokens` | `string[]` | Mint addresses of tokens that may be transferred | | `validation_config.allowed_spl_paid_tokens` | `string[]` | Mint addresses of tokens accepted as fee payment | | `validation_config.disallowed_accounts` | `string[]` | Accounts that transactions may not reference | | `validation_config.price_source` | `string` | Oracle used to price the fee token against SOL | | `validation_config.fee_payer_policy` | `object` | Per-instruction permissions for what the fee payer key itself may be used for, per program (system, spl\_token, token\_2022) | | `validation_config.price` | `object` | Fee pricing model. type is free, fixed, or margin; margin is the markup applied over the network cost | | `validation_config.token_2022` | `object` | Token-2022 mint and account extensions that are blocked | | `validation_config.allow_durable_transactions` | `boolean` | Whether durable nonce transactions are accepted | | `enabled_methods` | `object` | Map of RPC method names (snake\_case) to whether they are enabled on this endpoint | ## Errors[​](#errors "Direct link to Errors") Failures use standard JSON-RPC 2.0 error objects. The messages below were captured from the live endpoint: ``` { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "..." } } ``` | Code | Example message | Cause | | ------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -32000 | `Invalid transaction: Insufficient token payment. Required 10553 lamports` | The transaction does not transfer at least the quoted fee to the payment address. The required amount is expressed in lamports; convert with `estimateTransactionFee` | | -32000 | `Invalid request: Token EPjFW...Dt1v is not supported` | `fee_token` or a transferred token is not enabled for your endpoint (here USDC on an endpoint with only the default USDT). See [Supported Tokens](#supported-tokens) | | -32000 | `Invalid transaction: Failed to deserialize transaction: io error: unexpected end of file` | `transaction` is not a base64-encoded serialized Solana transaction | | -32600 | `Unsupported method - getPaymentInstruction not supported` | The method does not exist or is disabled on this endpoint | --- # -32500 Transaction Rejected By Entrypoint Simulation Transaction rejected by entryPoint’s simulateValidation, during wallet creation or validation. The message field is set the FailedOp’s “AAxx” error message from the EntryPoint. The EntryPoint may also provide an additional `AAxx` code to offer further guidance. Specifically: * AA1x error codes pertain to factories. * AA2x error codes are related to the sender. * AA3x error codes are associated with paymasters. * AA4x error codes involve general verification. * AA5x errors relate to actions taken after the user operation has been executed. See the explanation of each [error code provided by the Entrypoint](https://docs.candide.dev/wallet/technical-reference/entrypoint-error-codes.md) --- # -32501 Transaction Rejected by Paymaster The error code -32501 is generated in cases where a user operation is declined by a Paymaster. Prior to sending user operations onchain, bundlers meticulously validate that any paymasters involved will reimburse them for their services. If you encounter this error, it is advisable to inspect the paymasterAndData section of the user operation to identify and rectify any issues. --- # -32502 Transaction Rejected by opcode Validation The error code -32502 signals that a user operation has been declined due to an unauthorized operation performed by the sender during signature verification. To ensure a smooth process when forwarding user operations onchain, bundlers are obligated to verify that the user operations do not pose any harm to the bundler's operations. This includes preventing the use of specific opcodes that may be disruptive when verifying a signature. For all ERC-4337 bundlers, it is imperative to adhere to these opcode regulations, although certain bundlers have the capacity to create alternate user operation mempools that relax these rules. If you encounter this error, it may be necessary to make adjustments to the smart contracts associated with the account to ensure compliance. --- # -32503 UserOperation out of time-range This error code means that either the account or the paymaster returned a time-range, and it is already expired or will expire soon. Check the time range signed by the account or the paymaster. --- # -32504 Paymaster or Aggregator Throttled or Banned This error code signifies that the user operation has been declined due to the throttling or banning of the associated paymaster or aggregator. To safeguard the bundler network from potential abuse, bundlers enforce measures that limit the actions of entities violating certain rules. If you encounter this error unexpectedly, it's crucial to investigate why the bundler has taken such action. While user operations are designed not to interfere with one another, "global" entities like paymasters, factories, and aggregators can be accessed by multiple user operations, potentially invalidating previously valid ones. To prevent misuse, ERC-4337 bundlers implement throttling, reducing the rate at which entities can submit user operations, or even temporarily banning them if they disrupt the mempool with invalidations. To deter "sybil-attacks," these entities are required to stake with the EntryPoint, making DoS attacks economically unviable. Importantly, this stake is never forfeited and can be withdrawn after a set unstake delay. --- # -32505 Paymaster or Aggregator stake or unstake-delay is too low This error code indicates that the paymaster or aggregator used in the user operation lacks the required stake with the EntryPoint. While user operations are designed to not interfere with each other, certain "global" entities like paymasters, factories, and aggregators can be accessed by multiple user operations, potentially invalidating multiple previously valid ones. To prevent misuse, ERC-4337 bundlers employ throttling or temporary bans on entities causing widespread invalidations in the mempool. To deter potential "sybil-attacks," these entities are obliged to stake with the EntryPoint, making Denial-of-Service (DoS) attacks financially impractical. It's important to note that this stake is never subject to reduction and can be withdrawn at any time, following a specified unstake delay. --- # -32506 Unsupported Aggregator The transaction was rejected because the account specified an unsupported signature aggregator. If you encounter this code, it is likely the aggregator is incorrectly specified in the user operation or is not a valid ERC-4337 aggregator. --- # -32507 Invalid UserOp Signature or Paymaster Signature This error code means that the user operation contains an invalid signature from either the sender or the paymaster. This error code is returned when `validateUserOp` or `validatePaymasterUserOp` is checked by the bundler before submitting the user operation on-chain. --- # -32508 Paymaster Balance Too Low The transaction was rejected because the paymaster's EntryPoint deposit cannot cover all pending UserOperations that depend on it. This is distinct from a stake or unstake-delay failure. The paymaster may be valid, but its available deposit is not enough for the bundler to safely accept another sponsored UserOperation. If you control the paymaster, deposit more native tokens into the EntryPoint or wait for pending sponsored UserOperations to be included. If you are using a third-party paymaster service, retry later or contact the paymaster provider. --- # -32521 Transaction Reverted Transaction was reverted during the execution phase. This likely means that there is insufficient gas to pay for the execution or there an error with the callData in your user operation. We recommend using a service like Tenderly to simulate the callData and identify the error. --- # -32602 Invalid UserOperation This error code means that the user operation contains an invalid field. Check that all fields of your user operation are correct. --- # EntryPoint Error AA10 sender already constructed The `sender` has already already created, so initCode does not need to be included in the user operation. This error may occur if you attempt to create the same account multiple times. --- # EntryPoint Error AA13 initCode failed or OOG The `initCode` failed to create the account or ran out of gas. "OOG" = Out Of Gas. Check the amount of gas consumed, and then verify the initCode or the factory contract is correct. --- # EntryPoint Error AA14 initCode must return sender The `initCode` does not return the sender address. Check the initCode or the factory contract. --- # EntryPoint Error AA15 initCode must create sender The initCode in the user operation does not create an account. Check the initCode or the factory contract. --- # EntryPoint Error AA20 Account Not Deployed The sender of the user operation is not deployed and there is no initCode specified. If this is the first transaction by this account make sure an initCode is included. Otherwise, check that the correct sender address is specified and is an ERC-4337 account. --- # EntryPoint Error AA21 Didn’t pay prefund The sender lacks sufficient native tokens to cover the gas costs associated with the User Operation. If your intention was to utilize a Paymaster for sponsorship, it is crucial to verify that the paymasterAndData field of the user operation is correctly configured. After the user operation is executed, the remainder of the prefund is credited back to the sender. --- # EntryPoint Error AA22 Expired or not due The signature is not valid because it is outside of the specified time range. --- # EntryPoint Error AA23 reverted (or OOG) The sender signature validation was rejected or ran out of gas. "OOG" Out-Of-Gas. The verificationGasLimit may be too low. --- # EntryPoint Error AA24 Signature Error Check the signature. It may be in an incompatible format. --- # EntryPoint Error AA25 Invalid account nonce The nonce is invalid. The user operation may be re-using an old nonce, or formatted the nonce incorrectly. --- # EntryPoint Error AA30 Paymaster not deployed The paymaster address specified by `paymasterAndData` contains no code. Check that the first characters of the paymasterAndData field are the paymaster address you intend to use. --- # EntryPoint Error AA31 Paymaster deposit too low The paymaster is out of funds. More gas tokens must be deposited into the EntryPoint for the paymaster. This is usually done by calling the paymaster contract's deposit function. If you are using Candide Paymaster, let us know. --- # EntryPoint Error AA32 Paymaster expired or not due Paymaster signatures have a specific time range. If the user operation was executed after this time range, the paymaster's signature becomes invalid --- # EntryPoint Error AA33 reverted (or OOG) The paymaster validation was rejected or ran out of gas. "OOG" = Out-Of-Gas. First check the paymaster's signature in paymasterAndData. If the signature is correct, the verificationGasLimit may be too low. --- # EntryPoint Error AA34 Signature Error The paymaster's signature is invalid. Check the format of the signature in `paymasterAndData`. --- # EntryPoint Error AA40 over verificationGasLimit The verification gas limit was exceeded. Check the `verificationGasLimit` in your user operation. --- # EntryPoint Error AA41 too little verificationGas Verifying the user operation took too much gas and did not complete. You may need to increase `verificationGasLimit`. --- # EntryPoint Error AA50 PostOp reverted After the user operation was completed, the execution of additional logic by the EntryPoint reverted. --- # EntryPoint Error AA51 prefund below actualGasCost The actual cost of the user operation is higher than the total amount of gas approved. The prefund is the amount that the EntryPoint is allowed to execute the user operation. After the user operation is executed, the remainder of the prefund is credited back to the sender. --- # Bundler Error Codes If a UserOperation is valid, the bundler return the calculated userOpHash for it. In case of failure, the bundler return an error result object, with code and message. Here are the common error codes as defined by the protocol. These error codes supplement the standard JSON-RPC error codes that are typically returned in response to an incorrect method call. Additionally, many of these errors will be accompanied by [an extra error message provided by the Entrypoint](https://docs.candide.dev/wallet/technical-reference/entrypoint-error-codes.md). ## Invalid UserOp Error Codes[​](#invalid-userop-error-codes "Direct link to Invalid UserOp Error Codes") | Code | Description | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -32500 | Transaction rejected by entryPoint’s simulateValidation, during wallet creation or validation. The message field is set the FailedOp’s “AAxx” error message from the EntryPoint. | | -32501 | Transaction rejected by paymaster’s validatePaymasterUserOp | | -32502 | Transaction rejected because of opcode validation | | -32503 | UserOperation out of time-range: either wallet or paymaster returned a time-range, and it is already expired (or will expire soon) | | -32504 | Transaction rejected because paymaster (or signature aggregator) is throttled or banned | | -32505 | Transaction rejected because paymaster (or signature aggregator) stake or unstake-delay is too low | | -32506 | Transaction rejected because wallet specified unsupported signature aggregator | | -32507 | Transaction rejected because of wallet signature check failed (or paymaster signature, if the paymaster uses its data as signature) | | -32508 | Transaction rejected because paymaster balance cannot cover all pending UserOperations | | -32521 | Transaction was reverted during the execution phase | | -32602 | Invalid UserOperation struct/fields | --- # EVM Chain Nuances ## Gas prices[​](#gas-prices "Direct link to Gas prices") Smart Wallet developers face challenges with varying EVM gas estimates. AbstractionKit provides flexibility through overrides. Using the [createUserOperation](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createuseroperation) function, developers can pass their own values for `maxPriorityFeePerGas` and `maxFeePerGas`. Gas Overrides example ``` const userOperation = await smartAccount.createUserOperation( [transaction], jsonRpcNodeProvider, bundlerUrl, { maxPriorityFeePerGas: 56139916666n, maxFeePerGas: 134386409498n, } ) ``` ### Polygon[​](#polygon "Direct link to Polygon") Dealing with Polygon's occasional gas fee spikes poses challenges. AbstractionKit provides out of the box support for [Polygon's Gas Station API](https://docs.polygon.technology/tools/gas/polygon-gas-station/#mainnet) for reliable real-time gas prices. Opt for the 'Fast' fee prediction to add a multiplier during congestion to optimize transaction costs Using the [createUserOperation](https://docs.candide.dev/wallet/abstractionkit/safe-account-v3.md#createuseroperation) function, developers can pass select the polygon network and the gas multiplier. Gas Overrides example ``` import { PolygonChain, GasOption } from "abstractionkit"; const userOperation = await smartAccount.createUserOperation( [transaction], jsonRpcNodeProvider, bundlerUrl, { gasLevel: GasOption.Fast, // Slow, Medium, Fast polygonGasStation: PolygonChain.Mainnet, // fetched from Polygon Gas Station } ) ``` --- # Contract Deployments ![Arbitrum](/img/networks/42161.png)Arbitrum ![Avalanche](/img/networks/43114.png)Avalanche ![Base](/img/networks/8453.png)Base ![BnB Chain](/img/networks/56.png)BnB Chain ![Celo](/img/networks/5567.png)Celo ![Ethereum](/img/networks/1.png)Ethereum ![Gnosis](/img/networks/100.svg)Gnosis ![HyperEVM](/img/networks/999.png)HyperEVM ![Optimism](/img/networks/10.png)Optimism ![Plasma](/img/networks/9745.svg)Plasma ![Polygon](/img/networks/137.png)Polygon ![Solana](/img/networks/solana.svg)Solana ![Somnia](/img/networks/5031.png)Somnia ![Tempo](/img/networks/4217.png)Tempo ![World Chain](/img/networks/480.svg)World Chain ## Account Abstraction EntryPoint[​](#account-abstraction-entrypoint "Direct link to Account Abstraction EntryPoint") | Version | Address | Release | | ------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | v0.9 | 0x433709009B8330FDa32311DF1C2AFA402eD8D009 | [eth-infinitism/account-abstraction/releases/tag/v0.9.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.9.0) | | v0.8 | 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 | [eth-infinitism/account-abstraction/releases/tag/v0.8.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.8.0) | | v0.7 | 0x0000000071727De22E5E9d8BAf0edAc6f37da032 | [eth-infinitism/account-abstraction/releases/tag/v0.7.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.7.0) | | v0.6 | 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 | [eth-infinitism/account-abstraction/releases/tag/v0.6.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.6.0) | ## Simple7702Account[​](#simple7702account "Direct link to Simple7702Account") | Version | Address | Release | | ------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | v0.9 | 0xa46cc63eBF4Bd77888AA327837d20b23A63a56B5 | [eth-infinitism/account-abstraction/releases/tag/v0.9.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.9.0) | | v0.8 | 0xe6Cae83BdE06E4c305530e199D7217f42808555B | [eth-infinitism/account-abstraction/releases/tag/v0.8.0](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.8.0) | ## Candide Paymasters[​](#candide-paymasters "Direct link to Candide Paymasters") | Contract | Address | | ------------------------------- | ------------------------------------------ | | CandidePaymaster V3 for EP v0.7 | 0x8b1f6cb5d062aa2Ce8d581942bbb960420d875BA | | CandidePaymaster V3 for EP v0.6 | 0x36F4Aa64673568782461bF03C75462f8ef0A1b76 | ## Safe 4337 Module[​](#safe-4337-module "Direct link to Safe 4337 Module") | Contract | Address | | ---------------------- | ------------------------------------------ | | Safe4337Module v0.3.0 | 0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226 | | SafeModuleSetup v0.3.0 | 0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47 | | Safe4337Module v0.2.0 | 0xa581c4A4DB7175302464fF3C06380BC3270b4037 | | AddModulesLib v0.2.0 | 0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb | For Safe 4337 code and audits, refer to [safe-4337-module](https://github.com/safe-global/safe-modules-deployments/tree/main/src/assets/safe-4337-module). ## Safe 4337 MultiChain Signature Module[​](#safe-4337-multichain-signature-module "Direct link to Safe 4337 MultiChain Signature Module") | Contract | Address | | --------------------------------- | ------------------------------------------ | | Safe4337MultiChainSignatureModule | 0x22939E839e3c0F479B713eAF95e0df128554AEAd | For Safe 4337 MultiChain Signature Module code and audits, refer to [safe-4337-multi-chain-signature-module](https://github.com/candidelabs/safe-4337-multi-chain-signature-module). ## Social Recovery Module[​](#social-recovery-module "Direct link to Social Recovery Module") Addresses vary based on recovery grace periods. Deployments available for: * 3 Days * 7 Days * 14 Days * 3 Minutes (testnets only) | Network | 3 Days Recovery Grace Period | 7 Days Recovery Grace Period | 14 Days Recovery Grace Period | 3 minutes Recovery Grace Period | | ---------------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | | Arbitrum | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Avalanche C | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Base | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | BSC | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Celo | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Ethereum Mainnet | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Ethereum Sepolia | | | | 0x949d01d424bE050D09C16025dd007CB59b3A8c66 | | Gnosis | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Moonbeam | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Optimism | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Plasma | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | Polygon | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | | World Chain | 0x38275826E1933303E508433dD5f289315Da2541c | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | 0x9BacD92F4687Db306D7ded5d4513a51EA05df25b | | For smart contract code, formal verification, and audits, refer to [candide-contracts](https://github.com/candidelabs/candide-contracts). ## Safe Passkeys[​](#safe-passkeys "Direct link to Safe Passkeys") The Safe Passkeys Module is deployed in the following networks: | Network | `SafeWebAuthnSignerFactory` Address | `DaimoP256Verifier` Address | `FCLP256Verifier` Address | | ---------------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | | Arbitrum | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Arbitrum Sepolia | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Base | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Base Sepolia | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Ethereum Mainnet | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Ethereum Sepolia | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Optimism | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Optimism Sepolia | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Polygon | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | | Polygon Amoy | 0xF7488fFbe67327ac9f37D5F722d83Fc900852Fbf | 0xc2b78104907F722DABAc4C69f826a522B2754De4 | 0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765 | For Safe Passkeys code and audits, refer to [safe-4337-module](https://github.com/safe-global/safe-modules/tree/main/modules/passkey#deploy) --- # Entrypoint Error Codes If a UserOperation is valid, the bundler return the calculated userOpHash for it. In case of failure, the bundler return an error result object, with code and message. ## AAxx Error Codes[​](#aaxx-error-codes "Direct link to AAxx Error Codes") Entrypoint Validation Error Codes: These error codes are returned by ERC-4337 Entrypoint and may occur when you send user operations. * AA1x error codes pertain to account creation. * AA2x error codes concern the sender of the user operation. * AA3x error codes are associated with paymasters. * AA4x error codes relate to general verification. * AA5x errors are related to actions taken after the user operation has been executed. | Code | Description | Explanation | | ---- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AA10 | Sender already constructed | The `sender` has already already created, so initCode does not need to be included in the user operation. | | AA13 | initCode failed or Out of Gas OOG | Check the amount of gas consumed, and then verify the initCode or the factory contract is correct. | | AA14 | initCode must return sender | The initCode does not return the sender address. Check the initCode or the factory contract. | | AA15 | initCode must create sender | The initCode in the user operation does not create an account. Check the initCode or the factory contract. | | AA20 | Account Not Deployed | If this is the first transaction by this account make sure an initCode is included. Otherwise, check that the correct sender address is specified and is an ERC-4337 account. | | AA21 | Didn't pay prefund | The sender lacks sufficient native tokens to cover the gas costs associated with the User Operation. If your intention was to utilize a Paymaster for sponsorship, it is crucial to verify that the paymasterAndData field of the user operation is correctly configured. | | AA22 | Expired or not due | The signature is not valid because it is outside of the specified time range. | | AA23 | AA23 reverted OOG | The sender signature validation was rejected or ran out of gas. "OOG" is an abbreviation for Out-Of-Gas. The verificationGasLimit may be too low. | | AA24 | Signature Error | Check the signature. It may be in an incompatible format. | | AA25 | Invalid account nonce | The nonce is invalid. The user operation may be re-using an old nonce, or formatted the nonce incorrectly. | | AA30 | Paymaster not deployed | The paymaster address specified by paymasterAndData contains no code. Check that the first characters of the paymasterAndData field are the paymaster address you intend to use. | | AA31 | Paymaster deposit too low | The paymaster is out of funds. More gas tokens must be deposited into the EntryPoint for the paymaster. This is usually done by calling the paymaster contract's deposit function. If you are using Candide Paymaster, let us know. | | AA32 | Paymaster expired or not due | Paymaster signatures have a specific time range. If the user operation was executed after this time range, the paymaster's signature becomes invalid | | AA33 | AA33 reverted | The paymaster validation was rejected or ran out of gas. "OOG" = Out-Of-Gas. First check the paymaster's signature in paymasterAndData. If the signature is correct, the verificationGasLimit may be too low. | | AA34 | Signature Error | The paymaster's signature is invalid. Check the format of the signature in `paymasterAndData`. | | AA40 | Over verification gas limit | The verification gas limit was exceeded. Check the `verificationGasLimit` in your user operation. | | AA41 | Too little verification gas | Verifying the user operation took too much gas and did not complete. You may need to increase `verificationGasLimit`. | | AA50 | PostOp reverted | After the user operation was completed, the execution of additional logic by the EntryPoint reverted. | | AA51 | prefund below actualGasCost | The actual cost of the user operation is higher than the total amount of gas approved. The prefund is the amount that the EntryPoint is allowed to execute the user operation. After the user operation is executed, the remainder of the prefund is credited back to the sender. | ---