@circle-fin/x402-batching 2.0.3

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.
@@ -0,0 +1,1282 @@
1
+ // src/client/BatchEvmScheme.ts
2
+ import { getAddress } from "viem";
3
+
4
+ // src/constants.ts
5
+ var CIRCLE_BATCHING_NAME = "GatewayWalletBatched";
6
+ var CIRCLE_BATCHING_VERSION = "1";
7
+ var CIRCLE_BATCHING_SCHEME = "exact";
8
+
9
+ // src/detection.ts
10
+ function supportsBatching(requirements) {
11
+ const extra = requirements.extra;
12
+ if (!extra) {
13
+ return false;
14
+ }
15
+ return extra.name === CIRCLE_BATCHING_NAME && extra.version === CIRCLE_BATCHING_VERSION;
16
+ }
17
+ function getVerifyingContract(requirements) {
18
+ if (!supportsBatching(requirements)) {
19
+ return void 0;
20
+ }
21
+ const verifyingContract = requirements.extra?.verifyingContract;
22
+ if (typeof verifyingContract === "string") {
23
+ return verifyingContract;
24
+ }
25
+ return void 0;
26
+ }
27
+
28
+ // src/client/BatchEvmScheme.ts
29
+ var authorizationTypes = {
30
+ TransferWithAuthorization: [
31
+ { name: "from", type: "address" },
32
+ { name: "to", type: "address" },
33
+ { name: "value", type: "uint256" },
34
+ { name: "validAfter", type: "uint256" },
35
+ { name: "validBefore", type: "uint256" },
36
+ { name: "nonce", type: "bytes32" }
37
+ ]
38
+ };
39
+ function createNonce() {
40
+ const bytes = new Uint8Array(32);
41
+ crypto.getRandomValues(bytes);
42
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
43
+ }
44
+ var BatchEvmScheme = class {
45
+ /**
46
+ * Creates a new BatchEvmScheme.
47
+ *
48
+ * @param signer - The EVM signer for signing payment authorizations
49
+ */
50
+ constructor(signer) {
51
+ this.signer = signer;
52
+ }
53
+ scheme = CIRCLE_BATCHING_SCHEME;
54
+ /**
55
+ * Creates a payment payload for Circle batching.
56
+ *
57
+ * @param x402Version - The x402 protocol version
58
+ * @param paymentRequirements - The payment requirements (must be a Circle batching option)
59
+ * @returns Promise resolving to the payment payload
60
+ * @throws Error if requirements are not a Circle batching option
61
+ */
62
+ async createPaymentPayload(x402Version, paymentRequirements) {
63
+ if (!supportsBatching(paymentRequirements)) {
64
+ throw new Error(
65
+ `BatchEvmScheme can only handle Circle batching options. Expected extra.name="${CIRCLE_BATCHING_NAME}" and extra.version="${CIRCLE_BATCHING_VERSION}"`
66
+ );
67
+ }
68
+ const verifyingContract = getVerifyingContract(paymentRequirements);
69
+ if (!verifyingContract) {
70
+ throw new Error(
71
+ "Circle batching option missing extra.verifyingContract (GatewayWallet address)"
72
+ );
73
+ }
74
+ const nonce = createNonce();
75
+ const now = Math.floor(Date.now() / 1e3);
76
+ const authorization = {
77
+ from: this.signer.address,
78
+ to: getAddress(paymentRequirements.payTo),
79
+ value: paymentRequirements.amount,
80
+ validAfter: (now - 600).toString(),
81
+ // 10 minutes before
82
+ validBefore: (now + paymentRequirements.maxTimeoutSeconds).toString(),
83
+ nonce
84
+ };
85
+ const signature = await this.signAuthorization(
86
+ authorization,
87
+ paymentRequirements,
88
+ verifyingContract
89
+ );
90
+ const payload = {
91
+ authorization,
92
+ signature
93
+ };
94
+ return {
95
+ x402Version,
96
+ payload
97
+ };
98
+ }
99
+ /**
100
+ * Sign the EIP-3009 authorization using EIP-712.
101
+ * Uses the GatewayWallet contract as verifyingContract instead of USDC.
102
+ */
103
+ async signAuthorization(authorization, requirements, verifyingContract) {
104
+ if (!requirements.network.startsWith("eip155:")) {
105
+ throw new Error(
106
+ `BatchEvmScheme: unsupported network format "${requirements.network}". Expected "eip155:<chainId>"`
107
+ );
108
+ }
109
+ const chainId = parseInt(requirements.network.split(":")[1]);
110
+ const domain = {
111
+ name: CIRCLE_BATCHING_NAME,
112
+ version: CIRCLE_BATCHING_VERSION,
113
+ chainId,
114
+ verifyingContract: getAddress(verifyingContract)
115
+ };
116
+ const message = {
117
+ from: getAddress(authorization.from),
118
+ to: getAddress(authorization.to),
119
+ value: BigInt(authorization.value),
120
+ validAfter: BigInt(authorization.validAfter),
121
+ validBefore: BigInt(authorization.validBefore),
122
+ nonce: authorization.nonce
123
+ };
124
+ return await this.signer.signTypedData({
125
+ domain,
126
+ types: authorizationTypes,
127
+ primaryType: "TransferWithAuthorization",
128
+ message
129
+ });
130
+ }
131
+ };
132
+
133
+ // src/client/CompositeEvmScheme.ts
134
+ var CompositeEvmScheme = class {
135
+ constructor(batchScheme, fallbackScheme) {
136
+ this.batchScheme = batchScheme;
137
+ this.fallbackScheme = fallbackScheme;
138
+ if (fallbackScheme.scheme !== CIRCLE_BATCHING_SCHEME) {
139
+ throw new Error(
140
+ `CompositeEvmScheme: fallbackScheme.scheme must be "${CIRCLE_BATCHING_SCHEME}", got "${fallbackScheme.scheme}"`
141
+ );
142
+ }
143
+ }
144
+ scheme = CIRCLE_BATCHING_SCHEME;
145
+ async createPaymentPayload(x402Version, paymentRequirements) {
146
+ if (supportsBatching(paymentRequirements)) {
147
+ return this.batchScheme.createPaymentPayload(x402Version, paymentRequirements);
148
+ }
149
+ return this.fallbackScheme.createPaymentPayload(x402Version, paymentRequirements);
150
+ }
151
+ };
152
+
153
+ // src/client/register.ts
154
+ function registerBatchScheme(client, config) {
155
+ const batchScheme = new BatchEvmScheme(config.signer);
156
+ const schemeToRegister = config.fallbackScheme ? new CompositeEvmScheme(batchScheme, config.fallbackScheme) : batchScheme;
157
+ const networks = config.networks ?? ["eip155:*"];
158
+ for (const network of networks) {
159
+ client.register(network, schemeToRegister);
160
+ }
161
+ return schemeToRegister;
162
+ }
163
+
164
+ // src/client/GatewayClient.ts
165
+ import {
166
+ createPublicClient,
167
+ createWalletClient,
168
+ http,
169
+ parseUnits,
170
+ formatUnits,
171
+ pad,
172
+ zeroAddress,
173
+ maxUint256,
174
+ defineChain,
175
+ erc20Abi
176
+ } from "viem";
177
+ import { privateKeyToAccount } from "viem/accounts";
178
+ import * as chains from "viem/chains";
179
+ import { randomBytes } from "crypto";
180
+ var sonicTestnet = defineChain({
181
+ id: 14601,
182
+ name: "Sonic Testnet",
183
+ nativeCurrency: { decimals: 18, name: "Sonic", symbol: "S" },
184
+ rpcUrls: {
185
+ default: { http: ["https://rpc.testnet.soniclabs.com"] }
186
+ },
187
+ blockExplorers: {
188
+ default: { name: "Sonic Testnet Explorer", url: "https://testnet.soniclabs.com/" }
189
+ },
190
+ testnet: true
191
+ });
192
+ var GATEWAY_API_TESTNET = "https://gateway-api-testnet.circle.com/v1";
193
+ var GATEWAY_API_MAINNET = "https://gateway-api.circle.com/v1";
194
+ var GATEWAY_MINTER_ABI = [
195
+ {
196
+ name: "gatewayMint",
197
+ type: "function",
198
+ stateMutability: "nonpayable",
199
+ inputs: [
200
+ { name: "attestationPayload", type: "bytes" },
201
+ { name: "signature", type: "bytes" }
202
+ ],
203
+ outputs: []
204
+ }
205
+ ];
206
+ var GATEWAY_WALLET_ABI = [
207
+ {
208
+ name: "deposit",
209
+ type: "function",
210
+ stateMutability: "nonpayable",
211
+ inputs: [
212
+ { name: "token", type: "address" },
213
+ { name: "value", type: "uint256" }
214
+ ],
215
+ outputs: []
216
+ },
217
+ {
218
+ name: "depositFor",
219
+ type: "function",
220
+ stateMutability: "nonpayable",
221
+ inputs: [
222
+ { name: "token", type: "address" },
223
+ { name: "depositor", type: "address" },
224
+ { name: "value", type: "uint256" }
225
+ ],
226
+ outputs: []
227
+ },
228
+ {
229
+ name: "totalBalance",
230
+ type: "function",
231
+ stateMutability: "view",
232
+ inputs: [
233
+ { name: "token", type: "address" },
234
+ { name: "depositor", type: "address" }
235
+ ],
236
+ outputs: [{ name: "", type: "uint256" }]
237
+ },
238
+ {
239
+ name: "availableBalance",
240
+ type: "function",
241
+ stateMutability: "view",
242
+ inputs: [
243
+ { name: "token", type: "address" },
244
+ { name: "depositor", type: "address" }
245
+ ],
246
+ outputs: [{ name: "", type: "uint256" }]
247
+ },
248
+ {
249
+ name: "withdrawingBalance",
250
+ type: "function",
251
+ stateMutability: "view",
252
+ inputs: [
253
+ { name: "token", type: "address" },
254
+ { name: "depositor", type: "address" }
255
+ ],
256
+ outputs: [{ name: "", type: "uint256" }]
257
+ },
258
+ {
259
+ name: "withdrawableBalance",
260
+ type: "function",
261
+ stateMutability: "view",
262
+ inputs: [
263
+ { name: "token", type: "address" },
264
+ { name: "depositor", type: "address" }
265
+ ],
266
+ outputs: [{ name: "", type: "uint256" }]
267
+ },
268
+ {
269
+ name: "withdrawalDelay",
270
+ type: "function",
271
+ stateMutability: "view",
272
+ inputs: [],
273
+ outputs: [{ name: "", type: "uint256" }]
274
+ },
275
+ {
276
+ name: "withdrawalBlock",
277
+ type: "function",
278
+ stateMutability: "view",
279
+ inputs: [
280
+ { name: "token", type: "address" },
281
+ { name: "depositor", type: "address" }
282
+ ],
283
+ outputs: [{ name: "", type: "uint256" }]
284
+ },
285
+ {
286
+ name: "initiateWithdrawal",
287
+ type: "function",
288
+ stateMutability: "nonpayable",
289
+ inputs: [
290
+ { name: "token", type: "address" },
291
+ { name: "value", type: "uint256" }
292
+ ],
293
+ outputs: []
294
+ },
295
+ {
296
+ name: "withdraw",
297
+ type: "function",
298
+ stateMutability: "nonpayable",
299
+ inputs: [{ name: "token", type: "address" }],
300
+ outputs: []
301
+ }
302
+ ];
303
+ var GATEWAY_DOMAINS = {
304
+ // Testnet
305
+ arbitrumSepolia: 3,
306
+ arcTestnet: 26,
307
+ avalancheFuji: 1,
308
+ baseSepolia: 6,
309
+ sepolia: 0,
310
+ hyperEvmTestnet: 19,
311
+ optimismSepolia: 2,
312
+ polygonAmoy: 7,
313
+ seiAtlantic: 16,
314
+ sonicTestnet: 13,
315
+ unichainSepolia: 10,
316
+ worldChainSepolia: 14,
317
+ // Mainnet
318
+ arbitrum: 3,
319
+ avalanche: 1,
320
+ base: 6,
321
+ mainnet: 0,
322
+ hyperEvm: 19,
323
+ optimism: 2,
324
+ polygon: 7,
325
+ sei: 16,
326
+ sonic: 13,
327
+ unichain: 10,
328
+ worldChain: 14
329
+ };
330
+ var TESTNET_GATEWAY_WALLET = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
331
+ var TESTNET_GATEWAY_MINTER = "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B";
332
+ var MAINNET_GATEWAY_WALLET = "0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE";
333
+ var MAINNET_GATEWAY_MINTER = "0x2222222d7164433c4C09B0b0D809a9b52C04C205";
334
+ var CHAIN_CONFIGS = {
335
+ // Testnet chains
336
+ arbitrumSepolia: {
337
+ chain: chains.arbitrumSepolia,
338
+ domain: GATEWAY_DOMAINS.arbitrumSepolia,
339
+ usdc: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
340
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
341
+ gatewayMinter: TESTNET_GATEWAY_MINTER
342
+ },
343
+ arcTestnet: {
344
+ chain: chains.arcTestnet,
345
+ domain: GATEWAY_DOMAINS.arcTestnet,
346
+ usdc: "0x3600000000000000000000000000000000000000",
347
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
348
+ gatewayMinter: TESTNET_GATEWAY_MINTER,
349
+ rpcUrl: "https://rpc.testnet.arc.network"
350
+ },
351
+ avalancheFuji: {
352
+ chain: chains.avalancheFuji,
353
+ domain: GATEWAY_DOMAINS.avalancheFuji,
354
+ usdc: "0x5425890298aed601595a70AB815c96711a31Bc65",
355
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
356
+ gatewayMinter: TESTNET_GATEWAY_MINTER
357
+ },
358
+ baseSepolia: {
359
+ chain: chains.baseSepolia,
360
+ domain: GATEWAY_DOMAINS.baseSepolia,
361
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
362
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
363
+ gatewayMinter: TESTNET_GATEWAY_MINTER,
364
+ rpcUrl: "https://sepolia-preconf.base.org"
365
+ },
366
+ sepolia: {
367
+ chain: chains.sepolia,
368
+ domain: GATEWAY_DOMAINS.sepolia,
369
+ usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
370
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
371
+ gatewayMinter: TESTNET_GATEWAY_MINTER
372
+ },
373
+ hyperEvmTestnet: {
374
+ chain: chains.hyperliquidEvmTestnet,
375
+ domain: GATEWAY_DOMAINS.hyperEvmTestnet,
376
+ usdc: "0x2B3370eE501B4a559b57D449569354196457D8Ab",
377
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
378
+ gatewayMinter: TESTNET_GATEWAY_MINTER
379
+ },
380
+ optimismSepolia: {
381
+ chain: chains.optimismSepolia,
382
+ domain: GATEWAY_DOMAINS.optimismSepolia,
383
+ usdc: "0x5fd84259d66Cd46123540766Be93DFE6D43130D7",
384
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
385
+ gatewayMinter: TESTNET_GATEWAY_MINTER
386
+ },
387
+ polygonAmoy: {
388
+ chain: chains.polygonAmoy,
389
+ domain: GATEWAY_DOMAINS.polygonAmoy,
390
+ usdc: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
391
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
392
+ gatewayMinter: TESTNET_GATEWAY_MINTER
393
+ },
394
+ seiAtlantic: {
395
+ chain: chains.seiTestnet,
396
+ domain: GATEWAY_DOMAINS.seiAtlantic,
397
+ usdc: "0x4fCF1784B31630811181f670Aea7A7bEF803eaED",
398
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
399
+ gatewayMinter: TESTNET_GATEWAY_MINTER
400
+ },
401
+ sonicTestnet: {
402
+ chain: sonicTestnet,
403
+ domain: GATEWAY_DOMAINS.sonicTestnet,
404
+ usdc: "0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51",
405
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
406
+ gatewayMinter: TESTNET_GATEWAY_MINTER
407
+ },
408
+ unichainSepolia: {
409
+ chain: chains.unichainSepolia,
410
+ domain: GATEWAY_DOMAINS.unichainSepolia,
411
+ usdc: "0x31d0220469e10c4E71834a79b1f276d740d3768F",
412
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
413
+ gatewayMinter: TESTNET_GATEWAY_MINTER
414
+ },
415
+ worldChainSepolia: {
416
+ chain: chains.worldchainSepolia,
417
+ domain: GATEWAY_DOMAINS.worldChainSepolia,
418
+ usdc: "0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88",
419
+ gatewayWallet: TESTNET_GATEWAY_WALLET,
420
+ gatewayMinter: TESTNET_GATEWAY_MINTER
421
+ },
422
+ // Mainnet chains
423
+ arbitrum: {
424
+ chain: chains.arbitrum,
425
+ domain: GATEWAY_DOMAINS.arbitrum,
426
+ usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
427
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
428
+ gatewayMinter: MAINNET_GATEWAY_MINTER
429
+ },
430
+ avalanche: {
431
+ chain: chains.avalanche,
432
+ domain: GATEWAY_DOMAINS.avalanche,
433
+ usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
434
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
435
+ gatewayMinter: MAINNET_GATEWAY_MINTER
436
+ },
437
+ base: {
438
+ chain: chains.base,
439
+ domain: GATEWAY_DOMAINS.base,
440
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
441
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
442
+ gatewayMinter: MAINNET_GATEWAY_MINTER
443
+ },
444
+ mainnet: {
445
+ chain: chains.mainnet,
446
+ domain: GATEWAY_DOMAINS.mainnet,
447
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
448
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
449
+ gatewayMinter: MAINNET_GATEWAY_MINTER
450
+ },
451
+ hyperEvm: {
452
+ chain: chains.hyperEvm,
453
+ domain: GATEWAY_DOMAINS.hyperEvm,
454
+ usdc: "0xb88339CB7199b77E23DB6E890353E22632Ba630f",
455
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
456
+ gatewayMinter: MAINNET_GATEWAY_MINTER
457
+ },
458
+ optimism: {
459
+ chain: chains.optimism,
460
+ domain: GATEWAY_DOMAINS.optimism,
461
+ usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
462
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
463
+ gatewayMinter: MAINNET_GATEWAY_MINTER
464
+ },
465
+ polygon: {
466
+ chain: chains.polygon,
467
+ domain: GATEWAY_DOMAINS.polygon,
468
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
469
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
470
+ gatewayMinter: MAINNET_GATEWAY_MINTER
471
+ },
472
+ sei: {
473
+ chain: chains.sei,
474
+ domain: GATEWAY_DOMAINS.sei,
475
+ usdc: "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392",
476
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
477
+ gatewayMinter: MAINNET_GATEWAY_MINTER
478
+ },
479
+ sonic: {
480
+ chain: chains.sonic,
481
+ domain: GATEWAY_DOMAINS.sonic,
482
+ usdc: "0x29219dd400f2Bf60E5a23d13Be72B486D4038894",
483
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
484
+ gatewayMinter: MAINNET_GATEWAY_MINTER
485
+ },
486
+ unichain: {
487
+ chain: chains.unichain,
488
+ domain: GATEWAY_DOMAINS.unichain,
489
+ usdc: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
490
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
491
+ gatewayMinter: MAINNET_GATEWAY_MINTER
492
+ },
493
+ worldChain: {
494
+ chain: chains.worldchain,
495
+ domain: GATEWAY_DOMAINS.worldChain,
496
+ usdc: "0x79A02482A880bCe3F13E09da970dC34dB4cD24D1",
497
+ gatewayWallet: MAINNET_GATEWAY_WALLET,
498
+ gatewayMinter: MAINNET_GATEWAY_MINTER
499
+ }
500
+ };
501
+ var GatewayClient = class {
502
+ chainConfig;
503
+ account;
504
+ publicClient;
505
+ walletClient;
506
+ /**
507
+ * Creates a new GatewayClient.
508
+ *
509
+ * @param config - Configuration including chain and private key
510
+ */
511
+ constructor(config) {
512
+ const chainConfig = CHAIN_CONFIGS[config.chain];
513
+ if (!chainConfig) {
514
+ throw new Error(`Unsupported chain: ${config.chain}`);
515
+ }
516
+ this.chainConfig = chainConfig;
517
+ this.account = privateKeyToAccount(config.privateKey);
518
+ const rpcUrl = config.rpcUrl ?? chainConfig.rpcUrl;
519
+ const transport = rpcUrl ? http(rpcUrl) : http();
520
+ this.publicClient = createPublicClient({
521
+ chain: chainConfig.chain,
522
+ transport
523
+ });
524
+ this.walletClient = createWalletClient({
525
+ account: this.account,
526
+ chain: chainConfig.chain,
527
+ transport
528
+ });
529
+ }
530
+ /**
531
+ * Get the account address.
532
+ */
533
+ get address() {
534
+ return this.account.address;
535
+ }
536
+ /**
537
+ * Get the chain name.
538
+ */
539
+ get chainName() {
540
+ return this.chainConfig.chain.name;
541
+ }
542
+ /**
543
+ * Get the Gateway domain for this chain.
544
+ */
545
+ get domain() {
546
+ return this.chainConfig.domain;
547
+ }
548
+ /**
549
+ * Get the chain name key for this client.
550
+ */
551
+ getChainName() {
552
+ for (const [name, config] of Object.entries(CHAIN_CONFIGS)) {
553
+ if (config.domain === this.chainConfig.domain && config.chain.id === this.chainConfig.chain.id) {
554
+ return name;
555
+ }
556
+ }
557
+ throw new Error("Unknown chain configuration");
558
+ }
559
+ /**
560
+ * Get the USDC balance of the account (not in Gateway).
561
+ *
562
+ * @param address - Optional address to check (defaults to account address)
563
+ * @returns USDC balance in atomic units and formatted
564
+ */
565
+ async getUsdcBalance(address) {
566
+ const target = address ?? this.account.address;
567
+ const balance = await this.publicClient.readContract({
568
+ address: this.chainConfig.usdc,
569
+ abi: erc20Abi,
570
+ functionName: "balanceOf",
571
+ args: [target]
572
+ });
573
+ return {
574
+ balance,
575
+ formatted: formatUnits(balance, 6)
576
+ };
577
+ }
578
+ /**
579
+ * Get all balances (wallet + gateway) in one call.
580
+ *
581
+ * @param address - Optional address to check (defaults to account address)
582
+ * @returns All balances
583
+ */
584
+ async getBalances(address) {
585
+ const target = address ?? this.account.address;
586
+ const [wallet, gateway] = await Promise.all([
587
+ this.getUsdcBalance(target),
588
+ this.getGatewayBalance(target)
589
+ ]);
590
+ return {
591
+ wallet,
592
+ gateway
593
+ };
594
+ }
595
+ /**
596
+ * Get the Gateway balance for an address.
597
+ *
598
+ * @param address - Optional address to check (defaults to account address)
599
+ * @returns Gateway balance information
600
+ * @deprecated Use `getBalances()` instead for a unified view
601
+ */
602
+ async getBalance(address) {
603
+ return this.getGatewayBalance(address);
604
+ }
605
+ /**
606
+ * Get the Gateway balance for an address via Gateway API.
607
+ */
608
+ async getGatewayBalance(address) {
609
+ const target = address ?? this.account.address;
610
+ const apiBaseUrl = this.isTestnet() ? GATEWAY_API_TESTNET : GATEWAY_API_MAINNET;
611
+ const response = await fetch(`${apiBaseUrl}/balances`, {
612
+ method: "POST",
613
+ headers: { "Content-Type": "application/json" },
614
+ body: JSON.stringify({
615
+ token: "USDC",
616
+ sources: [{ depositor: target, domain: this.chainConfig.domain }]
617
+ })
618
+ });
619
+ const data = await response.json();
620
+ if (!response.ok) {
621
+ throw new Error(
622
+ `Gateway API balance fetch failed: ${data.message ?? response.statusText}`
623
+ );
624
+ }
625
+ if (!data.balances || data.balances.length === 0) {
626
+ throw new Error("Gateway API returned no balances for the depositor.");
627
+ }
628
+ const balanceData = data.balances[0];
629
+ const available = parseUnits(balanceData.balance, 6);
630
+ const withdrawing = parseUnits(balanceData.withdrawing ?? "0", 6);
631
+ const withdrawable = parseUnits(balanceData.withdrawable ?? "0", 6);
632
+ const total = available + withdrawing;
633
+ return {
634
+ total,
635
+ available,
636
+ withdrawing,
637
+ withdrawable,
638
+ formattedTotal: formatUnits(total, 6),
639
+ formattedAvailable: formatUnits(available, 6),
640
+ formattedWithdrawing: formatUnits(withdrawing, 6),
641
+ formattedWithdrawable: formatUnits(withdrawable, 6)
642
+ };
643
+ }
644
+ /**
645
+ * Deposit USDC into the Gateway Wallet.
646
+ *
647
+ * This method first approves the Gateway Wallet contract to spend USDC,
648
+ * then deposits the specified amount.
649
+ *
650
+ * @param amount - Amount of USDC to deposit (as a decimal string, e.g., "10.5")
651
+ * @param options - Optional deposit options
652
+ * @returns Deposit result with transaction hashes
653
+ */
654
+ async deposit(amount, options) {
655
+ const depositAmount = parseUnits(amount, 6);
656
+ const approveAmount = parseUnits(options?.approveAmount ?? amount, 6);
657
+ const { balance } = await this.getUsdcBalance();
658
+ if (balance < depositAmount) {
659
+ throw new Error(
660
+ `Insufficient USDC balance. Have: ${formatUnits(balance, 6)}, Need: ${amount}`
661
+ );
662
+ }
663
+ let approvalTxHash;
664
+ if (!options?.skipApprovalCheck) {
665
+ const allowance = await this.publicClient.readContract({
666
+ address: this.chainConfig.usdc,
667
+ abi: erc20Abi,
668
+ functionName: "allowance",
669
+ args: [this.account.address, this.chainConfig.gatewayWallet]
670
+ });
671
+ if (allowance < depositAmount) {
672
+ approvalTxHash = await this.walletClient.writeContract({
673
+ address: this.chainConfig.usdc,
674
+ abi: erc20Abi,
675
+ functionName: "approve",
676
+ args: [this.chainConfig.gatewayWallet, approveAmount]
677
+ });
678
+ try {
679
+ await this.publicClient.waitForTransactionReceipt({ hash: approvalTxHash });
680
+ } catch (err) {
681
+ throw new Error(`Approval transaction failed: ${approvalTxHash}`, {
682
+ cause: err
683
+ });
684
+ }
685
+ }
686
+ }
687
+ const depositTxHash = await this.walletClient.writeContract({
688
+ address: this.chainConfig.gatewayWallet,
689
+ abi: GATEWAY_WALLET_ABI,
690
+ functionName: "deposit",
691
+ args: [this.chainConfig.usdc, depositAmount],
692
+ gas: 120000n
693
+ });
694
+ try {
695
+ await this.publicClient.waitForTransactionReceipt({ hash: depositTxHash });
696
+ } catch (err) {
697
+ throw new Error(`Deposit transaction failed: ${depositTxHash}`, {
698
+ cause: err
699
+ });
700
+ }
701
+ return {
702
+ approvalTxHash,
703
+ depositTxHash,
704
+ amount: depositAmount,
705
+ formattedAmount: amount,
706
+ depositor: this.account.address
707
+ };
708
+ }
709
+ /**
710
+ * Deposit USDC into the Gateway Wallet on behalf of another address.
711
+ *
712
+ * The resulting balance belongs to the specified depositor address,
713
+ * not the caller.
714
+ *
715
+ * @param amount - Amount of USDC to deposit (as a decimal string)
716
+ * @param depositor - Address that will own the resulting balance
717
+ * @param options - Optional deposit options
718
+ * @returns Deposit result with transaction hashes
719
+ */
720
+ async depositFor(amount, depositor, options) {
721
+ const depositAmount = parseUnits(amount, 6);
722
+ const approveAmount = parseUnits(options?.approveAmount ?? amount, 6);
723
+ const { balance } = await this.getUsdcBalance();
724
+ if (balance < depositAmount) {
725
+ throw new Error(
726
+ `Insufficient USDC balance. Have: ${formatUnits(balance, 6)}, Need: ${amount}`
727
+ );
728
+ }
729
+ let approvalTxHash;
730
+ if (!options?.skipApprovalCheck) {
731
+ const allowance = await this.publicClient.readContract({
732
+ address: this.chainConfig.usdc,
733
+ abi: erc20Abi,
734
+ functionName: "allowance",
735
+ args: [this.account.address, this.chainConfig.gatewayWallet]
736
+ });
737
+ if (allowance < depositAmount) {
738
+ approvalTxHash = await this.walletClient.writeContract({
739
+ address: this.chainConfig.usdc,
740
+ abi: erc20Abi,
741
+ functionName: "approve",
742
+ args: [this.chainConfig.gatewayWallet, approveAmount]
743
+ });
744
+ try {
745
+ await this.publicClient.waitForTransactionReceipt({ hash: approvalTxHash });
746
+ } catch (err) {
747
+ throw new Error(`Approval transaction failed: ${approvalTxHash}`, {
748
+ cause: err
749
+ });
750
+ }
751
+ }
752
+ }
753
+ const depositTxHash = await this.walletClient.writeContract({
754
+ address: this.chainConfig.gatewayWallet,
755
+ abi: GATEWAY_WALLET_ABI,
756
+ functionName: "depositFor",
757
+ args: [this.chainConfig.usdc, depositor, depositAmount],
758
+ gas: 120000n
759
+ });
760
+ try {
761
+ await this.publicClient.waitForTransactionReceipt({ hash: depositTxHash });
762
+ } catch (err) {
763
+ throw new Error(`Deposit transaction failed: ${depositTxHash}`, {
764
+ cause: err
765
+ });
766
+ }
767
+ return {
768
+ approvalTxHash,
769
+ depositTxHash,
770
+ amount: depositAmount,
771
+ formattedAmount: amount,
772
+ depositor
773
+ };
774
+ }
775
+ // ============================================================================
776
+ // PAYMENT (Pay for x402 resources)
777
+ // ============================================================================
778
+ /**
779
+ * Check if a URL supports Gateway batching before paying.
780
+ *
781
+ * @param url - The URL to check
782
+ * @returns Whether batching is supported and payment requirements
783
+ */
784
+ async supports(url) {
785
+ try {
786
+ const response = await fetch(url);
787
+ if (response.status !== 402) {
788
+ return { supported: false, error: "Resource does not require payment (not 402)" };
789
+ }
790
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
791
+ if (!paymentRequiredHeader) {
792
+ return {
793
+ supported: false,
794
+ error: "Missing PAYMENT-REQUIRED header in 402 response"
795
+ };
796
+ }
797
+ const data = JSON.parse(
798
+ Buffer.from(paymentRequiredHeader, "base64").toString("utf-8")
799
+ );
800
+ const accepts = data.accepts;
801
+ if (!accepts || accepts.length === 0) {
802
+ return { supported: false, error: "No payment options in 402 response" };
803
+ }
804
+ const expectedNetwork = `eip155:${this.chainConfig.chain.id}`;
805
+ const batchingOption = accepts.find((opt) => {
806
+ const extra = opt.extra;
807
+ return opt.network === expectedNetwork && extra?.name === "GatewayWalletBatched" && extra?.version === "1" && typeof extra?.verifyingContract === "string";
808
+ });
809
+ if (!batchingOption) {
810
+ return {
811
+ supported: false,
812
+ error: `No Gateway batching option available for network ${expectedNetwork} (${this.chainConfig.chain.name})`
813
+ };
814
+ }
815
+ return { supported: true, requirements: batchingOption };
816
+ } catch (error) {
817
+ return { supported: false, error: error.message };
818
+ }
819
+ }
820
+ /**
821
+ * Pay for an x402-protected resource.
822
+ *
823
+ * This method handles the full 402 payment flow automatically:
824
+ * 1. Makes initial request
825
+ * 2. If 402, finds Gateway batching option
826
+ * 3. Signs payment authorization
827
+ * 4. Retries with Payment-Signature header
828
+ *
829
+ * @param url - The URL to pay for
830
+ * @param options - Optional request options
831
+ * @returns The response data and payment info
832
+ *
833
+ * @example
834
+ * ```typescript
835
+ * const { data, amount } = await gateway.pay('https://api.example.com/resource');
836
+ * console.log('Paid', amount, 'USDC for:', data);
837
+ * ```
838
+ */
839
+ async pay(url, options) {
840
+ const method = options?.method ?? "GET";
841
+ const headers = {
842
+ "Content-Type": "application/json",
843
+ ...options?.headers
844
+ };
845
+ const serializedBody = options?.body !== void 0 ? typeof options.body === "string" ? options.body : JSON.stringify(options.body) : void 0;
846
+ const initialResponse = await fetch(url, {
847
+ method,
848
+ headers,
849
+ body: serializedBody
850
+ });
851
+ if (initialResponse.status !== 402) {
852
+ if (initialResponse.ok) {
853
+ const data2 = await initialResponse.json();
854
+ return {
855
+ data: data2,
856
+ amount: 0n,
857
+ formattedAmount: "0",
858
+ transaction: "",
859
+ status: initialResponse.status
860
+ };
861
+ }
862
+ throw new Error(`Request failed with status ${initialResponse.status}`);
863
+ }
864
+ const paymentRequiredHeader = initialResponse.headers.get("PAYMENT-REQUIRED");
865
+ if (!paymentRequiredHeader) {
866
+ throw new Error("Missing PAYMENT-REQUIRED header in 402 response");
867
+ }
868
+ const paymentRequired = JSON.parse(
869
+ Buffer.from(paymentRequiredHeader, "base64").toString("utf-8")
870
+ );
871
+ const accepts = paymentRequired.accepts;
872
+ if (!accepts || accepts.length === 0) {
873
+ throw new Error("No payment options in 402 response");
874
+ }
875
+ const expectedNetwork = `eip155:${this.chainConfig.chain.id}`;
876
+ const batchingOption = accepts.find((opt) => {
877
+ const extra = opt.extra;
878
+ return opt.network === expectedNetwork && extra?.name === "GatewayWalletBatched" && extra?.version === "1" && typeof extra?.verifyingContract === "string";
879
+ });
880
+ if (!batchingOption) {
881
+ throw new Error(
882
+ `No Gateway batching option available for network ${expectedNetwork} (${this.chainConfig.chain.name}). The seller may not support this chain. Use supports() to check first.`
883
+ );
884
+ }
885
+ const paymentPayload = await this.createPaymentPayload(
886
+ paymentRequired.x402Version ?? 2,
887
+ batchingOption
888
+ );
889
+ const paymentHeader = Buffer.from(
890
+ JSON.stringify({
891
+ ...paymentPayload,
892
+ resource: paymentRequired.resource,
893
+ accepted: batchingOption
894
+ })
895
+ ).toString("base64");
896
+ const paidResponse = await fetch(url, {
897
+ method,
898
+ headers: {
899
+ ...headers,
900
+ "Payment-Signature": paymentHeader
901
+ },
902
+ body: serializedBody
903
+ });
904
+ if (!paidResponse.ok) {
905
+ const error = await paidResponse.json().catch(() => ({}));
906
+ throw new Error(
907
+ `Payment failed: ${error.error || paidResponse.statusText}`
908
+ );
909
+ }
910
+ const data = await paidResponse.json();
911
+ const amount = BigInt(batchingOption.amount);
912
+ let transaction = "";
913
+ const paymentResponseHeader = paidResponse.headers.get("PAYMENT-RESPONSE");
914
+ if (paymentResponseHeader) {
915
+ const settleResponse = JSON.parse(
916
+ Buffer.from(paymentResponseHeader, "base64").toString("utf-8")
917
+ );
918
+ transaction = settleResponse.transaction ?? "";
919
+ }
920
+ return {
921
+ data,
922
+ amount,
923
+ formattedAmount: formatUnits(amount, 6),
924
+ transaction,
925
+ status: paidResponse.status
926
+ };
927
+ }
928
+ /**
929
+ * Create a payment payload for x402 (low-level).
930
+ */
931
+ async createPaymentPayload(x402Version, requirements) {
932
+ const extra = requirements.extra;
933
+ const verifyingContract = extra.verifyingContract;
934
+ const network = requirements.network;
935
+ const chainId = parseInt(network.split(":")[1]);
936
+ const nonce = `0x${randomBytes(32).toString("hex")}`;
937
+ const now = Math.floor(Date.now() / 1e3);
938
+ const authorization = {
939
+ from: this.account.address,
940
+ to: requirements.payTo,
941
+ value: requirements.amount,
942
+ validAfter: (now - 600).toString(),
943
+ validBefore: (now + requirements.maxTimeoutSeconds).toString(),
944
+ nonce
945
+ };
946
+ const signature = await this.account.signTypedData({
947
+ domain: {
948
+ name: "GatewayWalletBatched",
949
+ version: "1",
950
+ chainId,
951
+ verifyingContract
952
+ },
953
+ types: {
954
+ TransferWithAuthorization: [
955
+ { name: "from", type: "address" },
956
+ { name: "to", type: "address" },
957
+ { name: "value", type: "uint256" },
958
+ { name: "validAfter", type: "uint256" },
959
+ { name: "validBefore", type: "uint256" },
960
+ { name: "nonce", type: "bytes32" }
961
+ ]
962
+ },
963
+ primaryType: "TransferWithAuthorization",
964
+ message: {
965
+ from: authorization.from,
966
+ to: authorization.to,
967
+ value: BigInt(authorization.value),
968
+ validAfter: BigInt(authorization.validAfter),
969
+ validBefore: BigInt(authorization.validBefore),
970
+ nonce: authorization.nonce
971
+ }
972
+ });
973
+ return {
974
+ x402Version,
975
+ payload: { authorization, signature }
976
+ };
977
+ }
978
+ // ============================================================================
979
+ // WITHDRAW (Instant way to get USDC out)
980
+ // ============================================================================
981
+ /**
982
+ * Withdraw USDC from Gateway to your wallet.
983
+ *
984
+ * By default, withdraws to the same chain (instant, no 7-day delay).
985
+ * Optionally, withdraw to a different chain (requires gas on destination).
986
+ *
987
+ * @param amount - Amount of USDC to withdraw (as a decimal string)
988
+ * @param options - Optional withdrawal options
989
+ * @returns Withdrawal result with transaction hash
990
+ *
991
+ * @example
992
+ * ```typescript
993
+ * // Withdraw to same chain (instant!)
994
+ * await gateway.withdraw('50');
995
+ *
996
+ * // Withdraw to Base Sepolia (requires ETH on Base for gas)
997
+ * await gateway.withdraw('25', { chain: 'baseSepolia' });
998
+ * ```
999
+ */
1000
+ async withdraw(amount, options) {
1001
+ const destinationChainName = options?.chain ?? this.getChainName();
1002
+ const destConfig = CHAIN_CONFIGS[destinationChainName];
1003
+ if (!destConfig) {
1004
+ throw new Error(`Unsupported destination chain: ${destinationChainName}`);
1005
+ }
1006
+ const withdrawAmount = parseUnits(amount, 6);
1007
+ const recipient = options?.recipient ?? this.account.address;
1008
+ const maxFee = parseUnits(options?.maxFee ?? "2.01", 6);
1009
+ const balances = await this.getBalances();
1010
+ if (balances.gateway.available < withdrawAmount) {
1011
+ throw new Error(
1012
+ `Insufficient available balance. Have: ${balances.gateway.formattedAvailable}, Need: ${amount}`
1013
+ );
1014
+ }
1015
+ const burnIntent = this.createBurnIntent(
1016
+ this.chainConfig,
1017
+ destConfig,
1018
+ withdrawAmount,
1019
+ recipient,
1020
+ maxFee
1021
+ );
1022
+ const signature = await this.account.signTypedData({
1023
+ domain: { name: "GatewayWallet", version: "1" },
1024
+ types: {
1025
+ EIP712Domain: [
1026
+ { name: "name", type: "string" },
1027
+ { name: "version", type: "string" }
1028
+ ],
1029
+ TransferSpec: [
1030
+ { name: "version", type: "uint32" },
1031
+ { name: "sourceDomain", type: "uint32" },
1032
+ { name: "destinationDomain", type: "uint32" },
1033
+ { name: "sourceContract", type: "bytes32" },
1034
+ { name: "destinationContract", type: "bytes32" },
1035
+ { name: "sourceToken", type: "bytes32" },
1036
+ { name: "destinationToken", type: "bytes32" },
1037
+ { name: "sourceDepositor", type: "bytes32" },
1038
+ { name: "destinationRecipient", type: "bytes32" },
1039
+ { name: "sourceSigner", type: "bytes32" },
1040
+ { name: "destinationCaller", type: "bytes32" },
1041
+ { name: "value", type: "uint256" },
1042
+ { name: "salt", type: "bytes32" },
1043
+ { name: "hookData", type: "bytes" }
1044
+ ],
1045
+ BurnIntent: [
1046
+ { name: "maxBlockHeight", type: "uint256" },
1047
+ { name: "maxFee", type: "uint256" },
1048
+ { name: "spec", type: "TransferSpec" }
1049
+ ]
1050
+ },
1051
+ primaryType: "BurnIntent",
1052
+ message: burnIntent
1053
+ });
1054
+ const apiUrl = this.isTestnet() ? GATEWAY_API_TESTNET : GATEWAY_API_MAINNET;
1055
+ const response = await fetch(`${apiUrl}/transfer`, {
1056
+ method: "POST",
1057
+ headers: { "Content-Type": "application/json" },
1058
+ body: JSON.stringify(
1059
+ [{ burnIntent, signature }],
1060
+ (_, v) => typeof v === "bigint" ? v.toString() : v
1061
+ )
1062
+ });
1063
+ const result = await response.json();
1064
+ if (result.success === false || result.error || !result.attestation || !result.signature) {
1065
+ throw new Error(
1066
+ `Gateway API error: ${result.message || result.error || JSON.stringify(result)}`
1067
+ );
1068
+ }
1069
+ const destTransport = destConfig.rpcUrl ? http(destConfig.rpcUrl) : http();
1070
+ const destWalletClient = createWalletClient({
1071
+ account: this.account,
1072
+ chain: destConfig.chain,
1073
+ transport: destTransport
1074
+ });
1075
+ const destPublicClient = createPublicClient({
1076
+ chain: destConfig.chain,
1077
+ transport: destTransport
1078
+ });
1079
+ const mintTxHash = await destWalletClient.writeContract({
1080
+ address: destConfig.gatewayMinter,
1081
+ abi: GATEWAY_MINTER_ABI,
1082
+ functionName: "gatewayMint",
1083
+ args: [result.attestation, result.signature]
1084
+ });
1085
+ try {
1086
+ await destPublicClient.waitForTransactionReceipt({ hash: mintTxHash });
1087
+ } catch (err) {
1088
+ throw new Error(`Mint transaction failed: ${mintTxHash}`, { cause: err });
1089
+ }
1090
+ return {
1091
+ mintTxHash,
1092
+ amount: withdrawAmount,
1093
+ formattedAmount: amount,
1094
+ sourceChain: this.chainConfig.chain.name,
1095
+ destinationChain: destConfig.chain.name,
1096
+ recipient
1097
+ };
1098
+ }
1099
+ /**
1100
+ * Transfer USDC to any supported chain (alias for withdraw with destination chain).
1101
+ * @deprecated Use `withdraw({ chain })` instead
1102
+ */
1103
+ async transfer(amount, destinationChain, recipient) {
1104
+ return this.withdraw(amount, { chain: destinationChain, recipient });
1105
+ }
1106
+ /**
1107
+ * Create a burn intent for a transfer.
1108
+ */
1109
+ createBurnIntent(from, to, value, recipient, maxFee) {
1110
+ const addressToBytes32 = (addr) => pad(addr.toLowerCase(), { size: 32 });
1111
+ return {
1112
+ maxBlockHeight: maxUint256,
1113
+ maxFee,
1114
+ spec: {
1115
+ version: 1,
1116
+ sourceDomain: from.domain,
1117
+ destinationDomain: to.domain,
1118
+ sourceContract: addressToBytes32(from.gatewayWallet),
1119
+ destinationContract: addressToBytes32(to.gatewayMinter),
1120
+ sourceToken: addressToBytes32(from.usdc),
1121
+ destinationToken: addressToBytes32(to.usdc),
1122
+ sourceDepositor: addressToBytes32(this.account.address),
1123
+ destinationRecipient: addressToBytes32(recipient),
1124
+ sourceSigner: addressToBytes32(this.account.address),
1125
+ destinationCaller: addressToBytes32(zeroAddress),
1126
+ value,
1127
+ salt: `0x${randomBytes(32).toString("hex")}`,
1128
+ hookData: "0x"
1129
+ }
1130
+ };
1131
+ }
1132
+ /**
1133
+ * Check if this client is connected to a testnet.
1134
+ */
1135
+ isTestnet() {
1136
+ const testnetChains = [
1137
+ "arbitrumSepolia",
1138
+ "arcTestnet",
1139
+ "avalancheFuji",
1140
+ "baseSepolia",
1141
+ "sepolia",
1142
+ "hyperEvmTestnet",
1143
+ "seiAtlantic",
1144
+ "sonicTestnet",
1145
+ "worldChainSepolia"
1146
+ ];
1147
+ return testnetChains.some(
1148
+ (name) => CHAIN_CONFIGS[name].domain === this.chainConfig.domain
1149
+ );
1150
+ }
1151
+ // ============================================================================
1152
+ // TRUSTLESS WITHDRAWAL (Emergency use only)
1153
+ // ============================================================================
1154
+ /**
1155
+ * Get the trustless withdrawal delay in blocks (~7 days).
1156
+ *
1157
+ * NOTE: This is for the emergency trustless withdrawal flow only.
1158
+ * For normal withdrawals, use `transfer()` to the same chain.
1159
+ *
1160
+ * @returns Number of blocks to wait after initiating trustless withdrawal
1161
+ */
1162
+ async getTrustlessWithdrawalDelay() {
1163
+ return this.publicClient.readContract({
1164
+ address: this.chainConfig.gatewayWallet,
1165
+ abi: GATEWAY_WALLET_ABI,
1166
+ functionName: "withdrawalDelay"
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Get the block at which a pending trustless withdrawal becomes available.
1171
+ *
1172
+ * @param address - Optional address to check (defaults to account address)
1173
+ * @returns Block number, or 0n if no pending withdrawal
1174
+ */
1175
+ async getTrustlessWithdrawalBlock(address) {
1176
+ const target = address ?? this.account.address;
1177
+ return this.publicClient.readContract({
1178
+ address: this.chainConfig.gatewayWallet,
1179
+ abi: GATEWAY_WALLET_ABI,
1180
+ functionName: "withdrawalBlock",
1181
+ args: [this.chainConfig.usdc, target]
1182
+ });
1183
+ }
1184
+ /**
1185
+ * Initiate a trustless withdrawal from the Gateway Wallet.
1186
+ *
1187
+ * ⚠️ **WARNING: This is for emergency use only!**
1188
+ *
1189
+ * Use this only when Circle's Gateway API is unavailable. For normal
1190
+ * withdrawals, use `transfer()` to the same chain - it's instant.
1191
+ *
1192
+ * After calling this, you must wait ~7 days (`withdrawalDelay` blocks)
1193
+ * before calling `completeTrustlessWithdrawal()`.
1194
+ *
1195
+ * @param amount - Amount of USDC to withdraw (as a decimal string)
1196
+ * @returns Withdrawal initiation result
1197
+ */
1198
+ async initiateTrustlessWithdrawal(amount) {
1199
+ const withdrawAmount = parseUnits(amount, 6);
1200
+ const balance = await this.getBalance();
1201
+ if (balance.available < withdrawAmount) {
1202
+ throw new Error(
1203
+ `Insufficient available balance. Have: ${balance.formattedAvailable}, Need: ${amount}`
1204
+ );
1205
+ }
1206
+ const txHash = await this.walletClient.writeContract({
1207
+ address: this.chainConfig.gatewayWallet,
1208
+ abi: GATEWAY_WALLET_ABI,
1209
+ functionName: "initiateWithdrawal",
1210
+ args: [this.chainConfig.usdc, withdrawAmount],
1211
+ gas: 100000n
1212
+ });
1213
+ try {
1214
+ await this.publicClient.waitForTransactionReceipt({ hash: txHash });
1215
+ } catch (err) {
1216
+ throw new Error(`Trustless withdrawal initiation failed: ${txHash}`, {
1217
+ cause: err
1218
+ });
1219
+ }
1220
+ const withdrawalBlock = await this.getTrustlessWithdrawalBlock();
1221
+ return {
1222
+ txHash,
1223
+ amount: withdrawAmount,
1224
+ formattedAmount: amount,
1225
+ withdrawalBlock
1226
+ };
1227
+ }
1228
+ /**
1229
+ * Complete a trustless withdrawal after the ~7-day delay period.
1230
+ *
1231
+ * ⚠️ **WARNING: This is for emergency use only!**
1232
+ *
1233
+ * Use this only when Circle's Gateway API is unavailable. For normal
1234
+ * withdrawals, use `transfer()` to the same chain - it's instant.
1235
+ *
1236
+ * @returns Withdrawal result
1237
+ */
1238
+ async completeTrustlessWithdrawal() {
1239
+ const balance = await this.getBalance();
1240
+ if (balance.withdrawable === 0n) {
1241
+ const withdrawalBlock = await this.getTrustlessWithdrawalBlock();
1242
+ const currentBlock = await this.publicClient.getBlockNumber();
1243
+ if (withdrawalBlock > 0n && currentBlock < withdrawalBlock) {
1244
+ throw new Error(
1245
+ `Trustless withdrawal not yet available. Current block: ${currentBlock}, Available at: ${withdrawalBlock}`
1246
+ );
1247
+ }
1248
+ throw new Error(
1249
+ "No withdrawable balance. Call initiateTrustlessWithdrawal() first."
1250
+ );
1251
+ }
1252
+ const withdrawAmount = balance.withdrawable;
1253
+ const txHash = await this.walletClient.writeContract({
1254
+ address: this.chainConfig.gatewayWallet,
1255
+ abi: GATEWAY_WALLET_ABI,
1256
+ functionName: "withdraw",
1257
+ args: [this.chainConfig.usdc],
1258
+ gas: 100000n
1259
+ });
1260
+ try {
1261
+ await this.publicClient.waitForTransactionReceipt({ hash: txHash });
1262
+ } catch (err) {
1263
+ throw new Error(`Trustless withdrawal completion failed: ${txHash}`, {
1264
+ cause: err
1265
+ });
1266
+ }
1267
+ return {
1268
+ txHash,
1269
+ amount: withdrawAmount,
1270
+ formattedAmount: formatUnits(withdrawAmount, 6)
1271
+ };
1272
+ }
1273
+ };
1274
+ export {
1275
+ BatchEvmScheme,
1276
+ CHAIN_CONFIGS,
1277
+ CompositeEvmScheme,
1278
+ GATEWAY_DOMAINS,
1279
+ GatewayClient,
1280
+ registerBatchScheme
1281
+ };
1282
+ //# sourceMappingURL=index.mjs.map