npm package: @gasfree-kit/evm-4337
Description: ERC-4337 gasless transactions for EVM chains — powered by WDK
Latest version: 0.3.1

# @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`](../core/README.md) for:

- **Seed phrase generation** — `generateSeedPhrase()` creates the mnemonic used to set up wallets
- **Chain metadata** — `EVM_CHAINS` provides 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

```bash
npm install @gasfree-kit/evm-4337 @gasfree-kit/core
```

Required peer dependency:

```bash
npm install @tetherto/wdk-wallet-evm-erc-4337
```

Optional passkey peer dependencies:

```bash
npm install @safe-global/protocol-kit @safe-global/relay-kit
```

## Choose 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:

```ts
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:

```ts
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

```ts
import { generateSeedPhrase } from '@gasfree-kit/core';

const seedPhrase = await generateSeedPhrase();
```

### 3. Set up the Safe smart account

```ts
import { setupErc4337Wallet } from '@gasfree-kit/evm-4337';

const { wallet, account, address } = await setupErc4337Wallet(seedPhrase, sponsoredConfig);

console.log(address); // Safe address
```

### 4. Check balance

```ts
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

```ts
const estimate = await EvmTransfer.getTransactionEstimateFee(
  seedPhrase,
  sponsoredConfig,
  '0x1111111111111111111111111111111111111111',
);

console.log(estimate.data.fee);
```

### 6. Send a transfer

```ts
const result = await EvmTransfer.sendToken(
  seedPhrase,
  sponsoredConfig,
  EVM_CHAINS.base.usdtAddress,
  '50.00',
  '0x1111111111111111111111111111111111111111',
);

console.log(result.transactionHash);
```

### 7. Send a batch transfer

```ts
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

```ts
import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';

const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
  rpName: 'My App',
  userName: 'user@example.com',
  storage: 'not_persist',
});

console.log(passkey.safeAddress);
console.log(passkey.credential.signerAddress);
```

### Check whether a passkey is linked

```ts
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

```ts
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

```ts
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

```ts
import {
  setupErc4337Wallet,
  EvmTransfer,
  PasskeyTransfer,
  linkPasskeyToSafe,
  isPasskeyLinked,
  unlinkPasskeyFromSafe,
  getErc4337ConfigForChain,
  GAS_FEE_FALLBACKS,
  GAS_FEE_ESTIMATES,
} from '@gasfree-kit/evm-4337';
```

## Notes

- `sponsorshipPolicyId` is required when `isSponsored` is `true`
- 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