@seapay-ai/erc3009 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -48
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/prepare.d.ts +47 -0
- package/dist/api/prepare.d.ts.map +1 -0
- package/dist/api/prepare.js +57 -0
- package/dist/core.d.ts +14 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +13 -0
- package/dist/domain/index.d.ts +3 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +2 -0
- package/dist/domain/normalize.d.ts +9 -0
- package/dist/domain/normalize.d.ts.map +1 -0
- package/dist/domain/normalize.js +25 -0
- package/dist/domain/resolveDomain.d.ts +15 -0
- package/dist/domain/resolveDomain.d.ts.map +1 -0
- package/dist/domain/resolveDomain.js +47 -0
- package/dist/erc3009/buildTypes.d.ts +6 -0
- package/dist/erc3009/buildTypes.d.ts.map +1 -0
- package/dist/erc3009/buildTypes.js +11 -0
- package/dist/erc3009/constants.d.ts +7 -0
- package/dist/erc3009/constants.d.ts.map +1 -0
- package/dist/erc3009/constants.js +6 -0
- package/dist/erc3009/index.d.ts +7 -0
- package/dist/erc3009/index.d.ts.map +1 -0
- package/dist/erc3009/index.js +6 -0
- package/dist/erc3009/message.d.ts +21 -0
- package/dist/erc3009/message.d.ts.map +1 -0
- package/dist/erc3009/message.js +29 -0
- package/dist/erc3009/sign.d.ts +7 -0
- package/dist/erc3009/sign.d.ts.map +1 -0
- package/dist/erc3009/sign.js +8 -0
- package/dist/erc3009/typedData.d.ts +15 -0
- package/dist/erc3009/typedData.d.ts.map +1 -0
- package/dist/erc3009/typedData.js +21 -0
- package/dist/erc3009/verify.d.ts +10 -0
- package/dist/erc3009/verify.d.ts.map +1 -0
- package/dist/erc3009/verify.js +17 -0
- package/dist/index.d.ts +23 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -65
- package/dist/registry/chains.d.ts +22 -0
- package/dist/registry/chains.d.ts.map +1 -0
- package/dist/registry/chains.js +104 -0
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +6 -0
- package/dist/registry/registry.d.ts +38 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +73 -0
- package/dist/registry/tokens/index.d.ts +11 -0
- package/dist/registry/tokens/index.d.ts.map +1 -0
- package/dist/registry/tokens/index.js +14 -0
- package/dist/registry/tokens/usdc.d.ts +27 -0
- package/dist/registry/tokens/usdc.d.ts.map +1 -0
- package/dist/registry/tokens/usdc.js +104 -0
- package/dist/types/domain.d.ts +24 -0
- package/dist/types/domain.d.ts.map +1 -0
- package/dist/types/domain.js +1 -0
- package/dist/types/erc3009.d.ts +38 -0
- package/dist/types/erc3009.d.ts.map +1 -0
- package/dist/types/erc3009.js +15 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/registry.d.ts +35 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +1 -0
- package/dist/utils/hex.d.ts +13 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +19 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/nonce.d.ts +5 -0
- package/dist/utils/nonce.d.ts.map +1 -0
- package/dist/utils/nonce.js +7 -0
- package/dist/utils/time.d.ts +13 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +18 -0
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -1,80 +1,299 @@
|
|
|
1
1
|
# @seapay-ai/erc3009
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multi-chain ERC-3009 (TransferWithAuthorization) helper library for building, signing, and verifying EIP-712 typed data.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Multi-chain support**: Base, Ethereum, Arbitrum, Optimism, Polygon (mainnet + testnets)
|
|
8
|
+
- ✅ **Token registry**: Pre-configured USDC addresses and domain parameters for all chains
|
|
9
|
+
- ✅ **Type-safe**: Full TypeScript support with strict types
|
|
10
|
+
- ✅ **Ergonomic API**: One-liner `prepare()` for common use cases
|
|
11
|
+
- ✅ **Override support**: Customize domain parameters for custom tokens
|
|
12
|
+
- ✅ **ethers.js v6**: Built on ethers v6 for signing and verification
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
7
16
|
```bash
|
|
8
|
-
npm install @seapay-ai/erc3009
|
|
9
|
-
# or
|
|
10
17
|
pnpm add @seapay-ai/erc3009
|
|
11
18
|
# or
|
|
12
|
-
|
|
19
|
+
npm install @seapay-ai/erc3009
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
### Basic Example
|
|
22
|
+
## Quick Start
|
|
18
23
|
|
|
19
24
|
```typescript
|
|
25
|
+
import { prepare } from "@seapay-ai/erc3009";
|
|
20
26
|
import { Wallet } from "ethers";
|
|
27
|
+
|
|
28
|
+
// One-call convenience API
|
|
29
|
+
const { typedData } = prepare({
|
|
30
|
+
chainId: 8453, // Base
|
|
31
|
+
token: "USDC",
|
|
32
|
+
from: wallet.address,
|
|
33
|
+
to: "0xRecipient...",
|
|
34
|
+
value: 1000000n, // 1 USDC (6 decimals)
|
|
35
|
+
ttlSeconds: 300, // 5 minutes
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Sign with ethers wallet
|
|
39
|
+
const signature = await wallet.signTypedData(
|
|
40
|
+
typedData.domain,
|
|
41
|
+
typedData.types,
|
|
42
|
+
typedData.message
|
|
43
|
+
);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage Examples
|
|
47
|
+
|
|
48
|
+
### 1. Ergonomic API (Recommended)
|
|
49
|
+
|
|
50
|
+
The `prepare()` function resolves the domain, builds the message, and returns everything needed:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { prepare } from "@seapay-ai/erc3009";
|
|
54
|
+
|
|
55
|
+
const { domain, message, typedData } = prepare({
|
|
56
|
+
chainId: 8453, // Base mainnet
|
|
57
|
+
token: "USDC",
|
|
58
|
+
from: "0xSender...",
|
|
59
|
+
to: "0xRecipient...",
|
|
60
|
+
value: 1000000n, // 1 USDC
|
|
61
|
+
ttlSeconds: 300, // optional, default: 300
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Sign it
|
|
65
|
+
const sig = await wallet.signTypedData(
|
|
66
|
+
typedData.domain,
|
|
67
|
+
typedData.types,
|
|
68
|
+
typedData.message
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Core Builders (Manual)
|
|
73
|
+
|
|
74
|
+
For more control, use the core builders:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
21
77
|
import {
|
|
78
|
+
resolveDomain,
|
|
79
|
+
buildMessage,
|
|
22
80
|
buildTypedData,
|
|
23
|
-
|
|
24
|
-
message_5_minutes,
|
|
25
|
-
signTransferWithAuthorization,
|
|
26
|
-
type EIP712Domain,
|
|
27
|
-
type TransferWithAuthorization,
|
|
81
|
+
erc3009,
|
|
28
82
|
} from "@seapay-ai/erc3009";
|
|
29
83
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
84
|
+
// 1. Resolve domain from registry
|
|
85
|
+
const domain = resolveDomain({
|
|
86
|
+
chainId: 8453,
|
|
87
|
+
token: "USDC",
|
|
88
|
+
});
|
|
32
89
|
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
90
|
+
// 2. Build message
|
|
91
|
+
const message = buildMessage({
|
|
92
|
+
from: "0xSender...",
|
|
93
|
+
to: "0xRecipient...",
|
|
94
|
+
value: 1000000n,
|
|
95
|
+
validAfter: 0n,
|
|
96
|
+
validBefore: 1234567890n,
|
|
97
|
+
nonce: "0x...", // or use randomNonce()
|
|
98
|
+
});
|
|
40
99
|
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
wallet.address, // from
|
|
44
|
-
"0x...", // to
|
|
45
|
-
BigInt("1000000") // value (1 USDC if 6 decimals)
|
|
46
|
-
);
|
|
100
|
+
// 3. Build typed data
|
|
101
|
+
const typedData = buildTypedData({ domain, message });
|
|
47
102
|
|
|
48
|
-
// Sign
|
|
49
|
-
const signature = await
|
|
103
|
+
// 4. Sign
|
|
104
|
+
const signature = await erc3009.sign(wallet, domain, message);
|
|
105
|
+
```
|
|
50
106
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
107
|
+
### 3. Using the Registry
|
|
108
|
+
|
|
109
|
+
Query supported chains and tokens:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { registry, getToken, CHAINS } from "@seapay-ai/erc3009";
|
|
113
|
+
|
|
114
|
+
// Get token config
|
|
115
|
+
const usdcBase = getToken("USDC", 8453);
|
|
116
|
+
// => { symbol: "USDC", chainId: 8453, verifyingContract: "0x...", ... }
|
|
117
|
+
|
|
118
|
+
// List all chains
|
|
119
|
+
const chains = registry.listChains();
|
|
120
|
+
|
|
121
|
+
// Check support
|
|
122
|
+
if (registry.isTokenSupported("USDC", 8453)) {
|
|
123
|
+
console.log("USDC is supported on Base");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// List tokens on a chain
|
|
127
|
+
const tokens = registry.listTokensOnChain(8453);
|
|
55
128
|
```
|
|
56
129
|
|
|
57
|
-
###
|
|
130
|
+
### 4. Custom Tokens / Domain Overrides
|
|
131
|
+
|
|
132
|
+
For custom tokens not in the registry:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { prepare } from "@seapay-ai/erc3009";
|
|
136
|
+
|
|
137
|
+
const { typedData } = prepare({
|
|
138
|
+
chainId: 8453,
|
|
139
|
+
token: "0xCustomTokenAddress",
|
|
140
|
+
// Override domain fields
|
|
141
|
+
name: "My Custom Token",
|
|
142
|
+
version: "1",
|
|
143
|
+
verifyingContract: "0xCustomTokenAddress",
|
|
144
|
+
from: "0xSender...",
|
|
145
|
+
to: "0xRecipient...",
|
|
146
|
+
value: 1000000n,
|
|
147
|
+
});
|
|
148
|
+
```
|
|
58
149
|
|
|
59
|
-
|
|
150
|
+
### 5. Signature Verification
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { verifySignature, recoverSigner } from "@seapay-ai/erc3009";
|
|
154
|
+
|
|
155
|
+
// Recover signer
|
|
156
|
+
const recovered = recoverSigner(domain, message, signature);
|
|
157
|
+
console.log("Signed by:", recovered);
|
|
158
|
+
|
|
159
|
+
// Verify signature
|
|
160
|
+
const isValid = verifySignature(domain, message, signature, expectedSigner);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Supported Chains
|
|
164
|
+
|
|
165
|
+
| Chain | Chain ID | Testnet |
|
|
166
|
+
| ---------------- | -------- | ------- |
|
|
167
|
+
| Ethereum | 1 | - |
|
|
168
|
+
| Sepolia | 11155111 | ✅ |
|
|
169
|
+
| Base | 8453 | - |
|
|
170
|
+
| Base Sepolia | 84532 | ✅ |
|
|
171
|
+
| Arbitrum One | 42161 | - |
|
|
172
|
+
| Arbitrum Sepolia | 421614 | ✅ |
|
|
173
|
+
| Optimism | 10 | - |
|
|
174
|
+
| Optimism Sepolia | 11155420 | ✅ |
|
|
175
|
+
| Polygon | 137 | - |
|
|
176
|
+
| Polygon Amoy | 80002 | ✅ |
|
|
177
|
+
|
|
178
|
+
## Supported Tokens
|
|
179
|
+
|
|
180
|
+
Currently supports **USDC** on all chains above. The registry includes:
|
|
181
|
+
|
|
182
|
+
- Proxy contract addresses
|
|
183
|
+
- EIP-712 domain parameters (name, version)
|
|
184
|
+
- Token decimals
|
|
185
|
+
|
|
186
|
+
### ⚠️ Important: Base Network Domain Names
|
|
187
|
+
|
|
188
|
+
USDC has **different domain names** on Base networks:
|
|
189
|
+
|
|
190
|
+
| Network | Chain ID | Domain Name |
|
|
191
|
+
| ------------ | -------- | ------------ |
|
|
192
|
+
| Base Mainnet | 8453 | `"USD Coin"` |
|
|
193
|
+
| Base Sepolia | 84532 | `"USDC"` |
|
|
194
|
+
|
|
195
|
+
**This is critical for signature verification!** Always use the correct domain name:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// ✅ Correct - Base Mainnet
|
|
199
|
+
const { typedData } = prepare({
|
|
200
|
+
chainId: 8453,
|
|
201
|
+
token: "USDC", // Resolves to name: "USD Coin"
|
|
202
|
+
from: "0x...",
|
|
203
|
+
to: "0x...",
|
|
204
|
+
value: 1000000n,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ✅ Correct - Base Sepolia
|
|
208
|
+
const { typedData } = prepare({
|
|
209
|
+
chainId: 84532,
|
|
210
|
+
token: "USDC", // Resolves to name: "USDC"
|
|
211
|
+
from: "0x...",
|
|
212
|
+
to: "0x...",
|
|
213
|
+
value: 1000000n,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ❌ Wrong - Signature will fail!
|
|
217
|
+
const { typedData } = prepare({
|
|
218
|
+
chainId: 84532,
|
|
219
|
+
token: "USDC",
|
|
220
|
+
name: "USD Coin", // Override with wrong name
|
|
221
|
+
// ...
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The `prepare()` function automatically uses the correct domain name from the registry.
|
|
226
|
+
|
|
227
|
+
## API Reference
|
|
228
|
+
|
|
229
|
+
### Core Functions
|
|
230
|
+
|
|
231
|
+
- `prepare(params)` - One-call API to build everything
|
|
232
|
+
- `buildMessage(params)` - Build TransferWithAuthorization message
|
|
233
|
+
- `buildTypedData({ domain, message })` - Build EIP-712 typed data
|
|
234
|
+
- `resolveDomain({ chainId, token, ...overrides })` - Resolve EIP-712 domain
|
|
235
|
+
- `erc3009.sign(wallet, domain, message)` - Sign with ethers wallet
|
|
236
|
+
- `verifySignature(domain, message, sig, signer)` - Verify signature
|
|
237
|
+
- `recoverSigner(domain, message, sig)` - Recover signer address
|
|
238
|
+
|
|
239
|
+
### Registry
|
|
240
|
+
|
|
241
|
+
- `registry.getToken(symbol, chainId)` - Get token config
|
|
242
|
+
- `registry.getChain(chainId)` - Get chain config
|
|
243
|
+
- `registry.listChains()` - List all supported chains
|
|
244
|
+
- `registry.listTokensOnChain(chainId)` - List tokens on a chain
|
|
245
|
+
- `registry.isTokenSupported(symbol, chainId)` - Check support
|
|
246
|
+
|
|
247
|
+
### Utils
|
|
248
|
+
|
|
249
|
+
- `randomNonce()` - Generate random bytes32 nonce
|
|
250
|
+
- `nowSeconds()` - Current Unix timestamp
|
|
251
|
+
- `nowPlusSeconds(n)` - Unix timestamp N seconds from now
|
|
252
|
+
- `normalizeAddress(addr)` - Normalize to checksum address
|
|
253
|
+
|
|
254
|
+
## TypeScript Types
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import type {
|
|
258
|
+
TransferWithAuthorization,
|
|
259
|
+
EIP712Domain,
|
|
260
|
+
TokenConfig,
|
|
261
|
+
ChainConfig,
|
|
262
|
+
PrepareParams,
|
|
263
|
+
} from "@seapay-ai/erc3009";
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Advanced: Custom Registry
|
|
267
|
+
|
|
268
|
+
You can extend the registry by directly importing and modifying `TOKENS`:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { TOKENS } from "@seapay-ai/erc3009";
|
|
272
|
+
|
|
273
|
+
// Add your custom token
|
|
274
|
+
TOKENS["MYTOKEN"] = {
|
|
275
|
+
8453: {
|
|
276
|
+
symbol: "MYTOKEN",
|
|
277
|
+
chainId: 8453,
|
|
278
|
+
verifyingContract: "0x...",
|
|
279
|
+
name: "My Token",
|
|
280
|
+
version: "1",
|
|
281
|
+
decimals: 18,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
```
|
|
60
285
|
|
|
61
|
-
|
|
62
|
-
- `EIP712Domain`: The EIP-712 domain structure
|
|
286
|
+
## Examples
|
|
63
287
|
|
|
64
|
-
|
|
288
|
+
See the `/apps/erc3009-relay/src/signer-test.ts` in the monorepo for a complete working example.
|
|
65
289
|
|
|
66
|
-
|
|
67
|
-
- `buildTypes()`: Returns the EIP-712 types for TransferWithAuthorization
|
|
68
|
-
- `buildMessage(message: TransferWithAuthorization)`: Normalizes message values
|
|
69
|
-
- `buildTypedData(params)`: Builds complete typed data for signing
|
|
70
|
-
- `signTransferWithAuthorization(wallet, domain, message)`: Convenience function to sign
|
|
71
|
-
- `message_5_minutes(from, to, value)`: Creates a message valid for 5 minutes
|
|
72
|
-
- `USDC_Domain()`: Returns USDC domain for Base Sepolia (example)
|
|
290
|
+
## Contributing
|
|
73
291
|
|
|
74
|
-
|
|
292
|
+
Contributions welcome! To add support for a new token:
|
|
75
293
|
|
|
76
|
-
|
|
77
|
-
|
|
294
|
+
1. Add token config to `src/registry/tokens/{token}.ts`
|
|
295
|
+
2. Export from `src/registry/tokens/index.ts`
|
|
296
|
+
3. Add to `TOKENS` registry
|
|
78
297
|
|
|
79
298
|
## License
|
|
80
299
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./prepare.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { EIP712Domain, TransferWithAuthorization } from "../types/index.js";
|
|
2
|
+
import type { TypedData } from "../erc3009/typedData.js";
|
|
3
|
+
export type PrepareParams = {
|
|
4
|
+
chainId: number;
|
|
5
|
+
token: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
verifyingContract?: string;
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
value: bigint | number | string;
|
|
12
|
+
ttlSeconds?: number;
|
|
13
|
+
nonce?: string;
|
|
14
|
+
validAfter?: bigint | number | string;
|
|
15
|
+
validBefore?: bigint | number | string;
|
|
16
|
+
};
|
|
17
|
+
export type PrepareResult = {
|
|
18
|
+
domain: EIP712Domain;
|
|
19
|
+
message: TransferWithAuthorization;
|
|
20
|
+
typedData: TypedData;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* One-call convenience helper to prepare complete ERC-3009 typed data
|
|
24
|
+
*
|
|
25
|
+
* This resolves the domain from registry, builds the message with time window,
|
|
26
|
+
* and returns everything needed for signing.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const { domain, message, typedData } = prepare({
|
|
31
|
+
* chainId: 8453,
|
|
32
|
+
* token: "USDC",
|
|
33
|
+
* from: "0x...",
|
|
34
|
+
* to: "0x...",
|
|
35
|
+
* value: 1000000n, // 1 USDC (6 decimals)
|
|
36
|
+
* ttlSeconds: 300, // 5 minutes
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const signature = await wallet.signTypedData(
|
|
40
|
+
* typedData.domain,
|
|
41
|
+
* typedData.types,
|
|
42
|
+
* typedData.message
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function prepare(params: PrepareParams): PrepareResult;
|
|
47
|
+
//# sourceMappingURL=prepare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../src/api/prepare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,yBAAyB,EAE1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAMzD,MAAM,MAAM,aAAa,GAAG;IAE1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,yBAAyB,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAgC5D"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { resolveDomain } from "../domain/resolveDomain.js";
|
|
2
|
+
import { buildMessageWithTTL } from "../erc3009/message.js";
|
|
3
|
+
import { buildTypedData } from "../erc3009/typedData.js";
|
|
4
|
+
import { randomNonce } from "../utils/nonce.js";
|
|
5
|
+
/**
|
|
6
|
+
* One-call convenience helper to prepare complete ERC-3009 typed data
|
|
7
|
+
*
|
|
8
|
+
* This resolves the domain from registry, builds the message with time window,
|
|
9
|
+
* and returns everything needed for signing.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const { domain, message, typedData } = prepare({
|
|
14
|
+
* chainId: 8453,
|
|
15
|
+
* token: "USDC",
|
|
16
|
+
* from: "0x...",
|
|
17
|
+
* to: "0x...",
|
|
18
|
+
* value: 1000000n, // 1 USDC (6 decimals)
|
|
19
|
+
* ttlSeconds: 300, // 5 minutes
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const signature = await wallet.signTypedData(
|
|
23
|
+
* typedData.domain,
|
|
24
|
+
* typedData.types,
|
|
25
|
+
* typedData.message
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function prepare(params) {
|
|
30
|
+
// Resolve domain
|
|
31
|
+
const domainParams = {
|
|
32
|
+
chainId: params.chainId,
|
|
33
|
+
token: params.token,
|
|
34
|
+
name: params.name,
|
|
35
|
+
version: params.version,
|
|
36
|
+
verifyingContract: params.verifyingContract,
|
|
37
|
+
};
|
|
38
|
+
const domain = resolveDomain(domainParams);
|
|
39
|
+
// Build message
|
|
40
|
+
const message = buildMessageWithTTL({
|
|
41
|
+
from: params.from,
|
|
42
|
+
to: params.to,
|
|
43
|
+
value: params.value,
|
|
44
|
+
ttlSeconds: params.ttlSeconds,
|
|
45
|
+
nonce: params.nonce ?? randomNonce(),
|
|
46
|
+
});
|
|
47
|
+
// Override time window if explicitly provided
|
|
48
|
+
if (params.validAfter !== undefined) {
|
|
49
|
+
message.validAfter = BigInt(params.validAfter);
|
|
50
|
+
}
|
|
51
|
+
if (params.validBefore !== undefined) {
|
|
52
|
+
message.validBefore = BigInt(params.validBefore);
|
|
53
|
+
}
|
|
54
|
+
// Build typed data
|
|
55
|
+
const typedData = buildTypedData({ domain, message });
|
|
56
|
+
return { domain, message, typedData };
|
|
57
|
+
}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ERC-3009 namespace with all builder functions
|
|
3
|
+
*/
|
|
4
|
+
import { buildTypes, buildMessage, buildMessageWithTTL, buildTypedData, signTransferWithAuthorization, recoverSigner, verifySignature } from "./erc3009/index.js";
|
|
5
|
+
export declare const erc3009: {
|
|
6
|
+
readonly buildTypes: typeof buildTypes;
|
|
7
|
+
readonly buildMessage: typeof buildMessage;
|
|
8
|
+
readonly buildMessageWithTTL: typeof buildMessageWithTTL;
|
|
9
|
+
readonly buildTypedData: typeof buildTypedData;
|
|
10
|
+
readonly sign: typeof signTransferWithAuthorization;
|
|
11
|
+
readonly recoverSigner: typeof recoverSigner;
|
|
12
|
+
readonly verifySignature: typeof verifySignature;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,cAAc,EACd,6BAA6B,EAC7B,aAAa,EACb,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,eAAO,MAAM,OAAO;;;;;;;;CAQV,CAAC"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ERC-3009 namespace with all builder functions
|
|
3
|
+
*/
|
|
4
|
+
import { buildTypes, buildMessage, buildMessageWithTTL, buildTypedData, signTransferWithAuthorization, recoverSigner, verifySignature, } from "./erc3009/index.js";
|
|
5
|
+
export const erc3009 = {
|
|
6
|
+
buildTypes,
|
|
7
|
+
buildMessage,
|
|
8
|
+
buildMessageWithTTL,
|
|
9
|
+
buildTypedData,
|
|
10
|
+
sign: signTransferWithAuthorization,
|
|
11
|
+
recoverSigner,
|
|
12
|
+
verifySignature,
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize address to checksum format
|
|
3
|
+
*/
|
|
4
|
+
export declare function normalizeAddress(address: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Normalize chainId to number
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeChainId(chainId: number | bigint | string): number;
|
|
9
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/domain/normalize.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASxD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAI1E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getAddress } from "ethers";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize address to checksum format
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeAddress(address) {
|
|
6
|
+
try {
|
|
7
|
+
return getAddress(address);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// If invalid address, return lowercase with 0x
|
|
11
|
+
return address.toLowerCase().startsWith("0x")
|
|
12
|
+
? address.toLowerCase()
|
|
13
|
+
: `0x${address.toLowerCase()}`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalize chainId to number
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeChainId(chainId) {
|
|
20
|
+
if (typeof chainId === "number")
|
|
21
|
+
return chainId;
|
|
22
|
+
if (typeof chainId === "bigint")
|
|
23
|
+
return Number(chainId);
|
|
24
|
+
return parseInt(String(chainId), 10);
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EIP712Domain, ResolveDomainParams } from "../types/domain.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve EIP-712 domain from chain + token with override support
|
|
4
|
+
*
|
|
5
|
+
* Priority:
|
|
6
|
+
* 1. Explicit overrides (name, version, verifyingContract)
|
|
7
|
+
* 2. Registry lookup (by symbol or address)
|
|
8
|
+
* 3. Error if cannot resolve
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveDomain(params: ResolveDomainParams): EIP712Domain;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve domain from token config only (no overrides)
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDomainFromToken(symbol: string, chainId: number): EIP712Domain;
|
|
15
|
+
//# sourceMappingURL=resolveDomain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveDomain.d.ts","sourceRoot":"","sources":["../../src/domain/resolveDomain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAI5E;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,YAAY,CA8BvE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,YAAY,CAcd"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getToken, getTokenByAddress } from "../registry/registry.js";
|
|
2
|
+
import { normalizeAddress, normalizeChainId } from "./normalize.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve EIP-712 domain from chain + token with override support
|
|
5
|
+
*
|
|
6
|
+
* Priority:
|
|
7
|
+
* 1. Explicit overrides (name, version, verifyingContract)
|
|
8
|
+
* 2. Registry lookup (by symbol or address)
|
|
9
|
+
* 3. Error if cannot resolve
|
|
10
|
+
*/
|
|
11
|
+
export function resolveDomain(params) {
|
|
12
|
+
const chainId = normalizeChainId(params.chainId);
|
|
13
|
+
// Try registry lookup
|
|
14
|
+
let config = getToken(params.token, chainId);
|
|
15
|
+
// If not found by symbol, try as address
|
|
16
|
+
if (!config && params.token.startsWith("0x")) {
|
|
17
|
+
config = getTokenByAddress(params.token, chainId);
|
|
18
|
+
}
|
|
19
|
+
// If still not found and no explicit verifyingContract, error
|
|
20
|
+
if (!config && !params.verifyingContract) {
|
|
21
|
+
throw new Error(`Token "${params.token}" not found in registry for chainId ${chainId}. ` +
|
|
22
|
+
`Provide explicit domain parameters or use a supported token.`);
|
|
23
|
+
}
|
|
24
|
+
// Build domain with overrides
|
|
25
|
+
const domain = {
|
|
26
|
+
name: params.name ?? config?.name ?? "USD Coin",
|
|
27
|
+
version: params.version ?? config?.version ?? "2",
|
|
28
|
+
chainId,
|
|
29
|
+
verifyingContract: normalizeAddress(params.verifyingContract ?? config?.verifyingContract ?? params.token),
|
|
30
|
+
};
|
|
31
|
+
return domain;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve domain from token config only (no overrides)
|
|
35
|
+
*/
|
|
36
|
+
export function resolveDomainFromToken(symbol, chainId) {
|
|
37
|
+
const config = getToken(symbol, chainId);
|
|
38
|
+
if (!config) {
|
|
39
|
+
throw new Error(`Token "${symbol}" not configured for chainId ${chainId}`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: config.name,
|
|
43
|
+
version: config.version,
|
|
44
|
+
chainId: config.chainId,
|
|
45
|
+
verifyingContract: config.verifyingContract,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildTypes.d.ts","sourceRoot":"","sources":["../../src/erc3009/buildTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAM7C;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAM7D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TRANSFER_WITH_AUTHORIZATION_TYPE, TRANSFER_WITH_AUTHORIZATION_FIELDS, } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build EIP-712 types object for ERC-3009
|
|
4
|
+
*/
|
|
5
|
+
export function buildTypes() {
|
|
6
|
+
return {
|
|
7
|
+
[TRANSFER_WITH_AUTHORIZATION_TYPE]: [
|
|
8
|
+
...TRANSFER_WITH_AUTHORIZATION_FIELDS,
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TRANSFER_WITH_AUTHORIZATION_TYPE, TRANSFER_WITH_AUTHORIZATION_FIELDS } from "../types/erc3009.js";
|
|
2
|
+
export { TRANSFER_WITH_AUTHORIZATION_TYPE, TRANSFER_WITH_AUTHORIZATION_FIELDS };
|
|
3
|
+
/**
|
|
4
|
+
* EIP-712 primary type for ERC-3009
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRIMARY_TYPE = "TransferWithAuthorization";
|
|
7
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/erc3009/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,kCAAkC,EACnC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,gCAAgC,EAAE,kCAAkC,EAAE,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,YAAY,8BAAmC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TRANSFER_WITH_AUTHORIZATION_TYPE, TRANSFER_WITH_AUTHORIZATION_FIELDS, } from "../types/erc3009.js";
|
|
2
|
+
export { TRANSFER_WITH_AUTHORIZATION_TYPE, TRANSFER_WITH_AUTHORIZATION_FIELDS };
|
|
3
|
+
/**
|
|
4
|
+
* EIP-712 primary type for ERC-3009
|
|
5
|
+
*/
|
|
6
|
+
export const PRIMARY_TYPE = TRANSFER_WITH_AUTHORIZATION_TYPE;
|