Note: This package is currently in beta. Please test thoroughly in development environments before using in production.
A simple and secure package to manage BIP-44 wallets for EVM-compatible blockchains. This package provides a clean API for creating, managing, and interacting with Ethereum-compatible wallets using BIP-39 seed phrases and EVM-specific derivation paths.
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.wdk.tether.io.
⬇️ Installation
To install the @tetherto/wdk-wallet-evm package, follow these instructions:
You can install it using npm:
npm install @tetherto/wdk-wallet-evm
🚀 Quick Start
Importing from @tetherto/wdk-wallet-evm
import WalletManagerEvm, { WalletAccountEvm, WalletAccountReadOnlyEvm,} from '@tetherto/wdk-wallet-evm'// Signers are exported under the /signers subpathimport { SeedSignerEvm, PrivateKeySignerEvm,} from '@tetherto/wdk-wallet-evm/signers'
Create a Wallet Manager (seed-based)
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers'// Use a BIP-39 seed phrase (replace with your own secure phrase)const seedPhrase = 'test only example nut use this real life secret phrase must random'// Create a root signer from the seed phraseconst root = new SeedSignerEvm(seedPhrase)// Create wallet manager with provider config (provider is required for chain ops)const wallet = new WalletManagerEvm(root, { // Option 1: Using RPC URL provider: 'https://sepolia.drpc.org', // any EVM RPC transferMaxFee: 100000000000000n, // Optional: max fee in wei (BigInt)})// OR// Option 2: Using EIP-1193 provider (e.g., from browser wallet)const wallet2 = new WalletManagerEvm(root, { provider: window.ethereum, // EIP-1193 provider transferMaxFee: 100000000000000n, // Optional})// Get a full access accountconst account0 = await wallet.getAccount(0)// Convert to a read-only accountconst readOnlyAccount = await account0.toReadOnlyAccount()
Single Account (no manager): Private key
import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers'// From a raw private key (hex string or bytes)const pkSigner = new PrivateKeySignerEvm('0x0123...abcd')const pkAccount = new WalletAccountEvm(pkSigner, { provider: 'https://eth-mainnet.g.alchemy.com/v2/your-api-key',})
Single Account (no manager): Seed + path
import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'// From a BIP-39 seed phrase (or seed bytes) and a BIP-44 derivation pathconst account = new WalletAccountEvm(mnemonic, "0'/0/0", { provider: 'https://eth-mainnet.g.alchemy.com/v2/your-api-key',})
Managing Multiple Accounts (seed-based manager)
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers'const root = new SeedSignerEvm(mnemonic)const wallet = new WalletManagerEvm(root, { provider: 'https://eth-mainnet.g.alchemy.com/v2/your-api-key',})// Get the first account (index 0)const account = await wallet.getAccount(0) // m/44'/60'/0'/0/0const address = await account.getAddress() // 0x...console.log('Account 0 address:', address)// Get the second account (index 1)const account1 = await wallet.getAccount(1) // m/44'/60'/0'/0/1const address1 = await account1.getAddress() // 0x...console.log('Account 1 address:', address1)// Get account by custom derivation path// Full path will be m/44'/60'/0'/0/5const customAccount = await wallet.getAccountByPath('0\'/0/5')const customAddress = await customAccount.getAddress()console.log('Custom account address:', customAddress)// Note: All addresses are checksummed Ethereum addresses (0x...)// All accounts inherit the provider configuration from the wallet manager
Checking Balances
Owned Account
For accounts where you have the seed phrase and full access:
// Assume wallet and account are already created// Get native token balance (in wei)const balance = await account.getBalance()console.log('Native balance:', balance, 'wei') // 1 ETH = 1000000000000000000 wei// Get ERC20 token balanceconst tokenContract = '0x...' // ERC20 contract addressconst tokenBalance = await account.getTokenBalance(tokenContract)console.log('Token balance:', tokenBalance)// Note: Provider is required for balance checks// Make sure wallet was created with a provider configuration
Read-Only Account
For addresses where you don't have the seed phrase:
import { WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm'// Create a read-only accountconst readOnlyAccount = new WalletAccountReadOnlyEvm('0x...', { // Ethereum address provider: 'https://sepolia.drpc.org', // Required for balance checks})// Check native token balanceconst balance = await readOnlyAccount.getBalance()console.log('Native balance:', balance, 'wei')// Check ERC20 token balance using contractconst tokenBalance = await readOnlyAccount.getTokenBalance('0x...') // ERC20 contract addressconsole.log('Token balance:', tokenBalance)// Note: ERC20 balance checks use the standard balanceOf(address) function// Make sure the contract address is correct and implements the ERC20 standard
Sending Transactions
Send native tokens and estimate fees using WalletAccountEvm. Supports EIP-1559 and auto-populates gas/fee fields where possible.
// Send native tokens// Modern EIP-1559 style transaction (recommended)const result = await account.sendTransaction({ to: '0x...', // Recipient address value: 1000000000000000000n, // 1 ETH in wei maxFeePerGas: 30000000000n, // Optional: max fee per gas (in wei) maxPriorityFeePerGas: 2000000000n, // Optional: max priority fee per gas (in wei)})console.log('Transaction hash:', result.hash)console.log('Transaction fee:', result.fee, 'wei')// OR Legacy style transactionconst legacyResult = await account.sendTransaction({ to: '0x...', value: 1000000000000000000n, gasPrice: 20000000000n, // Optional: legacy gas price (in wei) gasLimit: 21000, // Optional: gas limit})// Get transaction fee estimateconst quote = await account.quoteSendTransaction({ to: '0x...', value: 1000000000000000000n,})console.log('Estimated fee:', quote.fee, 'wei')
Token Transfers
Transfer ERC20 tokens and estimate fees using WalletAccountEvm. Uses standard ERC20 transfer function.
// Transfer ERC20 tokensconst transferResult = await account.transfer({ token: '0x...', // ERC20 contract address recipient: '0x...', // Recipient's address amount: 1000000n, // Amount in token's base units (use BigInt for large numbers)})console.log('Transfer hash:', transferResult.hash)console.log('Transfer fee:', transferResult.fee, 'wei')// Quote token transfer feeconst transferQuote = await account.quoteTransfer({ token: '0x...', // ERC20 contract address recipient: '0x...', // Recipient's address amount: 1000000n, // Amount in token's base units})console.log('Transfer fee estimate:', transferQuote.fee, 'wei')
Token Approvals
Approve a spender for a specific amount (uses ERC20 approve):
Sign messages using WalletAccountEvm and verify signatures using WalletAccountReadOnlyEvm.
// Sign a messageconst message = 'Hello, Ethereum!'const signature = await account.sign(message)console.log('Signature:', signature)// Verify a signature (can use read-only account)const isValid = await readOnlyAccount.verify(message, signature)console.log('Signature valid:', isValid)
Fee Management
Retrieve current fee rates using WalletManagerEvm. Supports EIP-1559 fee model.
// Get current fee ratesconst feeRates = await wallet.getFeeRates()console.log('Normal fee rate:', feeRates.normal, 'wei') // 1.1x base feeconsole.log('Fast fee rate:', feeRates.fast, 'wei') // 2.0x base fee
Memory Management
Clear sensitive data from memory using dispose methods in WalletAccountEvm and WalletManagerEvm.
// Dispose wallet accounts to clear private keys from memoryaccount.dispose()// Dispose entire wallet managerwallet.dispose()
🔐 Signers
Signers provide the cryptographic primitives for accounts. There are two signer implementations:
SeedSignerEvm (root + child): Derives accounts from a BIP-39 seed using the BIP-44 Ethereum path. Can act as a root (for WalletManagerEvm) and derive children (for WalletAccountEvm).
PrivateKeySignerEvm (child only): Wraps a raw private key in a memory-safe buffer. Cannot derive. Use directly with WalletAccountEvm. Not supported by WalletManagerEvm.
Examples:
// Root + manager (seed)import WalletManagerEvm from '@tetherto/wdk-wallet-evm'import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers'const root = new SeedSignerEvm(mnemonic)const wallet = new WalletManagerEvm(root, { provider: 'https://...' })const account0 = await wallet.getAccount(0)// Single account from a private keyimport { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers'const signer = new PrivateKeySignerEvm('0x0123...')const account = new WalletAccountEvm(signer, { provider: 'https://...' })
Key Capabilities
BIP-39 Seed Phrase Support: Generate and validate mnemonic seed phrases
BIP-44 Derivation Paths: Standard Ethereum derivation (m/44'/60')
Multi-Account Management: Derive multiple accounts from a single seed phrase
EIP-1559 Transaction Support: Modern fee estimation and transaction sending
ERC-20 Token Support: Query balances and transfer tokens
Message Signing: Sign and verify messages (EIP-191 and EIP-712)
Fee Estimation: Real-time network fee rates with normal/fast tiers
Secure Memory Disposal: Clear private keys from memory when done
Signer Submodule: Create your own signer from ISignerEvm, support for Seed and Private key signers
EIP-7702 Delegation: Delegate EOAs to smart contracts, sign authorizations, and send type 4 transactions
Compatibility
Ethereum Mainnet and testnets (Sepolia)
Layer 2 Networks: Arbitrum, Optimism, Base
Other EVM Chains: Polygon, Avalanche C-Chain, and any EVM-compatible chain