@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
Quick Start
Creating a Wallet (Sponsored Mode)
Creating a Wallet (Paymaster Token Mode)
Using Candide
Candide serves the bundler and paymaster from a single unified URL, so you only need bundlerUrl — the paymaster is reached at the same endpoint. The chain is selected by its chain ID in the path. Use the public endpoint (rate-limited, no key required) or an authenticated endpoint with an API key from the dashboard:
- Public:
https://api.candide.dev/public/v3/{chainId}
- Authenticated:
https://api.candide.dev/api/v3/{chainId}/{apiKey}
Wrapping an Existing WalletAccountEvm
Managing Multiple Accounts
Checking Balances
Token Transfers
Sending Transactions
Concurrent / Parallel Sends (nonce lanes)
By default every send uses the account's key-0 nonce, so it is sequential — a second send fired before the first is mined would collide on the nonce. To send independent operations concurrently, put each in its own nonce lane (an ERC-4337 two-dimensional nonce: a 192-bit key with its own sequence). Ops in different keys have no ordering constraint and validate independently, so they can be submitted at the same time.
Both options can also be set at construction (new WalletManagerEvm7702Gasless(seed, { ..., parallel: true })) and overridden per call. Precedence: nonceKey > parallel > default (key 0).
Notes:
- Requires a bundler that accepts parallel keys (Pimlico and Candide both do). There is no SDK-side lane limit; respect your bundler's cap (e.g. Pimlico allows up to 100 parallel).
- Per-sender mempool cap (≈4). Per ERC-7562, a standard bundler mempool holds at most 4 in-flight UserOperations from a single sender at once (
SAME_SENDER_MEMPOOL_COUNT = 4). Firing more than 4 lanes concurrently may be rejected until earlier ops are mined. This is a mempool validation limit, separate from parallel-key support, and can be raised by the bundler operator if needed.
- Delegate first. The first UserOperation from a fresh account carries the EIP-7702 delegation authorization, which is tied to the account's EOA nonce. Let one send land (delegating the account) before firing concurrent lanes, so parallel sends don't race on the initial delegation.
parallel: true mints a new EntryPoint nonce slot per send (a one-time gas cost per lane, paid by the paymaster; permanent state). For repeated parallel workloads, reuse a fixed set of lanes with nonceKey labels instead of a fresh key every time.
- A single lane is still sequential. Two concurrent sends sharing the same
nonceKey collide on the sequence: the second is typically still accepted by the bundler and returns a hash, yet can never mine — its hash never resolves to a receipt and no error is thrown (a silent phantom hash). To run operations concurrently, give each its own lane (parallel: true, or distinct nonceKeys). If operations must share a lane, either await each send's receipt before firing the next, or batch them into a single UserOperation by passing an array to sendTransaction([tx1, tx2, ...]) — batched calls execute atomically, in order, under one nonce.
Token Approvals
Transaction Receipts
Message Signing and Verification
Fee Management
Memory Management
Configuration Reference
Common Fields (always required)
| Field | Type | Description |
|---|
provider | string | Eip1193Provider | (string | Eip1193Provider)[] | RPC endpoint URL, EIP-1193 provider instance, or failover list mixing both formats |
bundlerUrl | string | URL of the ERC-4337 bundler service |
delegationAddress | string | Address of the smart account implementation to delegate to |
Optional Common Fields
| Field | Type | Description |
|---|
paymasterUrl | string | URL of the paymaster service, when it differs from bundlerUrl. Omit when one URL serves both the bundler and paymaster (e.g. Candide, Pimlico) |
retries | number | Additional retry attempts for provider failover arrays. Total attempts are 1 + retries. Defaults to 3 |
Sponsored Mode
| Field | Type | Description |
|---|
isSponsored | true | Enables sponsorship |
sponsorshipPolicyId | string (optional) | Sponsorship policy ID for the paymaster provider |
Paymaster Token Mode
| Field | Type | Description |
|---|
paymasterAddress | string (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 |
transferMaxFee | number | 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.
| Method | Description | Returns |
|---|
getAccount(index) | Returns a wallet account at the specified index | Promise<WalletAccountEvm7702Gasless> |
getAccountByPath(path) | Returns a wallet account at the specified BIP-44 derivation path | Promise<WalletAccountEvm7702Gasless> |
getFeeRates() | Returns current fee rates | Promise<{normal: bigint, fast: bigint}> |
dispose() | Disposes all wallet accounts, clearing private keys from memory | void |
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
| Method | Description | Returns |
|---|
getAddress() | Returns the EOA address | Promise<string> |
sign(message) | Signs a message | Promise<string> |
signTypedData(typedData) | Signs typed data (EIP-712) | Promise<string> |
verify(message, signature) | Verifies a message signature | Promise<boolean> |
verifyTypedData(typedData, signature) | Verifies a typed data signature | Promise<boolean> |
sendTransaction(tx, config?) | Sends a transaction via UserOperation | Promise<{hash, fee}> |
quoteSendTransaction(tx, config?) | Estimates the fee for a UserOperation | Promise<{fee}> |
transfer(options, config?) | Transfers ERC20 tokens via UserOperation | Promise<{hash, fee}> |
quoteTransfer(options, config?) | Estimates the fee for an ERC20 transfer | Promise<{fee}> |
approve(options) | Approves a spender for a token amount | Promise<{hash, fee}> |
getBalance() | Returns the native token balance (in wei) | Promise<bigint> |
getTokenBalance(tokenAddress) | Returns the balance of a specific ERC20 token | Promise<bigint> |
getTokenBalances(tokenAddresses) | Returns balances for multiple ERC20 tokens | Promise<Record<string, bigint>> |
getAllowance(token, spender) | Returns the current allowance | Promise<bigint> |
getTransactionReceipt(hash) | Returns a transaction receipt from a UserOp hash | Promise<EvmTransactionReceipt | null> |
getUserOperationReceipt(hash) |
| Property | Type | Description |
|---|
index | number | The derivation path's index |
path | string | The full derivation path |
keyPair | KeyPair | The account's key pair |
WalletAccountReadOnlyEvm7702Gasless
Read-only EIP-7702 gasless wallet account. Can query balances and estimate fees but cannot sign or send transactions.
| Method | Description | Returns |
|---|
getAddress() | Returns the EOA address | Promise<string> |
getBalance() | Returns the native token balance (in wei) | Promise<bigint> |
getTokenBalance(tokenAddress) | Returns the balance of a specific ERC20 token | Promise<bigint> |
getTokenBalances(tokenAddresses) | Returns balances for multiple ERC20 tokens | Promise<Record<string, bigint>> |
getPaymasterTokenBalance() | Returns the paymaster token balance | Promise<bigint> |
quoteSendTransaction(tx, config?) | Estimates the fee for a UserOperation | Promise<{fee}> |
quoteTransfer(options, config?) | Estimates the fee for an ERC20 transfer | Promise<{fee}> |
getAllowance(token, spender) | Returns the current allowance | Promise<bigint> |
getTransactionReceipt(hash) | Returns a transaction receipt from a UserOp hash | Promise<EvmTransactionReceipt | null> |
getUserOperationReceipt(hash) | Returns a UserOperation receipt | Promise<UserOperationReceipt | null> |
verify(message, signature) | Verifies a message signature | Promise<boolean> |
verifyTypedData(typedData, signature) | Verifies a typed data signature | Promise<boolean> |
Key Differences from ERC-4337 Module
| Aspect | ERC-4337 (wdk-wallet-evm-erc-4337) | ERC-7702 Gasless (this module) |
|---|
| Smart Account | Safe contract (predicted address) | EOA delegated via EIP-7702 |
getAddress() | Returns Safe contract address | Returns EOA address directly |
| Underlying Library | @tetherto/wdk-safe-relay-kit | abstractionkit |
| Address Prediction | predictSafeAddress() required | No prediction needed |
| Gas Payment | Native coins, paymaster token, sponsored | Sponsored or paymaster token |
| Token Approval | Manual amountToApprove | Automatic (including USDT reset) |
| Chain Requirement | Any EVM with ERC-4337 | Requires 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
| Provider | Sponsored | Paymaster Token | Status |
|---|
| Pimlico | Yes | Yes | Fully working — all flows tested on mainnet and Sepolia |
| Candide | Yes | Yes | Fully working — earlier gas-estimation issues fixed upstream (re-validated 2026-08) |
Not Compatible
| Provider | Reason |
|---|
| Alchemy | Locked to Modular Account v2 — rejects all other delegation addresses |
| ZeroDev | Locked to Kernel smart account — requires createKernelAccount, not standard toSimpleSmartAccount |
| Gelato | Proprietary Smart Wallet SDK — no standard bundler RPCs exposed |
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
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.