npm stats
  • Search
  • About
  • Repo
  • Sponsor
  • more
    • Search
    • About
    • Repo
    • Sponsor

Made by Antonio Ramirez

@tetherto/wdk-wallet-evm-7702-gasless

1.0.0-beta.1

@GitHub Actions

npmHomeRepoSnykSocket
Downloads:1
$ npm install @tetherto/wdk-wallet-evm-7702-gasless
DailyWeeklyMonthlyYearly

@tetherto/wdk-wallet-evm-7702-gasless

Note: This package is currently in beta. Please test thoroughly in development environments before using in production.

A simple and secure package to manage gasless EIP-7702 wallets for EVM-compatible blockchains. This package abstracts all EIP-7702 delegation and ERC-4337 UserOperation complexity behind a simple API — call transfer() and delegation, UserOp signing, paymaster sponsorship, and token approvals all happen internally.

About WDK

This module is part of the WDK (Wallet Development Kit) project, which empowers developers to build secure, non-custodial wallets with unified blockchain access, stateless architecture, and complete user control.

For detailed documentation about the complete WDK ecosystem, visit docs.wallet.tether.io.

Features

  • EIP-7702 Delegation: EOA becomes a smart account via delegation — no Safe contract, no address prediction
  • Gasless Transactions: Full paymaster integration for sponsored or ERC-20 token gas payment
  • Auto Approval: Paymaster token allowance is managed automatically, including USDT mainnet reset handling
  • Provider-Agnostic: Works with any ERC-4337 bundler/paymaster (Pimlico, Candide, etc.)
  • Failover Providers: Pass an array of provider URLs or EIP-1193 instances to enable automatic round-robin failover
  • Bare Runtime: Supports both Node.js and Bare runtime
  • EVM Derivation Paths: Support for BIP-44 standard derivation paths for Ethereum (m/44'/60')
  • Multi-Account Management: Create and manage multiple wallets from a single seed phrase
  • ERC20 Support: Query native token and ERC20 token balances, transfers, and approvals
  • Automatic Delegation Lifecycle: Delegation is checked and signed automatically per operation

Installation

npm install @tetherto/wdk-wallet-evm-7702-gasless

Quick Start

Creating a Wallet (Sponsored Mode)

import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless'

const wallet = new WalletManagerEvm7702Gasless(seedPhrase, {
  provider: 'https://rpc.mevblocker.io/fast',
  delegationAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B',
  bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY',
  isSponsored: true,
  sponsorshipPolicyId: 'sp_my_policy'
})

const account = await wallet.getAccount(0)
const address = await account.getAddress() // Returns the EOA address directly

Creating a Wallet (Paymaster Token Mode)

