### Complete Example: Email and SMS Alerts Source: https://docs.candide.dev/wallet/plugins/recovery-alerts-guide This snippet shows the full workflow for setting up both email and SMS alerts. It includes creating subscriptions, sending OTPs, activating them, and verifying the final configuration. Use this as a reference for the entire alert setup process. ```javascript ownerAccount.address, userEmail, emailSubscriptionSiweMessage, emailSubscriptionSignature ); const emailOtpChallenge = await askQuestion(`\nOTP sent to ${userEmail} — enter code: `); try { const emailActivationResult = await alertsService.activateSubscription(emailSubscriptionId, emailOtpChallenge); if (emailActivationResult) { console.log("Email alerts activated."); } else { console.log("Failed to activate email alerts"); } } catch (error) { if (error instanceof SafeRecoveryServiceSdkError) { console.error("Error activating email subscription:", error.stringify()); } else { console.error("Error activating email subscription:", error instanceof Error ? error.message : error); } } } // Create and activate SMS alert subscription (if selected) if (enableSms) { const smsSubscriptionSiweMessage = alertsService.createSubscriptionSiweStatementToSign( safeAccountAddress, ownerAccount.address, "sms", userPhone ); const smsSubscriptionSignature = await ownerAccount.signMessage({ message: smsSubscriptionSiweMessage }); const smsSubscriptionId = await alertsService.createSubscription( safeAccountAddress, ownerAccount.address, "sms", userPhone, smsSubscriptionSiweMessage, smsSubscriptionSignature ); const smsOtpChallenge = await askQuestion(`\nOTP sent to ${userPhone} — enter code: `); try { const smsActivationResult = await alertsService.activateSubscription(smsSubscriptionId, smsOtpChallenge); if (smsActivationResult) { console.log("SMS alerts activated."); } else { console.log("Failed to activate SMS alerts"); } } catch (error) { if (error instanceof SafeRecoveryServiceSdkError) { console.error("Error activating SMS subscription:", error.stringify()); } else { console.error("Error activating SMS subscription:", error instanceof Error ? error.message : error); } } } // Verify active subscriptions const verifySubscriptionsSiweMessage = alertsService.getSubscriptionsSiweStatementToSign(ownerAccount.address); const verifySubscriptionsSignature = await ownerAccount.signMessage({ message: verifySubscriptionsSiweMessage }); const activeSubscriptions = await alertsService.getActiveSubscriptions( safeAccountAddress, ownerAccount.address, verifySubscriptionsSiweMessage, verifySubscriptionsSignature ); const channels = [enableEmail ? `email (${userEmail})` : null, enableSms ? `SMS (${userPhone})` : null] .filter(Boolean).join(", "); console.log(`\nActive subscriptions (${activeSubscriptions.length}):`); activeSubscriptions.forEach((sub, i) => { console.log(` ${i + 1}. ${sub.channel} — ${sub.target}`); }); console.log(`\nAlerts configured for: ${channels}`); console.log("You will be notified when a recovery request is initiated, executed, or finalized."); console.log("\nNext: yarn dev:recovery-flow (Example 03)"); rl.close(); } main() .then(() => process.exit(0)) .catch((error) => { if (error instanceof SafeRecoveryServiceSdkError) { console.error("\nError:", error.stringify()); } else { console.error("\nError:", error instanceof Error ? error.message : error); let cause = error?.cause; while (cause) { console.error("Caused by:", cause instanceof Error ? cause.message : JSON.stringify(cause)); cause = cause?.cause; } } rl.close(); process.exit(1); }); ``` -------------------------------- ### Complete Working Example: Alerts Setup Source: https://docs.candide.dev/wallet/plugins/recovery-alerts-guide Subscribes email and SMS channels to recovery event notifications for a Safe account. Uses SIWE for off-chain authentication. Prerequisites include running Example 01 and configuring environment variables. ```typescript /** * Example 02: Alerts Setup * * Demonstrates: Subscribing email and SMS channels to recovery event notifications for * a Safe account that already has the Social Recovery Module enabled (set up in Example 01). * Uses SIWE (Sign-In With Ethereum) for off-chain authentication — no on-chain transaction needed. * * Prerequisites: * - Run Example 01 first and add SAFE_ACCOUNT_ADDRESS to your .env * - Fill in .env: CHAIN_ID, RECOVERY_SERVICE_URL, SAFE_ACCOUNT_ADDRESS, * OWNER_PRIVATE_KEY, USER_EMAIL (and USER_PHONE if you want SMS alerts) * * Expected duration: ~5 minutes (includes OTP round-trips) * * After this example: * - Your Safe will send email/SMS alerts whenever a recovery event occurs * - Run Example 03 to trigger a recovery and observe the alerts */ import { privateKeyToAccount } from 'viem/accounts'; import { Alerts, SafeRecoveryServiceSdkError } from "safe-recovery-service-sdk"; import * as dotenv from 'dotenv'; import * as readline from 'readline'; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function askQuestion(question: string): Promise { return new Promise((resolve) => rl.question(question, resolve)); } async function main() { dotenv.config(); console.log("Alerts Setup — Example 02"); console.log("==========================\n"); const requiredEnvVars = [ 'CHAIN_ID', 'RECOVERY_SERVICE_URL', 'SAFE_ACCOUNT_ADDRESS', 'OWNER_PRIVATE_KEY', 'USER_EMAIL' ]; const missing = requiredEnvVars.filter(v => !process.env[v]); if (missing.length > 0) { throw new Error(`Missing required environment variables: ${missing.join(', ')}. Please check your .env file.`); } const chainId = BigInt(process.env.CHAIN_ID as string); const serviceUrl = process.env.RECOVERY_SERVICE_URL as string; const safeAccountAddress = process.env.SAFE_ACCOUNT_ADDRESS as string; const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY as `0x${string}`; const userEmail = process.env.USER_EMAIL as string; const userPhone = process.env.USER_PHONE as string; const ownerAccount = privateKeyToAccount(ownerPrivateKey); console.log(`Owner: ${ownerAccount.address}`); console.log(`Safe Account: ${safeAccountAddress}\n`); const alertsService = new Alerts(serviceUrl, chainId); // Check for existing subscriptions before creating new ones. // SIWE is used here for off-chain authentication — it proves ownership of the owner address // without requiring an on-chain transaction, keeping this step gasless. const getSubscriptionsSiweMessage = alertsService.getSubscriptionsSiweStatementToSign(ownerAccount.address); const getSubscriptionsSignature = await ownerAccount.signMessage({ message: getSubscriptionsSiweMessage }); const existingSubscriptions = await alertsService.getActiveSubscriptions( safeAccountAddress, ownerAccount.address, getSubscriptionsSiweMessage, getSubscriptionsSignature ); if (existingSubscriptions.length > 0) { console.log(`Existing subscriptions (${existingSubscriptions.length}):`); existingSubscriptions.forEach((sub, i) => { console.log(` ${i + 1}. ${sub.channel} — ${sub.target}`); }); console.log(); } // Choose alert channels console.log("Choose your alert channels:"); console.log(" 1. Email only"); console.log(" 2. SMS only"); console.log(" 3. Both email and SMS"); const channelChoice = await askQuestion("Enter choice (1-3): "); const choice = parseInt(channelChoice); if (choice < 1 || choice > 3) { console.log("Invalid choice"); rl.close(); return; } const enableEmail = choice === 1 || choice === 3; const enableSms = choice === 2 || choice === 3; if (enableSms && !userPhone) { throw new Error("USER_PHONE is required for SMS alerts. Add it to your .env file."); } // Create and activate email alert subscription (if selected) if (enableEmail) { const emailSubscriptionSiweMessage = alertsService.createEmailSubscriptionSiweStatementToSign( safeAccountAddress, ownerAccount.address, userEmail ); const emailSubscriptionSignature = await ownerAccount.signMessage({ message: emailSubscriptionSiweMessage }); const emailSubscriptionId = await alertsService.createEmailSubscription( safeAccountAddress, ``` -------------------------------- ### Full Example: Multi-Chain Smart Account Operations Source: https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started This comprehensive example shows how to send a user operation to multiple chains, monitor its inclusion, and verify ownership on each chain. It requires setup with bundler URLs, node URLs, and the new owner's address. ```typescript userOperation2 = finalOp2 console.log("[5/5] Submitting to both chains...") await Promise.all([ sendAndMonitorUserOperation(userOperation1, bundlerUrl1, "Chain 1"), sendAndMonitorUserOperation(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("\n" + "=".repeat(60)) console.log("VERIFICATION COMPLETE") console.log("=".repeat(60)) console.log("\nOwners on Chain 1:", owners1) console.log("Owners on Chain 2:", owners2) const hasNewOwner1 = owners1.map((o: string) => o.toLowerCase()).includes(newOwnerAddress.toLowerCase()) const hasNewOwner2 = owners2.map((o: string) => o.toLowerCase()).includes(newOwnerAddress.toLowerCase()) if (hasNewOwner1 && hasNewOwner2) { console.log("\nNew owner successfully added on BOTH chains with ONE signature!") } } async function sendAndMonitorUserOperation( userOperation: UserOperationV9, bundlerUrl: string, chainName: string ): Promise { const smartAccount = new SafeAccount(userOperation.sender); const sendUserOperationResponse = await smartAccount.sendUserOperation( userOperation, bundlerUrl ) console.log(` [${chainName}] UserOperation sent. Waiting for inclusion...`) const receipt = await sendUserOperationResponse.included() if (receipt == null) { console.log(` [${chainName}] Receipt not found (timeout)`) } else if (receipt.success) { console.log(` [${chainName}] Success! Tx: ${receipt.receipt.transactionHash}`) } else { console.log(` [${chainName}] Execution failed`) } } main().catch(console.error) ``` -------------------------------- ### Install AbstractionKit with npm Source: https://docs.candide.dev/wallet/plugins/recovery-module-reference Install the AbstractionKit library using npm. ```bash npm i abstractionkit ``` -------------------------------- ### Install AbstractionKit, Magic SDK, and Viem Source: https://docs.candide.dev/wallet/guides/magic Install AbstractionKit, Magic SDK, and Viem for projects utilizing Viem. ```bash npm i abstractionkit && magic-sdk && viem ``` -------------------------------- ### Install with npm Source: https://docs.candide.dev/wallet/plugins/recovery-service-sdk-reference Install the Safe Recovery Service SDK using npm. ```bash npm i safe-recovery-service-sdk ``` -------------------------------- ### Install Dependencies Source: https://docs.candide.dev/wallet/guides/recovery-with-google-using-lit Install the necessary AbstractionKit and Lit SDK packages using npm. ```bash npm i abstractionkit && @lit-protocol/lit-auth-client ``` -------------------------------- ### Install with yarn Source: https://docs.candide.dev/wallet/plugins/recovery-service-sdk-reference Install the Safe Recovery Service SDK using yarn. ```bash yarn add safe-recovery-service-sdk ``` -------------------------------- ### Install AbstractionKit and Turnkey Dependencies (Viem) Source: https://docs.candide.dev/wallet/guides/turnkey Install the necessary AbstractionKit and Turnkey packages for projects using Viem. ```bash npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/viem ``` -------------------------------- ### Install AbstractionKit and Magic SDK Source: https://docs.candide.dev/wallet/guides/magic Install the necessary packages for AbstractionKit and the Magic SDK using npm. ```bash npm i abstractionkit && magic-sdk ``` -------------------------------- ### Install Dependencies Source: https://docs.candide.dev/wallet/guides/getting-started-calibur Install necessary development and runtime dependencies for the project. ```bash npm i typescript --save-dev npm i abstractionkit dotenv ``` -------------------------------- ### Install AbstractionKit Source: https://docs.candide.dev/ Install the AbstractionKit package using npm. This is the first step to using the toolkit for building smart accounts. ```bash npm install abstractionkit ``` -------------------------------- ### Full Example Script Source: https://docs.candide.dev/wallet/guides/getting-started-eip-7702 This is a placeholder for the full example script. Refer to the GitHub link for the complete code. ```typescript loading... ``` -------------------------------- ### Example wallet_getCapabilities Request Parameters Source: https://docs.candide.dev/instagas/batch-sponsor-transactions Example parameters for the `wallet_getCapabilities` RPC, specifying an address and an array of chain IDs. ```json ["0xd46e8dd67c5d32be8058bb8eb970870f07244567", ["0x2105", "0x14A34"]] ``` -------------------------------- ### Install AbstractionKit Source: https://docs.candide.dev/wallet/plugins/allowance-migration Install the latest version of AbstractionKit to use the Allowance Module v1.0.0. ```bash npm install abstractionkit ``` -------------------------------- ### Install Dependencies Source: https://docs.candide.dev/account-abstraction/7702/delegation Install the necessary abstractionkit and ethers packages for EIP-7702 functionality. ```bash npm i abstractionkit ethers ``` -------------------------------- ### Install AbstractionKit and Ox for WebAuthn Source: https://docs.candide.dev/wallet/plugins/passkeys Install the necessary libraries for Safe account tooling and WebAuthn interactions using npm. ```bash npm i abstractionkit ox ``` -------------------------------- ### Install Dependencies Source: https://docs.candide.dev/wallet/guides/calibur-passkeys Install the necessary libraries for abstractionkit and dotenv to manage environment variables. ```bash npm i abstractionkit dotenv ``` -------------------------------- ### Install Dependencies with npm Source: https://docs.candide.dev/wallet/plugins/recover-account-candide-guardian Install the necessary packages for account recovery using npm. ```bash npm i abstractionkit safe-recovery-service-sdk viem ``` -------------------------------- ### Install Project Dependencies Source: https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started Installs necessary development and runtime dependencies for the project, including abstractionkit, dotenv, and viem. ```bash npm install typescript ts-node --save-dev npm install abstractionkit dotenv viem ``` -------------------------------- ### Install AbstractionKit and Turnkey Dependencies (Ethers) Source: https://docs.candide.dev/wallet/guides/turnkey Install the necessary AbstractionKit and Turnkey packages for projects using Ethers.js. ```bash npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/ethers ``` -------------------------------- ### Install AbstractionKit and Ox for WebAuthn with Yarn Source: https://docs.candide.dev/wallet/plugins/passkeys Install the necessary libraries for Safe account tooling and WebAuthn interactions using yarn. ```bash yarn add abstractionkit ox ``` -------------------------------- ### Install Recovery Service SDK and Abstraction Kit Source: https://docs.candide.dev/wallet/plugins/recovery-alerts-guide Install the necessary npm packages for recovery alerts, account management, and wallet operations. ```bash npm i safe-recovery-service-sdk abstractionkit viem ``` -------------------------------- ### Full Runnable Example: Setting Up and Executing Allowances Source: https://docs.candide.dev/wallet/plugins/allowance This comprehensive script demonstrates the complete process of setting up and managing token allowances using the Allowance Module. It includes funding the Safe, enabling the module, adding delegates, and setting recurring allowances. ```typescript import { loadEnv, getOrCreateOwner, requireEnv } from '../utils/env' import { SafeMultiChainSigAccountV1 as SafeAccount, AllowanceModule, Erc7677Paymaster, } from "abstractionkit"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { createPublicClient, http, parseAbi } from "viem"; const ERC20_ABI = parseAbi(["function balanceOf(address owner) view returns (uint256)"]); const allowanceTransferAmount = 1n; async function main(): Promise { const { chainId, bundlerUrl, nodeUrl, paymasterUrl, sponsorshipPolicyId } = loadEnv() const allowanceToken = requireEnv('TOKEN_ADDRESS') // source account owner const { publicAddress: sourceOwnerPublicAddress, privateKey: sourceOwnerPrivateKey } = getOrCreateOwner() // delegate account owner const delegateOwnerPrivateKey = generatePrivateKey(); const delegateOwner = privateKeyToAccount(delegateOwnerPrivateKey); const delegateOwnerPublicAddress = delegateOwner.address; // source safe account const sourceSafeAccount = SafeAccount.initializeNewAccount( [sourceOwnerPublicAddress], { c2Nonce: 0n } ); const client = createPublicClient({ transport: http(nodeUrl) }); const sourceSafeAccountBalance = await client.readContract({ address: allowanceToken as `0x${string}`, abi: ERC20_ABI, functionName: 'balanceOf', args: [sourceSafeAccount.accountAddress as `0x${string}`], }); if (sourceSafeAccountBalance < allowanceTransferAmount) { console.log(`Please fund the Safe Account with at least ${allowanceTransferAmount} token first`); console.log("Safe Account Address: " + sourceSafeAccount.accountAddress); console.log("Token: ", allowanceToken); console.log("Network Chain ID ", chainId.toString()); return; } // delegate safe account const delegateSafeAccount = SafeAccount.initializeNewAccount( [delegateOwnerPublicAddress], ); const allowanceModule = new AllowanceModule(); const setupTransactions = []; const allowanceModuleEnabled = await sourceSafeAccount.isModuleEnabled( nodeUrl, allowanceModule.moduleAddress, ); // Need to be enabled only once. Skipping this on reruns keeps the example idempotent. if (!allowanceModuleEnabled) { setupTransactions.push( allowanceModule.createEnableModuleMetaTransaction(sourceSafeAccount.accountAddress) ); } const addDelegateMetaTransaction = allowanceModule.createAddDelegateMetaTransaction(delegateSafeAccount.accountAddress); setupTransactions.push(addDelegateMetaTransaction); const setAllowanceMetaTransaction = allowanceModule.createRecurringAllowanceMetaTransaction( delegateSafeAccount.accountAddress, // The address of the delegate to whom the recurring allowance is given. allowanceToken, // The address of the token for which the allowance is set. allowanceTransferAmount, // The amount of the token allowed for the delegate. 3n, // The time period (in minutes) after which the allowance resets. 0n, // The delay in minutes before the allowance can be used. ); let setAllowanceUserOp = await sourceSafeAccount.createUserOperation( [...setupTransactions, setAllowanceMetaTransaction], nodeUrl, bundlerUrl, ); const paymaster = new Erc7677Paymaster(paymasterUrl); const { userOperation: sponsoredSetAllowanceUserOp } = await paymaster.createPaymasterUserOperation( ``` -------------------------------- ### Get Supported ERC-20 Tokens Response Source: https://docs.candide.dev/wallet/paymaster/rpc-methods-v2 Example response for pm_supportedERC20Tokens, detailing paymaster metadata and a list of supported ERC-20 tokens with their properties. ```json { "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": [ { "symbol": "DAI", "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "decimal": "0x12", "fee": "0x0", "exchangeRate": "0x0ddeb609310c89b1" } ] } } ``` -------------------------------- ### Initialize New Safe Account Source: https://docs.candide.dev/wallet/abstractionkit/safe-account Initialize a new Safe Account instance and retrieve its address. This example demonstrates the basic setup for creating a Safe Account. ```typescript const ownerPublicAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // Safe owner pub address const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]); const accountAddress = smartAccount.accountAddress; ``` -------------------------------- ### Create Project Directory and Initialize TypeScript Source: https://docs.candide.dev/wallet/guides/getting-started-eip-7702 Sets up a new project directory and initializes TypeScript for the project. This is the first step in preparing your environment. ```bash mkdir candide-eip7702-upgrade-eoa cd candide-eip7702-upgrade-eoa npx tsc --init ``` -------------------------------- ### Complete Runnable Example for Multisig Source: https://docs.candide.dev/wallet/guides/multisig This example shows the full process of creating a 2/2 multisig account, defining minting transactions, creating a UserOperation, signing it with multiple private keys, and submitting it for execution. It requires environment variables to be set for chain ID, bundler URL, RPC provider, and owner private keys. ```typescript 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) ``` -------------------------------- ### Setup Guardian with Magic and Ethers Source: https://docs.candide.dev/wallet/guides/magic Initialize Magic, get a guardian signer using ethers BrowserProvider, and create transactions to enable the Social Recovery Module and add a guardian. ```typescript 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, ); ``` -------------------------------- ### Get Paymaster Data with Public and Private Fallback Source: https://docs.candide.dev/wallet/guides/send-gasless-tx This code attempts to sponsor a user operation using public gas policies first, falling back to a private policy if necessary. Ensure the abstractionkit library is installed and environment variables are set. ```typescript import { Erc7677Paymaster } from "abstractionkit"; const paymasterRPC = process.env.PAYMASTER_RPC 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; ``` -------------------------------- ### Test Project Setup Source: https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started Executes the main script using ts-node to test the project setup and display the owner and Safe account addresses. ```bash npx ts-node index.ts ``` -------------------------------- ### Install Voltaire Dependencies with Poetry Source: https://docs.candide.dev/wallet/bundler/installation After installing Poetry, run this command to install the project's dependencies. ```bash poetry install ``` -------------------------------- ### Install Project Dependencies Source: https://docs.candide.dev/wallet/guides/getting-started-eip-7702 Installs necessary development and runtime dependencies including TypeScript, abstractionkit, dotenv, and ethers. Ensure you have Node.js and a package manager installed. ```bash npm i typescript --save-dev npm i abstractionkit dotenv ethers ``` -------------------------------- ### Create and Sign User Operation with Viem Source: https://docs.candide.dev/wallet/abstractionkit/safe-account-v2 Example demonstrating how to initialize a Safe account, create a user operation, and sign it using viem. Requires setting up owner private keys and chain ID. ```typescript 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; ``` -------------------------------- ### Example UserOperation Hash Source: https://docs.candide.dev/wallet/abstractionkit/bundler This is an example of a UserOperation hash returned by the bundler. ```plaintext '0x0ff052095987556f476c6e6b7cdff65cc6191d2cede50a109fe6977c3287fc9a' ``` -------------------------------- ### Sign UserOperation with Viem Local Account (Full Example) Source: https://docs.candide.dev/wallet/abstractionkit/external-signers A complete example demonstrating the use of `fromViem` to sign a UserOperation with a viem local account and a Safe account. ```typescript 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, ); ``` -------------------------------- ### Example pm_chainId Request Source: https://docs.candide.dev/wallet/paymaster/rpc-methods-v2 An example of a complete JSON-RPC request for the pm_chainId method. ```json { "jsonrpc": "2.0", "id": 0, "method": "pm_chainId", "params": [] } ``` -------------------------------- ### Install Dependencies with Yarn Source: https://docs.candide.dev/wallet/plugins/recover-account-candide-guardian Install the necessary packages for account recovery using yarn. ```bash yarn add abstractionkit safe-recovery-service-sdk viem ``` -------------------------------- ### Full Example: Minting NFTs with Sponsored Transactions Source: https://docs.candide.dev/wallet/guides/getting-started This comprehensive example demonstrates the end-to-end process of creating a smart account, batching NFT minting transactions, sponsoring them using an ERC-7677 paymaster, signing, and sending the user operation. ```javascript 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() ``` -------------------------------- ### Initialize Main Script with Dotenv Source: https://docs.candide.dev/wallet/guides/chain-abstraction-getting-started Sets up the main script file and initializes dotenv to load environment variables. ```typescript import * as dotenv from 'dotenv' async function main(): Promise { dotenv.config() // We'll build our multi-chain logic here } main().catch(console.error) ``` -------------------------------- ### Example Response Signature Source: https://docs.candide.dev/wallet/abstractionkit/safe-account-v2 This is an example of the signature field returned after signing a user operation. ```plaintext 0x00000000000000000000000041c6297bd9573e8d979a272db4f6576a98f639a7e6874055a627769401dc46d01143551ccaa473364ace4340ec395c546dccb725e1eac2639ecef443d229f0071b ``` -------------------------------- ### Setup Guardian with Magic and Viem Source: https://docs.candide.dev/wallet/guides/magic Initialize Magic, create a wallet client using Viem's custom transport, and generate transactions to enable the Social Recovery Module and add a guardian. ```typescript 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, ); ``` -------------------------------- ### Install Poetry for Development Source: https://docs.candide.dev/wallet/bundler/installation Use this command to install Poetry, a Python dependency management tool, on Ubuntu. ```bash curl -sSL https://install.python-poetry.org | python3 - ``` -------------------------------- ### Create and Sign User Operation with Viem Source: https://docs.candide.dev/wallet/abstractionkit/safe-account-v3 Example demonstrating how to initialize a Safe Account, create a user operation, and sign it using viem. This snippet shows the process of using viem's privateKeyToAccount and sign methods to generate and format the signature. ```typescript 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; ``` -------------------------------- ### Initialize Smart Account with Viem Source: https://docs.candide.dev/wallet/guides/authentication This snippet shows how to initialize a smart account using Viem. It creates a wallet client from the browser's Ethereum provider and uses the first obtained signer address to set the smart account owner. ```typescript 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]); ``` -------------------------------- ### Install AbstractionKit with yarn Source: https://docs.candide.dev/wallet/abstractionkit/introduction Install the AbstractionKit library using yarn. This is an alternative package manager for Node.js. ```bash yarn add abstractionkit ``` -------------------------------- ### Example User Operation EIP-712 Hash Response Source: https://docs.candide.dev/wallet/abstractionkit/safe-account-v3 This is an example of the output hash generated by the `getUserOperationEip712Hash` function. ```plaintext 0xec030c825b12b398c10f1b552004e43ec753fdf001e1c1daa1ceffe4f7ff5056 ``` -------------------------------- ### Complete ERC20 Gas Payment Example Source: https://docs.candide.dev/wallet/guides/pay-gas-in-erc20 This example shows the full process of setting up a smart account, creating transactions, using an ERC20 token for gas payment via a paymaster, signing, and submitting the UserOperation. ```typescript 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_RPC 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) ```