Configuration
Configuration options and settings for @tetherto/wdk-wallet-evm
Wallet Configuration
The WalletManagerEvm accepts a configuration object that defines how the wallet interacts with the blockchain:
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'
const config = {
// Recommended: RPC endpoint URL, EIP-1193 provider, or ordered failover list
provider: 'https://eth.drpc.org',
// Optional: Pin this RPC endpoint to a known chain without detecting it
chainId: 1,
// Optional: Additional failover attempts when provider is an array
retries: 2,
// Optional: Maximum fee for ERC-20 transfer operations (in wei)
transferMaxFee: 100000000000000, // 0.0001 ETH
// Optional: Execution-gas fee cap for send/provider-backed sign operations (in wei)
transactionMaxFee: 100000000000000
}
const wallet = new WalletManagerEvm(seedPhrase, config)Signer Configuration
WalletManagerEvm accepts a BIP-39 mnemonic, raw BIP-32 master seed bytes (16–64 bytes), or a derivable EVM signer as its first argument. Raw seed bytes do not have to originate from BIP-39. Use the @tetherto/wdk-wallet-evm/signers entrypoint when you need explicit signer objects:
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'
import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers'
const signer = new SeedSignerEvm(seedPhrase)
const wallet = new WalletManagerEvm(signer, {
provider: 'https://eth.drpc.org'
})SeedSignerEvm(seed) starts at the coin node m/44'/60'. Its derive() method uses a path relative to the signer's current node. The manager's default signer must support derivation. Non-derivable signers, such as private-key signers, can be registered by name and used to retrieve signer-backed accounts:
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'
import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers'
const wallet = new WalletManagerEvm(seedPhrase, {
provider: 'https://eth.drpc.org'
})
wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey))
const treasury = await wallet.getAccount('treasury')For a standalone account backed by one private key, construct the account directly:
import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'
const account = WalletAccountEvm.fromPrivateKey(privateKey, {
provider: 'https://eth.drpc.org'
})Signer Migration
When upgrading from beta.20:
- Replace
new SeedSignerEvm(seed, options)withnew SeedSignerEvm(seed, absolutePath). The default ism/44'/60', rather than the account leaf. Usenew WalletAccountEvm(seed, config)for the standardm/44'/60'/0'/0/0account, orsigner.derive("0'/0/0")from the default coin node. - Use
account.pathinstead of the removedaccount.index. A private-key signer haspath: null. - Use
getAddress()instead of deprecated synchronousaddressgetters, which can beundefinedfor external signers. getAccount('name')wraps the registered signer at its current path without deriving. UsegetAccount(index, { signerName: 'name' })orgetAccountByPath(path, { signerName: 'name' })to derive from a named signer.- A manager initiates cleanup of its seed-created default signer, then delegates cached-account cleanup to its pinned base-wallet beta.21. That implementation calls
account.dispose()only whenaccount.keyPair?.privateKeyis truthy. It skips derived external signers without exposed private-key bytes, and a throwing key getter or disposal can interrupt later cleanup. Dispose such derived accounts explicitly. Default and named signers supplied by your app remain app-owned. An account created from a supplied signer preserves that signer unlessshouldWipeSignerOnDisposal: trueis set. Accounts created from a seed or throughfromPrivateKey()own their signers.
Dispose app-owned signers only after their last consumer finishes. PrivateKeySignerEvm copies an input key; its cleanup does not erase the caller's input. Mnemonic construction clears temporary seed and master-key buffers, but preserves caller-owned seed bytes and the derived signer needed by the wallet. This does not establish complete erasure of secret copies.
@tetherto/wdk-wallet-evm/signers exports the runtime ISignerEvm, SeedSignerEvm, and PrivateKeySignerEvm classes, including a Bare entrypoint. Import TransactionLike as a type from that subpath when implementing a signer; it replaces the removed UnsignedEvmTransaction type. The root exports the base ISigner, while ISignerEvm is available from the signers subpath.
Account Configuration
Both WalletAccountEvm and WalletAccountReadOnlyEvm share similar configuration options:
import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm'
// Full access account
const account = new WalletAccountEvm(
seedPhrase,
"0'/0/0", // BIP-44 derivation path
{
provider: 'https://eth.drpc.org',
transferMaxFee: 100000000000000,
transactionMaxFee: 100000000000000
}
)
// Read-only account
const readOnlyAccount = new WalletAccountReadOnlyEvm(
'0x...', // Ethereum address
{
provider: 'https://eth.drpc.org'
}
)Configuration Options
Provider
The provider option accepts an RPC URL, an ethers Provider, an EIP-1193 provider, or an ordered array of those values for failover. Existing ethers providers are reused.
Type: string | Provider | Eip1193Provider | Array<string | Provider | Eip1193Provider>
WalletManagerEvm builds or accepts its provider once and shares the same provider object with every account it derives. Direct account constructors process their configurations independently, but they can share a caller-supplied ethers Provider object.
The documented Velora, USD₮0 bridge, and Aave adapters require EIP-1193 request() on non-string provider inputs. An account whose manager supplies a shared ethers provider without request() cannot initialize these adapters. Use a directly constructed WalletAccountEvm that retains its original RPC URL or genuine EIP-1193 provider for these adapters; do not replace the account's internal provider field. Direct accounts do not receive WDK Core policy or middleware decoration, so preserve the required application checks and account-level controls.
Examples:
// Option 1: Using RPC URL
const config = {
provider: 'https://eth.drpc.org'
}
// Option 2: Using browser provider (e.g., MetaMask)
const config = {
provider: window.ethereum
}
// Option 3: Using a custom EIP-1193 provider
// Works in Node.js, Bare, and browsers - zero external dependencies
function createFetchProvider(rpcUrl) {
let requestId = 0
return {
request: async ({ method, params }) => {
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: ++requestId,
method,
params: params || []
})
})
const data = await response.json()
if (data.error) throw new Error(data.error.message)
return data.result
}
}
}
const config = {
provider: createFetchProvider('https://eth.drpc.org')
}
// Option 4: Using ordered provider failover
const config = {
provider: [
'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
'https://eth.drpc.org',
createFetchProvider('https://ethereum.publicnode.com')
],
retries: 2
}You can reuse an existing ethers provider:
import { JsonRpcProvider } from 'ethers'
const provider = new JsonRpcProvider('https://eth.drpc.org')
const config = { provider }When provider is an array, the wallet uses the candidates in order and retries connection failures against the next provider. If retries is greater than the number of providers, the failover loop wraps around in round-robin order.
Retries
The retries option controls how many additional attempts can happen after the first provider call fails. It only applies when provider is an array.
Type: number (optional)
Default: 3
Example:
const config = {
provider: [
'https://primary.example',
'https://secondary.example'
],
retries: 1
}Chain ID
A nonzero chainId pins URL and EIP-1193 providers created by this module, including entries in a failover list. They use that chain ID without sending eth_chainId requests. Omit the option for automatic network detection; chainId: 0 does not enable pinning.
An existing ethers Provider retains its own network configuration. Passing chainId alongside it does not reconfigure that provider.
Pin only providers that stay on the intended network. Pinning does not verify the endpoint's chain or detect a browser wallet's network switch. If the network changes, stop using the pinned wallet and recreate it with the intended configuration before continuing.
This option configures provider network identity, not every signing input. signTransaction() forwards the transaction's supplied chain ID to the signer without populating it from this option. An explicit chainId: 0 passed to signAuthorization() remains a separate opt-in to authorization across chains.
Type: number (optional)
Example:
const config = {
provider: 'https://polygon-rpc.com',
chainId: 137
}Transfer Max Fee
The transferMaxFee option sets a maximum fee limit for ERC-20 transfer() operations.
Type: number | bigint (optional)
Unit: Wei (1 ETH = 1000000000000000000 Wei)
Examples:
import { MaximumFeeExceededError } from '@tetherto/wdk-wallet'
const config = {
// Set maximum fee to 0.0001 ETH
transferMaxFee: 100000000000000n,
}
// Usage example
try {
const result = await account.transfer({
token: '0x...', // ERC20 address
recipient: '0x...',
amount: 1000000n
})
} catch (error) {
if (error instanceof MaximumFeeExceededError) {
console.error('Transfer canceled: Fee too high')
} else {
throw error
}
}Transaction Max Fee
The transactionMaxFee option rejects sendTransaction() and provider-backed signTransaction() when the quoted execution-gas fee exceeds the configured value. Equality is allowed. ERC-20 transfers and approvals also send through sendTransaction(), so this cap applies to them; transfers additionally check transferMaxFee.
In the documented release, the quote uses the supplied gasLimit (or an estimate) multiplied by maxFeePerGas, then gasPrice, then the provider's current fee rate, in that order. For signed raw bytes, it uses the encoded gas limit and encoded fee rate. The quote still simulates the transaction and can reject a revert even when a gas limit is supplied. See fee quoting.
This cap covers execution gas, not transferred value. Blob transactions are rejected. Unspecified fields can change when an object transaction is populated after the check; a quote does not lock those fields or report the eventual fee paid. Review all signed fields before submitting external bytes. Offline signing without a provider does not run the fee-cap check.
For a policy example that separately validates Ethereum type-2 transaction fields and execution-gas liability, see Bound Transaction Fees.
Type: number | bigint (optional)
Unit: Wei (1 ETH = 1000000000000000000 Wei)
Example:
const config = {
provider: 'https://eth.drpc.org',
transactionMaxFee: 100000000000000n
}Fee Rate Multipliers
The wallet manager uses predefined multipliers for fee calculations:
// Normal fee rate = base fee × 1.1
const normalFee = await wallet.getFeeRates()
console.log('Normal fee:', normalFee.normal)
// Fast fee rate = base fee × 2.0
const fastFee = await wallet.getFeeRates()
console.log('Fast fee:', fastFee.fast)Network Support
The configuration works with any EVM-compatible network. Just change the provider URL:
// Ethereum Mainnet
const mainnetConfig = {
provider: 'https://eth.drpc.org'
}
// Polygon (Matic)
const polygonConfig = {
provider: 'https://polygon-rpc.com'
}
// Arbitrum
const arbitrumConfig = {
provider: 'https://arb1.arbitrum.io/rpc'
}
// BSC (Binance Smart Chain)
const bscConfig = {
provider: 'https://bsc-dataseed.binance.org'
}
// Avalanche C-Chain
const avalancheConfig = {
provider: 'https://avalanche-c-chain-rpc.publicnode.com',
}
// Plasma
const plasmaConfig = {
provider: 'https://plasma.drpc.org',
}
// Stable (uses USDt0 as its native gas token)
// No need for ERC-4337 paymaster/bundler setup.
const stableConfig = {
provider: 'https://rpc.stable.xyz',
}
// Sepolia Testnet
const sepoliaConfig = {
provider: 'https://sepolia.drpc.org',
}
Next Steps
Node.js Quickstart
Get started with WDK in a Node.js environment
React Native Quickstart
Build mobile wallets with React Native Expo
WDK EVM Wallet Usage
Get started with WDK's EVM Wallet Usage
WDK EVM Wallet API
Get started with WDK's EVM Wallet API