const wallet = new WalletManagerEvm7702Gasless(seedPhrase, {
  provider: 'https://rpc.mevblocker.io/fast',
  delegationAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B',
  bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY',
  paymasterAddress: '0x888888888888Ec68A58AB8094Cc1AD20Ba3D2402',
  paymasterToken: { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, // USDT
  transferMaxFee: 100000000000000n
})

Using Candide (Separate Paymaster Endpoint)

Candide uses separate bundler and paymaster URLs. Use the paymasterUrl field:

const wallet = new WalletManagerEvm7702Gasless(seedPhrase, {
  provider: 'https://rpc.mevblocker.io/fast',
  delegationAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B',
  bundlerUrl: 'https://api.candide.dev/bundler/v3/1/YOUR_KEY',
  paymasterUrl: 'https://api.candide.dev/paymaster/v3/1/YOUR_KEY',
  isSponsored: true,
  sponsorshipPolicyId: 'your_policy_id'
})

Wrapping an Existing WalletAccountEvm

import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'
import { WalletAccountEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless'

const evmAccount = new WalletAccountEvm(seed, "0'/0/0", { provider: '...' })

const gaslessAccount = new WalletAccountEvm7702Gasless(evmAccount, {
  provider: '...',
  delegationAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B',
  bundlerUrl: '...',
  isSponsored: true
})

Managing Multiple Accounts

const account0 = await wallet.getAccount(0)
const account1 = await wallet.getAccount(1)

// Or by custom derivation path (full path: m/44'/60'/0'/0/5)
const customAccount = await wallet.getAccountByPath("0'/0/5")

Checking Balances

// Native token balance (in wei)
const balance = await account.getBalance()

// ERC20 token balance
const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7')

// Multiple token balances
const balances = await account.getTokenBalances([token1, token2])

Token Transfers

// Transfer ERC20 tokens — delegation + UserOp + paymaster handled internally
const result = await account.transfer({
  token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
  recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
  amount: 1000000n // 1 USDT (6 decimals)
})
console.log('UserOperation hash:', result.hash)
console.log('Fee:', result.fee)

// Quote transfer fee before sending
const quote = await account.quoteTransfer({
  token: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
  recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
  amount: 1000000n
})
console.log('Estimated fee:', quote.fee)

Sending Transactions

// Send a raw transaction via UserOperation
const result = await account.sendTransaction({
  to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
  value: 1n, // 1 wei
  data: '0x'
})

// Batch transactions
const batchResult = await account.sendTransaction([
  { to: '0x...', value: 0n, data: '0x...' },
  { to: '0x...', value: 0n, data: '0x...' }
])

// Quote fee
const quote = await account.quoteSendTransaction({
  to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
  value: 0n,
  data: '0x'
})

Token Approvals

await account.approve({
  token: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
  spender: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
  amount: 1000000n
})

Transaction Receipts

// Get full transaction receipt from a UserOp hash
const receipt = await account.getTransactionReceipt(userOpHash)

// Get the raw UserOperation receipt
const userOpReceipt = await account.getUserOperationReceipt(userOpHash)

Message Signing and Verification

// Sign a message
const signature = await account.sign('Hello, EIP-7702!')

// Verify a signature
const isValid = await account.verify('Hello, EIP-7702!', signature)

// Sign typed data (EIP-712)
const typedSig = await account.signTypedData({
  domain: { name: 'MyDApp', version: '1', chainId: 1, verifyingContract: '0x...' },
  types: { Transfer: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }] },
  message: { to: '0x...', amount: '1000000' }
})

Fee Management

const feeRates = await wallet.getFeeRates()
console.log('Normal fee rate:', feeRates.normal, 'wei')
console.log('Fast fee rate:', feeRates.fast, 'wei')

Memory Management

account.dispose() // Clear private keys from memory
wallet.dispose()  // Dispose all accounts

Configuration Reference

Common Fields (always required)

FieldTypeDescription
providerstring | Eip1193Provider | (string | Eip1193Provider)[]RPC endpoint URL, EIP-1193 provider instance, or failover list mixing both formats
bundlerUrlstringURL of the ERC-4337 bundler service
delegationAddressstringAddress of the smart account implementation to delegate to

Optional Common Fields

FieldTypeDescription
paymasterUrlstringURL of the paymaster service, if different from bundlerUrl (e.g. Candide)
retriesnumberAdditional retry attempts for provider failover arrays. Total attempts are 1 + retries. Defaults to 3

Sponsored Mode

FieldTypeDescription
isSponsoredtrueEnables sponsorship
sponsorshipPolicyIdstring (optional)Sponsorship policy ID for the paymaster provider

Paymaster Token Mode

FieldTypeDescription
paymasterAddressstring (optional)Pin on the paymaster smart contract address. When omitted, it is derived from the paymaster RPC (pm_supportedERC20Tokens for Candide, pimlico_getTokenQuotes for Pimlico)
paymasterToken{ address: string }ERC-20 token used for gas payment
transferMaxFeenumber | bigint (optional)Maximum fee for transfer operations

API Reference

WalletManagerEvm7702Gasless

The main class for managing EIP-7702 gasless wallets. Extends WalletManager from @tetherto/wdk-wallet.

