@gasfree-kit/evm-4337
v0.3.1
Published
ERC-4337 gasless transactions for EVM chains — powered by WDK
Maintainers
Readme
@gasfree-kit/evm-4337
ERC-4337 USDT transfers for EVM chains using WDK-backed Safe smart accounts.
This package gives you:
- standard seed-phrase driven transfers
- sponsored mode, where a paymaster covers gas
- token-paid mode, where gas is charged in USDT
- optional passkey linking and passkey-signed transfers
How It Works
┌──────────────────────┐
│ Your app │
└──────────┬───────────┘
│
v
┌──────────────────────┐
│ @gasfree-kit/evm-4337│
└──────────┬───────────┘
│
v
┌──────────────────────┐ ┌──────────────────────┐
│ WDK wallet manager │──────>│ Safe smart account │
└──────────────────────┘ └──────────┬───────────┘
│
v
┌──────────────────────┐
┌──────────────────────┐ │ UserOperation │
│ Paymaster │·····> │ (ERC-4337) │
│ │ └──────────┬───────────┘
│ sponsors gas or │ │
│ charges in USDT │ v
└──────────────────────┘ ┌──────────────────────┐
│ Bundler │
└──────────┬───────────┘
│
v
┌──────────────────────┐
│ EVM chain │
└──────────────────────┘Supported Chains
| Chain | Chain ID | Typical fallback fee estimate |
| -------- | -------- | ----------------------------- |
| Ethereum | 1 | 1.40 USDT |
| Base | 8453 | 0.15 USDT |
| Arbitrum | 42161 | 0.10 USDT |
| Optimism | 10 | 0.10 USDT |
| Polygon | 137 | 0.10 USDT |
| Celo | 42220 | 0.05 USDT |
| Plasma | 9745 | 0.05 USDT |
Prerequisites
This package depends on @gasfree-kit/core for:
- Seed phrase generation —
generateSeedPhrase()creates the mnemonic used to set up wallets - Chain metadata —
EVM_CHAINSprovides chain IDs, USDT addresses, and explorer URLs - Address validation —
validateEvmAddress()catches malformed addresses before sending - Error classes —
GasfreeError,InsufficientBalanceError, etc. for consistent error handling
@gasfree-kit/core is installed automatically as a dependency.
Installation
npm install @gasfree-kit/evm-4337 @gasfree-kit/coreRequired peer dependency:
npm install @tetherto/wdk-wallet-evm-erc-4337Optional passkey peer dependencies:
npm install @safe-global/protocol-kit @safe-global/relay-kitChoose Your Gas Mode
| Mode | When to use it | Required config |
| ---------- | --------------------------------- | ------------------------------------------------ |
| Sponsored | Your paymaster fully covers gas | isSponsored: true and sponsorshipPolicyId |
| Token-paid | Gas is deducted from USDT balance | isSponsored: false and a paymaster/token setup |
Quick Start
1. Create a config
Sponsored mode:
import type { EVM4337ClientConfig } from '@gasfree-kit/evm-4337';
const sponsoredConfig: EVM4337ClientConfig = {
chain: 'base',
rpcUrl: 'https://mainnet.base.org',
bundlerUrl: 'https://your-bundler.example.com',
paymasterUrl: 'https://your-paymaster.example.com',
isSponsored: true,
sponsorshipPolicyId: 'your-policy-id',
};Token-paid mode:
const tokenPaidConfig: EVM4337ClientConfig = {
chain: 'base',
rpcUrl: 'https://mainnet.base.org',
bundlerUrl: 'https://your-bundler.example.com',
paymasterUrl: 'https://your-paymaster.example.com',
isSponsored: false,
paymasterAddress: '0xYourPaymasterAddress',
// Optional:
// paymasterTokenAddress: '0xYourUSDTLikeToken'
};2. Generate a seed phrase
import { generateSeedPhrase } from '@gasfree-kit/core';
const seedPhrase = await generateSeedPhrase();3. Set up the Safe smart account
import { setupErc4337Wallet } from '@gasfree-kit/evm-4337';
const { wallet, account, address } = await setupErc4337Wallet(seedPhrase, sponsoredConfig);
console.log(address); // Safe address4. Check balance
import { EvmTransfer } from '@gasfree-kit/evm-4337';
import { EVM_CHAINS } from '@gasfree-kit/core';
const balance = await EvmTransfer.checkTokenBalance(
seedPhrase,
sponsoredConfig,
EVM_CHAINS.base.usdtAddress,
);
console.log(balance.data.usdBalance);5. Estimate fees
const estimate = await EvmTransfer.getTransactionEstimateFee(
seedPhrase,
sponsoredConfig,
'0x1111111111111111111111111111111111111111',
);
console.log(estimate.data.fee);6. Send a transfer
const result = await EvmTransfer.sendToken(
seedPhrase,
sponsoredConfig,
EVM_CHAINS.base.usdtAddress,
'50.00',
'0x1111111111111111111111111111111111111111',
);
console.log(result.transactionHash);7. Send a batch transfer
const batch = await EvmTransfer.sendBatchToken(
seedPhrase,
sponsoredConfig,
EVM_CHAINS.base.usdtAddress,
[
{ address: '0x1111111111111111111111111111111111111111', amount: '25.00' },
{ address: '0x2222222222222222222222222222222222222222', amount: '10.00' },
],
);Passkey Flow
Passkeys are optional. They let you add a WebAuthn signer to an existing Safe so transfers can be approved with biometrics instead of a seed phrase.
Important:
- the Safe must already be deployed on-chain before you link a passkey
storage: 'persist'is convenience storage only, not hardened secret storage
Passkey diagram
┌──────────────────────┐
│ Seed phrase owner │──────┐
└──────────────────────┘ │
v
┌──────────────────────┐
│ Safe smart account │
└──────────┬───────────┘
┌──────────────────────┐ │
│ Passkey (WebAuthn) │──────┘
└──────────────────────┘
│
v
┌──────────────────────┐
│ Signed UserOperation │
└──────────┬───────────┘
│
v
┌──────────────────────┐
│ Bundler + Paymaster │
└──────────┬───────────┘
│
v
┌──────────────────────┐
│ EVM chain │
└──────────────────────┘Link a passkey to an existing Safe
import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';
const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
rpName: 'My App',
userName: '[email protected]',
storage: 'not_persist',
});
console.log(passkey.safeAddress);
console.log(passkey.credential.signerAddress);Check whether a passkey is linked
import { isPasskeyLinked } from '@gasfree-kit/evm-4337';
const status = await isPasskeyLinked(seedPhrase, sponsoredConfig);
if (status.linked) {
console.log(status.signerAddress);
}Send a transfer with a passkey
import { PasskeyTransfer } from '@gasfree-kit/evm-4337';
import { EVM_CHAINS } from '@gasfree-kit/core';
const tx = await PasskeyTransfer.sendToken(
passkey.credential,
sponsoredConfig,
EVM_CHAINS.base.usdtAddress,
'10.00',
'0x1111111111111111111111111111111111111111',
);Remove a passkey owner
import { unlinkPasskeyFromSafe } from '@gasfree-kit/evm-4337';
await unlinkPasskeyFromSafe(seedPhrase, sponsoredConfig, passkey.credential);Main Exports
| Export | What it does |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| setupErc4337Wallet | Creates a WDK-backed ERC-4337 wallet and resolves the Safe address |
| EvmTransfer | Estimates, checks balance, sends single transfers, and sends batch transfers |
| PasskeyTransfer | Sends passkey-signed transfers after a passkey has been linked |
| linkPasskeyToSafe | Registers a passkey and adds its signer to the Safe owner set |
| isPasskeyLinked | Checks whether a stored passkey is still an owner on-chain |
| unlinkPasskeyFromSafe | Removes a passkey signer from the Safe |
| getErc4337ConfigForChain | Normalizes the public client config into runtime config |
| GAS_FEE_FALLBACKS and GAS_FEE_ESTIMATES | Exposes fallback fee heuristics per chain |
| toUsdtBaseUnitsEvm, formatTokenBalance, feeToUsdt, encodeErc20Transfer | Utility helpers for amounts and calldata |
Export Example
import {
setupErc4337Wallet,
EvmTransfer,
PasskeyTransfer,
linkPasskeyToSafe,
isPasskeyLinked,
unlinkPasskeyFromSafe,
getErc4337ConfigForChain,
GAS_FEE_FALLBACKS,
GAS_FEE_ESTIMATES,
} from '@gasfree-kit/evm-4337';Notes
sponsorshipPolicyIdis required whenisSponsoredistrue- self-transfers are blocked
- token-paid mode checks that the USDT balance can cover the transfer and gas reserve
- passkey linking verifies the signer deployment before adding it as an owner
License
MIT
