@seapay-ai/erc3009
v0.2.7
Published
TypeScript utilities for ERC-3009 (Transfer With Authorization) EIP-712 signing
Maintainers
Readme
@seapay-ai/erc3009
Simplified TypeScript library for ERC-3009 (TransferWithAuthorization) EIP-712 signing and verification.
Installation
pnpm add @seapay-ai/erc3009
# or
npm install @seapay-ai/erc3009Quick Start
import {
buildTypedData,
buildMessage,
resolveDomain,
nowPlusSeconds,
} from "@seapay-ai/erc3009";
import { Wallet } from "ethers";
// 1. Build the message
const message = buildMessage({
from: wallet.address,
to: "0xRecipient...",
value: 1000000n, // 1 USDC (6 decimals)
validBefore: nowPlusSeconds(300), // Valid for 5 minutes
});
// 2. Build typed data (includes domain resolution from registry)
const {
domain,
types,
message: msg,
} = buildTypedData({
chainId: 8453, // Base mainnet
token: "USDC",
message,
});
// 3. Sign with ethers wallet
const signature = await wallet.signTypedData(domain, types, msg);Core Functions
1. Build Message
import { buildMessage, nowPlusSeconds, randomNonce } from "@seapay-ai/erc3009";
const message = buildMessage({
from: "0xSender...",
to: "0xRecipient...",
value: 1000000n, // Amount in token's smallest unit
validAfter: 0n, // Optional, defaults to 0
validBefore: nowPlusSeconds(300), // Unix timestamp
nonce: randomNonce(), // Optional, auto-generated if not provided
});2. Build Typed Data
Automatically resolves token info from the registry:
import { buildTypedData } from "@seapay-ai/erc3009";
const typedData = buildTypedData({
chainId: 8453, // Base mainnet
token: "USDC", // Symbol from registry
message,
});
// Returns:
// {
// domain: { name, version, chainId, verifyingContract },
// types: { TransferWithAuthorization: [...] },
// message: { from, to, value, ... },
// primaryType: "TransferWithAuthorization"
// }For custom tokens not in the registry:
const typedData = buildTypedData({
chainId: 8453,
token: "0xCustomTokenAddress",
message,
domainOverrides: {
name: "My Token",
version: "1",
verifyingContract: "0xCustomTokenAddress",
},
});3. Resolve Domain
Resolve EIP-712 domain from chain ID and token:
import { resolveDomain } from "@seapay-ai/erc3009";
// Resolve USDC domain on Base
const domain = resolveDomain(8453, "USDC");
// Returns:
// {
// name: "USD Coin",
// version: "2",
// chainId: 8453,
// verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
// }
// For custom tokens
const customDomain = resolveDomain(8453, "0xCustomTokenAddress", {
name: "My Token",
version: "1",
verifyingContract: "0xCustomTokenAddress",
});This function is useful when you need just the domain object without building the complete typed data structure. It's used internally by buildTypedData.
4. Verify Signature
import { verifySignature, recoverSigner } from "@seapay-ai/erc3009";
// Verify signature matches expected signer
const isValid = verifySignature(domain, message, signature, expectedSigner);
// Or recover the signer address
const signer = recoverSigner(domain, message, signature);
console.log("Signed by:", signer);Registry
The package includes a built-in registry of USDC deployments across multiple chains.
Query the Registry
import { registry, getTokenInfo } from "@seapay-ai/erc3009";
// Get token config
const usdcBase = registry.getToken("USDC", 8453);
// => { symbol: "USDC", chainId: 8453, address: "0x...", name: "USD Coin", version: "2", decimals: 6 }
// Check if token is supported
if (registry.isSupported("USDC", 8453)) {
console.log("USDC supported on Base");
}
// List all chains
const chains = registry.listChains();
// List tokens on a chain
const tokens = registry.listTokensOnChain(8453);
// Get chain info
const baseChain = registry.getChain(8453);
// => { chainId: 8453, name: "Base", testnet: false }Supported Chains
| Chain | Chain ID | Testnet | | ---------------- | -------- | ------- | | Ethereum | 1 | - | | Sepolia | 11155111 | ✅ | | Base | 8453 | - | | Base Sepolia | 84532 | ✅ | | Arbitrum One | 42161 | - | | Arbitrum Sepolia | 421614 | ✅ | | Optimism | 10 | - | | Optimism Sepolia | 11155420 | ✅ | | Polygon | 137 | - | | Polygon Amoy | 80002 | ✅ |
Supported Tokens
Currently includes USDC on all chains above.
⚠️ Important: Base Network Domain Names
USDC has different EIP-712 domain names on Base networks:
| Network | Chain ID | Domain Name |
| ------------ | -------- | ------------ |
| Base Mainnet | 8453 | "USD Coin" |
| Base Sepolia | 84532 | "USDC" |
The registry handles this automatically. Always use the registry to ensure correct domain parameters.
Complete Example
import {
buildTypedData,
buildMessage,
nowPlusSeconds,
verifySignature,
} from "@seapay-ai/erc3009";
import { Wallet } from "ethers";
// Create wallet
const wallet = new Wallet("0x...");
// Build message
const message = buildMessage({
from: wallet.address,
to: "0xRecipient...",
value: 1000000n, // 1 USDC
validBefore: nowPlusSeconds(300), // 5 minutes
});
// Build typed data
const {
domain,
types,
message: msg,
} = buildTypedData({
chainId: 84532, // Base Sepolia
token: "USDC",
message,
});
// Sign
const signature = await wallet.signTypedData(domain, types, msg);
// Verify
const isValid = verifySignature(domain, message, signature, wallet.address);
console.log("Signature valid:", isValid);API Reference
Build Functions
buildMessage(params)- Create TransferWithAuthorization messagebuildTypedData(params)- Create complete EIP-712 typed data with domain resolution
Domain Resolution
resolveDomain(chainId, token, domainOverrides?)- Resolve EIP-712 domain from chain and token
Verification Functions
verifySignature(domain, message, signature, expectedSigner)- Verify signaturerecoverSigner(domain, message, signature)- Recover signer address
Registry Functions
getTokenInfo(symbol, chainId)- Get token configurationregistry.getToken(symbol, chainId)- Get token configregistry.getChain(chainId)- Get chain configregistry.listChains()- List all chainsregistry.listChainIds()- List all chain IDsregistry.isSupported(symbol, chainId)- Check if token is supportedregistry.listTokensOnChain(chainId)- List tokens on a chain
Utility Functions
randomNonce()- Generate random bytes32 noncenowPlusSeconds(seconds)- Get Unix timestamp N seconds from nownowSeconds()- Get current Unix timestamp
TypeScript Types
import type {
TransferWithAuthorization,
EIP712Domain,
TypedData,
TokenConfig,
ChainConfig,
} from "@seapay-ai/erc3009";License
Apache-2.0