MethodDescriptionReturns
getAccount(index)Returns a wallet account at the specified indexPromise<WalletAccountEvm7702Gasless>
getAccountByPath(path)Returns a wallet account at the specified BIP-44 derivation pathPromise<WalletAccountEvm7702Gasless>
getFeeRates()Returns current fee ratesPromise<{normal: bigint, fast: bigint}>
dispose()Disposes all wallet accounts, clearing private keys from memoryvoid

WalletAccountEvm7702Gasless

Individual gasless wallet account. Extends WalletAccountReadOnlyEvm7702Gasless, implements IWalletAccount.

Constructor overloads:

  • new WalletAccountEvm7702Gasless(seed, path, config) — standard BIP-44 derivation
  • new WalletAccountEvm7702Gasless(walletAccountEvm, config) — wrap an existing WalletAccountEvm
MethodDescriptionReturns
getAddress()Returns the EOA addressPromise<string>
sign(message)Signs a messagePromise<string>
signTypedData(typedData)Signs typed data (EIP-712)Promise<string>
verify(message, signature)Verifies a message signaturePromise<boolean>
verifyTypedData(typedData, signature)Verifies a typed data signaturePromise<boolean>
sendTransaction(tx, config?)Sends a transaction via UserOperationPromise<{hash, fee}>
quoteSendTransaction(tx, config?)Estimates the fee for a UserOperationPromise<{fee}>
transfer(options, config?)Transfers ERC20 tokens via UserOperationPromise<{hash, fee}>
quoteTransfer(options, config?)Estimates the fee for an ERC20 transferPromise<{fee}>
approve(options)Approves a spender for a token amountPromise<{hash, fee}>
getBalance()Returns the native token balance (in wei)Promise<bigint>
getTokenBalance(tokenAddress)Returns the balance of a specific ERC20 tokenPromise<bigint>
getTokenBalances(tokenAddresses)Returns balances for multiple ERC20 tokensPromise<Record<string, bigint>>
getAllowance(token, spender)Returns the current allowancePromise<bigint>
getTransactionReceipt(hash)Returns a transaction receipt from a UserOp hashPromise<EvmTransactionReceipt | null>
getUserOperationReceipt(hash)Returns a UserOperation receiptPromise<UserOperationReceipt | null>
toReadOnlyAccount()Returns a read-only copy of the accountPromise<WalletAccountReadOnlyEvm7702Gasless>
dispose()Disposes the wallet accountvoid
PropertyTypeDescription
indexnumberThe derivation path's index
pathstringThe full derivation path
keyPairKeyPairThe account's key pair

WalletAccountReadOnlyEvm7702Gasless

Read-only EIP-7702 gasless wallet account. Can query balances and estimate fees but cannot sign or send transactions.

MethodDescriptionReturns
getAddress()Returns the EOA addressPromise<string>
getBalance()Returns the native token balance (in wei)Promise<bigint>
getTokenBalance(tokenAddress)Returns the balance of a specific ERC20 tokenPromise<bigint>
getTokenBalances(tokenAddresses)Returns balances for multiple ERC20 tokensPromise<Record<string, bigint>>
getPaymasterTokenBalance()Returns the paymaster token balancePromise<bigint>
quoteSendTransaction(tx, config?)Estimates the fee for a UserOperationPromise<{fee}>
quoteTransfer(options, config?)Estimates the fee for an ERC20 transferPromise<{fee}>
getAllowance(token, spender)Returns the current allowancePromise<bigint>
getTransactionReceipt(hash)Returns a transaction receipt from a UserOp hashPromise<EvmTransactionReceipt | null>
getUserOperationReceipt(hash)Returns a UserOperation receiptPromise<UserOperationReceipt | null>
verify(message, signature)Verifies a message signaturePromise<boolean>
verifyTypedData(typedData, signature)Verifies a typed data signaturePromise<boolean>

Key Differences from ERC-4337 Module

