@0xkey-io/gas-station 0.1.0

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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +655 -0
  3. package/dist/abi/gas-station.d.ts +500 -0
  4. package/dist/abi/gas-station.d.ts.map +1 -0
  5. package/dist/abi/gas-station.js +252 -0
  6. package/dist/abi/gas-station.js.map +1 -0
  7. package/dist/abi/gas-station.mjs +250 -0
  8. package/dist/abi/gas-station.mjs.map +1 -0
  9. package/dist/abi/reimbursable-gas-station.d.ts +756 -0
  10. package/dist/abi/reimbursable-gas-station.d.ts.map +1 -0
  11. package/dist/abi/reimbursable-gas-station.js +625 -0
  12. package/dist/abi/reimbursable-gas-station.js.map +1 -0
  13. package/dist/abi/reimbursable-gas-station.mjs +623 -0
  14. package/dist/abi/reimbursable-gas-station.mjs.map +1 -0
  15. package/dist/config.d.ts +57 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +59 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/config.mjs +52 -0
  20. package/dist/config.mjs.map +1 -0
  21. package/dist/gasStationClient.d.ts +110 -0
  22. package/dist/gasStationClient.d.ts.map +1 -0
  23. package/dist/gasStationClient.js +415 -0
  24. package/dist/gasStationClient.js.map +1 -0
  25. package/dist/gasStationClient.mjs +413 -0
  26. package/dist/gasStationClient.mjs.map +1 -0
  27. package/dist/gasStationUtils.d.ts +7250 -0
  28. package/dist/gasStationUtils.d.ts.map +1 -0
  29. package/dist/gasStationUtils.js +147 -0
  30. package/dist/gasStationUtils.js.map +1 -0
  31. package/dist/gasStationUtils.mjs +137 -0
  32. package/dist/gasStationUtils.mjs.map +1 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +36 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/index.mjs +8 -0
  38. package/dist/index.mjs.map +1 -0
  39. package/dist/intentBuilder.d.ts +75 -0
  40. package/dist/intentBuilder.d.ts.map +1 -0
  41. package/dist/intentBuilder.js +259 -0
  42. package/dist/intentBuilder.js.map +1 -0
  43. package/dist/intentBuilder.mjs +257 -0
  44. package/dist/intentBuilder.mjs.map +1 -0
  45. package/dist/policyUtils.d.ts +271 -0
  46. package/dist/policyUtils.d.ts.map +1 -0
  47. package/dist/policyUtils.js +386 -0
  48. package/dist/policyUtils.js.map +1 -0
  49. package/dist/policyUtils.mjs +380 -0
  50. package/dist/policyUtils.mjs.map +1 -0
  51. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,655 @@
