Skip to Content
⚙️ Core SDKBundler & UserOperations

Bundler & UserOperations

Submit, estimate, and track ERC-4337 UserOperations through the low-level bundler API.

The bundler module provides direct access to UserOperation submission, gas estimation, and fee calculation. Most users should prefer the higher-level ServerWallet.sendUserOperation() API — use this module when you need fine-grained control over gas parameters or fee strategies.

Setup

Configure the public client for on-chain reads (fee estimation, nonce queries):

import { setPublicClient } from '@embarkai/core' import { createPublicClient, http } from 'viem' const client = createPublicClient({ transport: http('https://rpc.lumia.org'), }) setPublicClient(client)

UserOperationV07 Type

The module uses the ERC-4337 v0.7 UserOperation format:

import type { UserOperationV07 } from '@embarkai/core' const userOp: UserOperationV07 = { sender: '0xYourSmartAccountAddress', nonce: 0n, initCode: '0x', callData: '0x...', callGasLimit: 500000n, verificationGasLimit: 500000n, preVerificationGas: 100000n, maxFeePerGas: 1000000000n, maxPriorityFeePerGas: 100000000n, paymasterAndData: '0x', signature: '0x', }

Submitting UserOperations

Basic Submission

import { sendUserOperationRaw, ENTRYPOINT_V07, } from '@embarkai/core' const userOpHash = await sendUserOperationRaw( bundlerClient, ENTRYPOINT_V07, packedUserOp, )

Submission with Retry

Automatically retries on transient network errors:

import { sendUserOperationWithRetry, ENTRYPOINT_V07 } from '@embarkai/core' const userOpHash = await sendUserOperationWithRetry( bundlerClient, ENTRYPOINT_V07, packedUserOp, 3, // maxRetries (default: 3) )

Get Receipt

import { getUserOperationReceipt } from '@embarkai/core' const receipt = await getUserOperationReceipt(userOpHash) if (receipt?.success) { console.log('TX:', receipt.transactionHash) }

Gas Estimation

Standard Estimation

import { estimateUserOperationGas } from '@embarkai/core' const gasEstimate = await estimateUserOperationGas(userOp) // gasEstimate: { callGasLimit, verificationGasLimit, preVerificationGas }

With Existing Signature

Skip dummy signature injection when your UserOp is already signed:

import { estimateUserOperationGasWithSignature } from '@embarkai/core' const gasEstimate = await estimateUserOperationGasWithSignature(signedUserOp)

With Dynamic Fees

Estimates gas and applies current fee calculations in one call:

import { estimateUserOperationGasWithDynamicFees } from '@embarkai/core' const result = await estimateUserOperationGasWithDynamicFees(userOp) // Returns gas limits with maxFeePerGas and maxPriorityFeePerGas applied

Simulation

Dry-run a UserOperation to check for revert reasons before submitting:

import { simulateUserOperation, validateUserOperation } from '@embarkai/core' const simResult = await simulateUserOperation(userOp) console.log('Success:', simResult.success) const validationResult = await validateUserOperation(userOp) console.log('Valid:', validationResult.success)

Fee Calculation

The module offers multiple fee strategies for different use cases:

StrategyFunctionDescription
DynamiccalculateDynamicFees()EIP-1559 fees based on current base fee + priority fee
LegacycalculateLegacyFees()Pre-EIP-1559 gas price calculation
FastcalculateFastFees()Higher priority fee for faster inclusion
EconomycalculateEconomyFees()Lower fees, longer confirmation time

Fee Strategy Comparison

StrategyPriority Fee MultiplierBase Fee BufferBest For
Economy1x+10%Non-urgent background operations
Dynamic (Standard)1.5x+20%Most transactions
Fast2x+30%Time-sensitive operations

Usage

import { calculateDynamicFees, calculateFastFees, calculateEconomyFees, getCurrentBaseFee, getFeeEstimates, } from '@embarkai/core' // Get current base fee const baseFee = await getCurrentBaseFee() // Calculate fees with a specific strategy const dynamicFees = await calculateDynamicFees() const fastFees = await calculateFastFees() const economyFees = await calculateEconomyFees() // Get all strategies at once const estimates = await getFeeEstimates() console.log('Economy:', estimates.economy.maxFeePerGas) console.log('Standard:', estimates.standard.maxFeePerGas) console.log('Fast:', estimates.fast.maxFeePerGas)

Custom Fee Config

Override defaults with DEFAULT_FEE_CONFIG or CONSERVATIVE_FEE_CONFIG:

import { DEFAULT_FEE_CONFIG, CONSERVATIVE_FEE_CONFIG } from '@embarkai/core' // DEFAULT_FEE_CONFIG: balanced defaults // CONSERVATIVE_FEE_CONFIG: higher buffers for congested networks

Packing UserOperations

Convert a v0.7 UserOperation into the packed format required by the EntryPoint contract:

import { packUserOperationV07 } from '@embarkai/core' const packed = packUserOperationV07(userOp) // packed: PackedUserOperationV07 ready for on-chain submission

Utility Functions

import { DUMMY_SIGNATURE, createDummyUserOperation, createUserOperationWithDynamicFees, isNetworkError, } from '@embarkai/core' // Create a placeholder UserOp for gas estimation const dummyOp = createDummyUserOperation(senderAddress) // Create a UserOp with current dynamic fees pre-filled const userOp = await createUserOperationWithDynamicFees(baseUserOp) // Check if an error is a retryable network issue if (isNetworkError(error)) { // safe to retry }

Usage with ServerWallet

In most cases, ServerWallet handles all bundler interactions internally:

import { createServerWalletManager, MemoryKeyshareStorage } from '@embarkai/core' const manager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 994873017, storage: new MemoryKeyshareStorage(), }) const wallet = await manager.getWallet('treasury') const hash = await wallet.sendUserOperation( '0xRecipient', '1000000000000000000', '0x', 'fast', // uses calculateFastFees internally )

Next Steps

Last updated on