AspectERC-4337 (wdk-wallet-evm-erc-4337)ERC-7702 Gasless (this module)
Smart AccountSafe contract (predicted address)EOA delegated via EIP-7702
getAddress()Returns Safe contract addressReturns EOA address directly
Underlying Library@tetherto/wdk-safe-relay-kitabstractionkit
Address PredictionpredictSafeAddress() requiredNo prediction needed
Gas PaymentNative coins, paymaster token, sponsoredSponsored or paymaster token
Token ApprovalManual amountToApproveAutomatic (including USDT reset)
Chain RequirementAny EVM with ERC-4337Requires Pectra-activated chains (EIP-7702)

Supported Networks

This package works with EVM-compatible blockchains that support both EIP-7702 (Pectra upgrade) and ERC-4337:

  • Ethereum Mainnet (post-Pectra)
  • Ethereum Sepolia (testnet)
  • Other Pectra-activated EVM chains

Bundler/Paymaster Compatibility

This module uses standard ERC-4337 bundler RPCs (eth_sendUserOperation, eth_estimateUserOperationGas) and ERC-7677 paymaster RPCs (pm_getPaymasterStubData, pm_getPaymasterData). Not all providers support generic EIP-7702 SimpleAccount delegation — many lock you to their own smart account implementation.

Tested Providers

ProviderSponsoredPaymaster TokenStatus
PimlicoYesYesFully working — all flows tested on mainnet and Sepolia
CandideYesYesWorking with known limitations (see below)

Not Compatible

ProviderReason
AlchemyLocked to Modular Account v2 — rejects all other delegation addresses
ZeroDevLocked to Kernel smart account — requires createKernelAccount, not standard toSimpleSmartAccount
GelatoProprietary Smart Wallet SDK — no standard bundler RPCs exposed

Known Candide Limitations

Candide's Voltaire bundler has known gas estimation issues affecting EIP-7702 accounts. These are bundler-side bugs, not module issues — the same operations work correctly on Pimlico.

1. callGasLimit underestimation for USDT on Ethereum mainnet

The bundler returns ~27k callGasLimit for USDT transfers, but USDT's non-standard implementation needs ~40k+. The UserOp inner call reverts with empty reason data (out of gas). Standard ERC-20 tokens (USDC, DAI) are not affected.

2. verificationGasLimit underestimation for ERC-20 paymaster

The bundler's gas estimate doesn't meet its own validation margin. Error: "verificationGas should have extra 2000 gas, has only -15791". Observed on Sepolia with the ERC-20 token paymaster flow.

Related Candide GitHub issues:

  • abstractionkit #57: Gas estimation ran before approval was prepended, producing wrong estimates
  • abstractionkit #78 (open): Simple7702Account ignoring paymaster fields in overrides
  • voltaire #33: Gas estimation failed for IAccountExecute accounts on EntryPoint v0.8

Recommendation: Use Pimlico for production deployments until Candide resolves these estimation issues. If using Candide, the paymaster-token flow on Ethereum mainnet works reliably; the sponsored flow works for ETH transfers and standard ERC-20 tokens but may fail for USDT.

Security Considerations

  • Seed Phrase Security: Always store your seed phrase securely and never share it
  • Private Key Management: The package handles private keys internally via memory-safe buffers (Uint8Array) that are zeroed on dispose()
  • Memory Cleanup: Use the dispose() method to clear private keys from memory when done
  • Fee Limits: Set transferMaxFee to prevent excessive transaction fees
  • Delegation Awareness: The EOA delegates execution to a smart account implementation — verify the delegationAddress is trusted and audited
  • Bundler Security: Use trusted bundler services and validate UserOperation responses
  • Contract Interactions: Verify contract addresses and token decimals before transfers

Development

# Install dependencies
npm install

# Lint code
npm run lint

# Fix linting issues
npm run lint:fix

# Build TypeScript definitions
npm run build:types

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For support, please open an issue on the GitHub repository.