1
+ # ZeroXKey Gas Station SDK
2
+
3
+ A reusable SDK for implementing gasless transactions using EIP-7702, ZeroXKey wallet management, and your own paymaster. This package provides clean abstractions and utility methods to quickly integrate with ZeroXKey's contracts for sponsored transaction execution.
4
+
5
+ ## What is This?
6
+
7
+ This SDK enables you to:
8
+
9
+ - **Bring your own paymaster** to sponsor user transactions
10
+ - **Use ZeroXKey** for secure wallet management and transaction signing
11
+ - **Execute gasless transactions** via EIP-7702 delegation and EIP-712 signed intents
12
+ - **Support any on-chain action** through generic execution parameters
13
+
14
+ Perfect for building dApps where users don't need ETH for gas, enabling seamless onboarding and better UX.
15
+
16
+ ## How It Works
17
+
18
+ 1. **EIP-7702 Authorization**: One-time setup where an EOA authorizes a gas station contract
19
+ 2. **EIP-712 Signed Intents**: User signs off-chain intents for what they want to execute
20
+ 3. **Paymaster Execution**: Your paymaster submits the transaction and pays for gas
21
+ 4. **ZeroXKey Integration**: All signatures handled securely through ZeroXKey
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Install Dependencies
26
+
27
+ ```bash
28
+ pnpm install @0xkey-io/gas-station @0xkey-io/sdk-server @0xkey-io/viem viem
29
+ ```
30
+
31
+ ### 2. Set Up Environment
32
+
33
+ Create `.env.local`:
34
+
35
+ ```bash
36
+ # ZeroXKey Configuration
37
+ BASE_URL=https://api.0xkey.com
38
+ API_PRIVATE_KEY=your_0xkey_api_private_key
39
+ API_PUBLIC_KEY=your_0xkey_api_public_key
40
+ ORGANIZATION_ID=your_0xkey_organization_id
41
+
42
+ # Wallet Addresses
43
+ EOA_ADDRESS=0x... # User's wallet address
44
+ PAYMASTER_ADDRESS=0x... # Your paymaster address
45
+
46
+ # RPC Configuration
47
+ BASE_RPC_URL=https://mainnet.base.org
48
+ ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/...
49
+
50
+ # Gas Station Contracts (Optional - defaults to deterministic addresses)
51
+ DELEGATE_CONTRACT=0x... # EIP-7702 delegate contract
52
+ EXECUTION_CONTRACT=0x... # Gas Sponsorship entrypoint contract which calls the delegate.
53
+ ```
54
+
55
+ **Note**: The gas station contracts are currently deployed at deterministic addresses on the following chains:
56
+
57
+ - **Ethereum Mainnet**
58
+ - **Base Mainnet**
59
+
60
+ These addresses are built into the SDK, so you don't need to specify them unless you are using custom deployments.
61
+
62
+ ### 3. Initialize and Use
63
+
64
+ ```typescript
65
+ import { GasStationClient } from "@0xkey-io/gas-station";
66
+ import { ZeroXKey } from "@0xkey-io/sdk-server";
67
+ import { createAccount } from "@0xkey-io/viem";
68
+ import { parseEther, parseUnits, createWalletClient, http } from "viem";
69
+ import { base } from "viem/chains";
70
+
71
+ // Initialize ZeroXKey
72
+ const zeroXKeyClient = new ZeroXKey({
73
+ apiBaseUrl: process.env.BASE_URL!,
74
+ apiPrivateKey: process.env.API_PRIVATE_KEY!,
75
+ apiPublicKey: process.env.API_PUBLIC_KEY!,
76
+ defaultOrganizationId: process.env.ORGANIZATION_ID!,
77
+ });
78
+
79
+ // Create ZeroXKey accounts
80
+ const userAccount = await createAccount({
81
+ client: zeroXKeyClient.apiClient(),
82
+ organizationId: process.env.ORGANIZATION_ID!,
83
+ signWith: process.env.EOA_ADDRESS as `0x${string}`,
84
+ });
85
+
86
+ const paymasterAccount = await createAccount({
87
+ client: zeroXKeyClient.apiClient(),
88
+ organizationId: process.env.ORGANIZATION_ID!,
89
+ signWith: process.env.PAYMASTER_ADDRESS as `0x${string}`,
90
+ });
91
+
92
+ // Create viem wallet clients
93
+ const userWalletClient = createWalletClient({
94
+ account: userAccount,
95
+ chain: base,
96
+ transport: http(process.env.BASE_RPC_URL!),
97
+ });
98
+
99
+ const paymasterWalletClient = createWalletClient({
100
+ account: paymasterAccount,
101
+ chain: base,
102
+ transport: http(process.env.BASE_RPC_URL!),
103
+ });
104
+
105
+ // Create Gas Station clients
106
+ const userClient = new GasStationClient({
107
+ walletClient: userWalletClient,
108
+ });
109
+
110
+ const paymasterClient = new GasStationClient({
111
+ walletClient: paymasterWalletClient,
112
+ });
113
+
114
+ // One-time: Authorize the EOA to use gas station
115
+ const authorization = await userClient.signAuthorization();
116
+ await paymasterClient.submitAuthorizations([authorization]);
117
+
118
+ // Execute a gasless ETH transfer
119
+ let nonce = await userClient.getNonce();
120
+ const ethIntent = await userClient
121
+ .createIntent()
122
+ .transferETH("0xRecipient...", parseEther("0.1"))
123
+ .sign(nonce);
124
+ await paymasterClient.execute(ethIntent);
125
+
126
+ // Execute a gasless token transfer
127
+ nonce = await userClient.getNonce();
128
+ const usdcIntent = await userClient
129
+ .createIntent()
130
+ .transferToken(
131
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
132
+ "0xRecipient...",
133
+ parseUnits("10", 6),
134
+ )
135
+ .sign(nonce);
136
+ await paymasterClient.execute(usdcIntent);
137
+ ```
138
+
139
+ ## Core API
140
+
141
+ ### GasStationClient
142
+
143
+ Main client for gas station operations. Each client instance wraps a viem wallet client.
144
+
145
+ #### Constructor
146
+
147
+ ```typescript
148
+ new GasStationClient({
149
+ walletClient: WalletClient, // Viem wallet client (e.g., with ZeroXKey account)
150
+ delegateContract?: `0x${string}`, // Optional: defaults to deterministic address
151
+ executionContract?: `0x${string}`, // Optional: defaults to deterministic address
152
+ })
153
+ ```
154
+
155
+ #### Methods
156
+
157
+ **End User Methods** (call with user client):
158
+
159
+ **`signAuthorization(): Promise<SignedAuthorization>`**
160
+
161
+ - Sign an EIP-7702 authorization for the gas station contract
162
+ - Returns authorization that can be submitted by paymaster
163
+
164
+ **`createIntent(): IntentBuilder`**
165
+
166
+ - Create a builder for composing transactions
167
+ - Intent must be signed before execution
168
+
169
+ **`getNonce(address?: Address): Promise<bigint>`**
170
+
171
+ - Get current nonce from gas station contract
172
+ - Defaults to the signer's address if not specified
173
+
174
+ **Paymaster Methods** (call with paymaster client):
175
+
176
+ **`submitAuthorizations(authorizations: SignedAuthorization[]): Promise<{ txHash, blockNumber }>`**
177
+
178
+ - Submit signed EIP-7702 authorization transaction(s)
179
+ - Supports authorizing multiple EOAs in a single transaction
180
+ - Paymaster pays for gas
181
+
182
+ **`execute(intent: ExecutionIntent): Promise<{ txHash, blockNumber, gasUsed }>`**
183
+
184
+ - Execute a signed intent through the gas station
185
+ - Paymaster pays for gas
186
+
187
+ ### IntentBuilder
188
+
189
+ Composable builder for complex multi-step transactions.
190
+
191
+ ```typescript
192
+ const nonce = await userClient.getNonce();
193
+ const builder = userClient.createIntent();
194
+
195
+ const intent = await builder
196
+ .transferToken(usdcAddress, recipient, amount)
197
+ .sign(nonce);
198
+
199
+ await paymasterClient.execute(intent);
200
+ ```
201
+
202
+ ## Common Use Cases
203
+
204
+ ### Simple Payment
205
+
206
+ ```typescript
207
+ // Gasless USDC payment
208
+ const nonce = await userClient.getNonce();
209
+ const intent = await userClient
210
+ .createIntent()
211
+ .transferToken(usdcAddress, recipientAddress, parseUnits("50", 6))
212
+ .sign(nonce);
213
+
214
+ const result = await paymasterClient.execute(intent);
215
+ console.log(`Payment sent: ${result.txHash}`);
216
+ ```
217
+
218
+ ### Token Approval + DEX Swap
219
+
220
+ ```typescript
221
+ // Step 1: Approve DEX to spend tokens
222
+ let nonce = await userClient.getNonce();
223
+ const approvalIntent = await userClient
224
+ .createIntent()
225
+ .approveToken(usdcAddress, dexAddress, parseUnits("100", 6))
226
+ .sign(nonce);
227
+ await paymasterClient.execute(approvalIntent);
228
+
229
+ // Step 2: Execute swap
230
+ nonce = await userClient.getNonce();
231
+ const swapIntent = await userClient
232
+ .createIntent()
233
+ .callContract({
234
+ contract: dexAddress,
235
+ abi: DEX_ABI,
236
+ functionName: "swapExactTokensForTokens",
237
+ args: [amountIn, amountOutMin, path, recipient, deadline],
238
+ })
239
+ .sign(nonce);
240
+ await paymasterClient.execute(swapIntent);
241
+ ```
242
+
243
+ ### User Onboarding
244
+
245
+ ```typescript
246
+ async function onboardUser(userAddress: string) {
247
+ // Create viem wallet client for user
248
+ const userAccount = await createAccount({
249
+ client: zeroXKeyClient.apiClient(),
250
+ organizationId: ORGANIZATION_ID,
251
+ signWith: userAddress as `0x${string}`,
252
+ });
253
+
254
+ const userWalletClient = createWalletClient({
255
+ account: userAccount,
256
+ chain: base,
257
+ transport: http(BASE_RPC_URL),
258
+ });
259
+
260
+ // Create Gas Station clients
261
+ const userClient = new GasStationClient({
262
+ walletClient: userWalletClient,
263
+ });
264
+
265
+ // Authorize user (paymaster pays)
266
+ const authorization = await userClient.signAuthorization();
267
+ await paymasterClient.submitAuthorizations([authorization]);
268
+
269
+ // User can now execute transactions without ETH
270
+ console.log("✅ User ready for gasless transactions!");
271
+ }
272
+ ```
273
+
274
+ ## Architecture
275
+
276
+ ### Gas Station Pattern
277
+
278
+ 1. **Delegate Contract**: Authorized to EOA via EIP-7702
279
+ 2. **Execution Contract**: Contains execution logic and nonce management
280
+ 3. **EOA**: Signs EIP-712 intents off-chain
281
+ 4. **Paymaster**: Submits transactions and pays gas
282
+
283
+ ### Transaction Flow
284
+
285
+ ```
286
+ User (EOA)
287
+ ↓ Signs EIP-712 intent off-chain
288
+
289
+ SDK (GasStationClient)
290
+ ↓ Builds transaction
291
+
292
+ Paymaster
293
+ ↓ Submits transaction, pays gas
294
+
295
+ Gas Station Contract
296
+ ↓ Validates signature & nonce
297
+ ↓ Executes on behalf of EOA
298
+
299
+ Target Contract (USDC, NFT, DEX, etc.)
300
+ ```
301
+
302
+ ## Chain Support
303
+
304
+ Available presets for quick setup:
305
+
306
+ - **BASE_MAINNET** - Base mainnet (includes USDC address)
307
+ - **ETHEREUM_MAINNET** - Ethereum mainnet (includes USDC address)
308
+
309
+ ```typescript
310
+ // Chain presets are available for quick configuration
311
+ import { CHAIN_PRESETS, GasStationClient } from "@0xkey-io/gas-station";
312
+ import { createWalletClient, http } from "viem";
313
+
314
+ const basePreset = CHAIN_PRESETS.BASE_MAINNET;
315
+ const userWalletClient = createWalletClient({
316
+ account: userAccount,
317
+ chain: basePreset.chain,
318
+ transport: http(basePreset.rpcUrl),
319
+ });
320
+
321
+ const userClient = new GasStationClient({
322
+ walletClient: userWalletClient,
323
+ });
324
+ ```
325
+
326
+ ## Security
327
+
328
+ - **EIP-712 Signed Intents**: All executions require valid typed signatures
329
+ - **EIP-7702 Scoping**: Authorization is per-EOA and can be revoked
330
+ - **Deadline Enforcement**: Each transaction includes a deadline (Unix timestamp) to prevent replay attacks; signatures expire after this time
331
+ - **ZeroXKey Integration**: Private keys never leave ZeroXKey's secure infrastructure
332
+
333
+ ### Security Policies
334
+
335
+ ZeroXKey policies provide additional security layers by restricting what transactions can be signed and executed. The Gas Station SDK includes helpers for creating these policies.
336
+
337
+ #### EOA Intent Signing Policies
338
+
339
+ Restrict what EIP-712 intents the EOA can sign:
340
+
341
+ ```typescript
342
+ import { buildIntentSigningPolicy } from "@0xkey-io/gas-station";
343
+
344
+ // USDC-only policy
345
+ const eoaPolicy = buildIntentSigningPolicy({
346
+ organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
347
+ eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
348
+ restrictions: {
349
+ allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC on Base
350
+ disallowEthTransfer: true, // Disallow ETH transfers
351
+ },
352
+ policyName: "USDC Only Policy",
353
+ });
354
+
355
+ // Resulting policy restricts signing to USDC transfers only:
356
+ // {
357
+ // organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
358
+ // policyName: "USDC Only Policy",
359
+ // effect: "EFFECT_ALLOW",
360
+ // consensus: "approvers.any(user, user.id == '3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a')",
361
+ // condition: "activity.resource == 'PRIVATE_KEY' && " +
362
+ // "activity.action == 'SIGN' && " +
363
+ // "eth.eip_712.primary_type == 'Execution' && " +
364
+ // "(eth.eip_712.message['to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
365
+ // "eth.eip_712.message['value'] == '0'",
366
+ // notes: "Restricts which EIP-712 intents the EOA can sign for gas station execution"
367
+ // }
368
+ ```
369
+
370
+ #### Paymaster Execution Policies
371
+
372
+ Restrict what on-chain transactions the paymaster can submit:
373
+
374
+ ```typescript
375
+ import {
376
+ buildPaymasterExecutionPolicy,
377
+ DEFAULT_EXECUTION_CONTRACT,
378
+ ensureGasStationInterface,
379
+ } from "@0xkey-io/gas-station";
380
+ import { parseGwei, parseEther } from "viem";
381
+
382
+ // First, ensure the Gas Station ABI is uploaded (enables ABI-based policies)
383
+ await ensureGasStationInterface(
384
+ zeroXKeyClient.apiClient(),
385
+ "your-org-id",
386
+ DEFAULT_EXECUTION_CONTRACT,
387
+ undefined,
388
+ "Base Mainnet",
389
+ );
390
+
391
+ // Paymaster protection policy with ETH amount limit
392
+ const paymasterPolicy = buildPaymasterExecutionPolicy({
393
+ organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
394
+ paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
395
+ executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
396
+ restrictions: {
397
+ allowedEOAs: ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"],
398
+ allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
399
+ maxEthAmount: parseEther("0.1"), // Max 0.1 ETH per EOA transaction
400
+ maxGasPrice: parseGwei("50"), // Max 50 gwei gas price
401
+ maxGasLimit: 500000n, // Max 500k gas limit
402
+ },
403
+ policyName: "Paymaster Protection",
404
+ });
405
+
406
+ // Resulting policy uses ABI parsing for direct argument access:
407
+ // {
408
+ // organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
409
+ // policyName: "Paymaster Protection",
410
+ // effect: "EFFECT_ALLOW",
411
+ // consensus: "approvers.any(user, user.id == '8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c')",
412
+ // condition: "activity.resource == 'PRIVATE_KEY' && " +
413
+ // "activity.action == 'SIGN' && " +
414
+ // "eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c' && " +
415
+ // "(eth.tx.contract_call_args['_to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
416
+ // "(eth.tx.contract_call_args['_target'] == '0x742d35cc6634c0532925a3b844bc9e7595f0beb') && " +
417
+ // "eth.tx.contract_call_args['_ethAmount'] <= 100000000000000000 && " +
418
+ // "eth.tx.gasPrice <= 50000000000 && " +
419
+ // "eth.tx.gas <= 500000",
420
+ // notes: "Restricts which transactions the paymaster can execute on the gas station"
421
+ // }
422
+ ```
423
+
424
+ **Note:** The `ensureGasStationInterface()` function uploads the Gas Station ABI to ZeroXKey's Smart Contract Interface feature. This enables ZeroXKey's policy engine to parse the ABI-encoded transaction data and directly compare the `_ethAmount` parameter as a uint256 value, rather than raw bytes. The function checks if the ABI already exists before uploading to avoid duplicates.
425
+
426
+ #### Defense in Depth
427
+
428
+ Combine both policy types for maximum security:
429
+
430
+ ```typescript
431
+ import {
432
+ buildIntentSigningPolicy,
433
+ buildPaymasterExecutionPolicy,
434
+ DEFAULT_EXECUTION_CONTRACT,
435
+ } from "@0xkey-io/gas-station";
436
+ import { parseGwei } from "viem";
437
+
438
+ // Layer 1: EOA can only sign USDC intents
439
+ const eoaPolicy = buildIntentSigningPolicy({
440
+ organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
441
+ eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
442
+ restrictions: {
443
+ allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
444
+ disallowEthTransfer: true, // No ETH transfers
445
+ },
446
+ });
447
+
448
+ // Layer 2: Paymaster can only execute for specific users with gas limits
449
+ const paymasterPolicy = buildPaymasterExecutionPolicy({
450
+ organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
451
+ paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
452
+ executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
453
+ restrictions: {
454
+ allowedEOAs: ["0xUserAddress..."],
455
+ allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
456
+ maxGasPrice: parseGwei("50"),
457
+ maxGasLimit: 500000n,
458
+ },
459
+ });
460
+ ```
461
+
462
+ ### Advanced: Writing Custom Paymaster Policies
463
+
464
+ When using `buildPaymasterExecutionPolicy`, the SDK creates ZeroXKey policies that parse the transaction calldata to enforce restrictions. Understanding the transaction structure allows you to write custom policies for advanced use cases.
465
+
466
+ #### Transaction Data Structure
467
+
468
+ When the paymaster signs an execution transaction calling `execute(address _target, address _to, uint256 _ethAmount, bytes _data)`, the transaction data (`eth.tx.data`) has the following structure:
469
+
470
+ | Position in eth.tx.data | Length | Content | Example |
471
+ | ----------------------- | --------- | ---------------------- | ------------------------------------------------- |
472
+ | `[2..10]` | 8 chars | Function selector | `6c5c2ed9` (execute) |
473
+ | `[10..74]` | 64 chars | \_target (EOA, padded) | `0000...742d35cc6634c0532925a3b844bc9e7595f0beb` |
474
+ | `[74..138]` | 64 chars | \_to (output contract) | `0000...833589fcd6edb6e08f4c7c32d4f71b54bda02913` |
475
+ | `[138..202]` | 64 chars | \_ethAmount (uint256) | `0000...0000` (0 ETH) or amount in wei |
476
+ | `[202..266]` | 64 chars | Offset to \_data bytes | `0000...0080` (128 bytes) |
477
+ | `[266..330]` | 64 chars | Packed data length | `0000...0055` (85 bytes: 65+16+4) |
478
+ | `[330..460]` | 130 chars | Signature (65 bytes) | EIP-712 signature from EOA |
479
+ | `[460..492]` | 32 chars | Nonce (16 bytes) | `00000000000000000000000000000000` |
480
+ | `[492..500]` | 8 chars | Deadline (4 bytes) | `6ac7d340` (Unix timestamp) |
481
+ | `[500+]` | Variable | Call data | Encoded function call for target contract |
482
+
483
+ **Important:** ZeroXKey's `eth.tx.data` includes the `0x` prefix, so positions start at index 2 (after `0x`).
484
+
485
+ **Note:** The deadline is a Unix timestamp that prevents replay attacks by expiring signatures after a specified time. The SDK defaults to 1 hour, customizable with `withDeadline()`.
486
+
487
+ #### Policy Conditions Reference
488
+
489
+ **Check execution contract address:**
490
+
491
+ ```typescript
492
+ eth.tx.to == "0x00000000008c57a1ce37836a5e9d36759d070d8c";
493
+ ```
494
+
495
+ **Check which EOA is executing:**
496
+
497
+ ```typescript
498
+ eth.tx.data[10..74] == '0000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb'
499
+ ```
500
+
501
+ **Check target contract (output contract):**
502
+
503
+ ```typescript
504
+ eth.tx.data[74..138] == '0000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913'
505
+ ```
506
+
507
+ **Check ETH amount:**
508
+
509
+ ```typescript
510
+ eth.tx.data[138..202].hex_to_uint() <= 100000000000000000; // Max 0.1 ETH in wei
511
+ ```
512
+
513
+ **Check gas price:**
514
+
515
+ ```typescript
516
+ eth.tx.gasPrice <= 50000000000; // 50 gwei in wei
517
+ ```
518
+
519
+ **Check gas limit:**
520
+
521
+ ```typescript
522
+ eth.tx.gas <= 500000;
523
+ ```
524
+
525
+ #### Example: Custom Multi-Contract Policy
526
+
527
+ Allow paymaster to execute for USDC or DAI only:
528
+
529
+ ```typescript
530
+ const policy = {
531
+ organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
532
+ policyName: "Stablecoin Execution Policy",
533
+ effect: "EFFECT_ALLOW",
534
+ consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
535
+ condition: [
536
+ "activity.resource == 'PRIVATE_KEY'",
537
+ "activity.action == 'SIGN'",
538
+ "eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
539
+ // Allow USDC or DAI
540
+ "(eth.tx.data[74..138] == '0000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913' || eth.tx.data[74..138] == '00000000000000000000006b175474e89094c44da98b954eedeac495271d0f')",
541
+ // Gas limits
542
+ "eth.tx.gasPrice <= 100000000000",
543
+ "eth.tx.gas <= 500000",
544
+ ].join(" && "),
545
+ notes: "Allow USDC and DAI execution with gas limits",
546
+ };
547
+
548
+ await zeroXKeyClient.apiClient().createPolicy(policy);
549
+ ```
550
+
551
+ #### Example: Whitelist Specific EOAs
552
+
553
+ Only allow execution for approved user wallets:
554
+
555
+ ```typescript
556
+ const approvedEOAs = [
557
+ "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
558
+ "0x1234567890123456789012345678901234567890",
559
+ ];
560
+
561
+ const eoaConditions = approvedEOAs
562
+ .map((addr) => {
563
+ const padded = addr.slice(2).toLowerCase().padStart(64, "0");
564
+ return `eth.tx.data[10..74] == '${padded}'`;
565
+ })
566
+ .join(" || ");
567
+
568
+ const policy = {
569
+ organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
570
+ policyName: "Approved Users Only",
571
+ effect: "EFFECT_ALLOW",
572
+ consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
573
+ condition: [
574
+ "activity.resource == 'PRIVATE_KEY'",
575
+ "activity.action == 'SIGN'",
576
+ "eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
577
+ `(${eoaConditions})`,
578
+ ].join(" && "),
579
+ };
580
+
581
+ await zeroXKeyClient.apiClient().createPolicy(policy);
582
+ ```
583
+
584
+ #### Using the Helper Functions
585
+
586
+ For most cases, use the built-in helpers which handle the byte positions correctly:
587
+
588
+ ```typescript
589
+ import {
590
+ buildPaymasterExecutionPolicy,
591
+ DEFAULT_EXECUTION_CONTRACT,
592
+ } from "@0xkey-io/gas-station";
593
+ import { parseGwei } from "viem";
594
+
595
+ const policy = buildPaymasterExecutionPolicy({
596
+ organizationId: subOrgId,
597
+ paymasterUserId: paymasterUserId,
598
+ executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
599
+ restrictions: {
600
+ allowedContracts: [
601
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
602
+ "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", // DAI
603
+ ],
604
+ allowedEOAs: ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"],
605
+ maxGasPrice: parseGwei("100"),
606
+ maxGasLimit: 500000n,
607
+ },
608
+ policyName: "Production Paymaster Policy",
609
+ });
610
+
611
+ await zeroXKeyClient.apiClient().createPolicy(policy);
612
+ ```
613
+
614
+ The helper functions automatically:
615
+
616
+ - Convert addresses to lowercase
617
+ - Add proper padding for EOA addresses
618
+ - Calculate correct byte positions (accounting for `0x` prefix)
619
+ - Generate proper OR conditions for multiple allowed values
620
+
621
+ ## Troubleshooting
622
+
623
+ ### Authorization Failed
624
+
625
+ - Ensure paymaster has ETH for gas
626
+ - Verify delegate contract address is correct
627
+
628
+ ### Execution Failed
629
+
630
+ - Confirm EOA is authorized (check with `isAuthorized()`)
631
+ - Verify execution contract address matches deployment
632
+ - Check nonce hasn't been reused
633
+ - Ensure target contract call is valid
634
+
635
+ ### Insufficient Funds
636
+
637
+ - EOA must have sufficient token balance for transfers
638
+ - Paymaster must have ETH for gas
639
+
640
+ ### Invalid Signature
641
+
642
+ - Verify EOA address is correct
643
+ - Ensure chain ID matches the network
644
+ - Check intent was signed with correct nonce
645
+
646
+ ## Best Practices
647
+
648
+ 1. **Client Separation**: Create separate client instances for users and paymasters
649
+ 2. **Authorization**: Only call `authorize()` once per EOA
650
+ 3. **Nonce Management**: Always fetch fresh nonce before creating intents
651
+ 4. **Rate Limiting**: Implement paymaster rate limits to prevent abuse
652
+
653
+ ## License
654
+
655
+ See the main SDK repository for license information.