@happyvertical/payments 0.80.0 → 0.80.2

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.
@@ -1,1182 +1,801 @@
1
+ import { a as PaymentVerificationError, r as PaymentProviderError, t as PaymentConfigurationError } from "../chunks/errors-feZrwnTy.js";
2
+ import { _ as normalizeUrlString, b as rememberPaymentOption, d as normalizeFutureDate, f as normalizeMaxStoredPaymentOptions, h as normalizePositiveMinorUnitAmount, i as decimalToMinorUnitAmount, l as normalizeCurrency, m as normalizeNonEmptyString, n as currencyMinorUnitDecimals, o as getFetch, p as normalizeMinorUnitAmount, r as decimalToAtomicUnits, s as minorUnitsToDecimal, u as normalizeDate, v as pollPaymentStatus, y as readJsonResponse } from "../chunks/shared-DVl6UzD9.js";
1
3
  import { createHash } from "node:crypto";
2
- import { P as PaymentConfigurationError, b as PaymentProviderError, d as PaymentVerificationError } from "../chunks/errors-BgFC46qQ.js";
3
- import { n as normalizeNonEmptyString, g as getFetch, a as normalizeMaxStoredPaymentOptions, b as normalizeUrlString, c as normalizeFutureDate, d as normalizeCurrency, e as normalizePositiveMinorUnitAmount, f as decimalToMinorUnitAmount, r as rememberPaymentOption, p as pollPaymentStatus, h as normalizeDate, i as normalizeMinorUnitAmount, j as readJsonResponse, m as minorUnitsToDecimal, k as currencyMinorUnitDecimals, l as decimalToAtomicUnits } from "../chunks/shared-DGHSqDQT.js";
4
- const BASE_USDC_BACKEND_ID = "base-usdc";
5
- const BASE_MAINNET_CHAIN_ID = 8453;
6
- const BASE_MAINNET_CAIP2 = "eip155:8453";
7
- const BASE_USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
8
- const USDC_DECIMALS = 6;
9
- const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
10
- const DEFAULT_LOG_LOOKBACK_BLOCKS = 10000n;
11
- const DEFAULT_MAX_SEEN_X402_TRANSACTION_IDS = 5e4;
12
- class BaseUsdcAdapter {
13
- constructor(options) {
14
- this.options = options;
15
- if (typeof options.rpcUrl !== "string" || !options.rpcUrl.trim()) {
16
- throw new PaymentConfigurationError(
17
- "BaseUsdcAdapter requires an RPC URL."
18
- );
19
- }
20
- this.masterXpub = options.masterXpub === void 0 ? void 0 : normalizeNonEmptyString(options.masterXpub, "Base USDC masterXpub");
21
- this.derivationPathPrefix = normalizeDerivationPathPrefix(
22
- options.derivationPathPrefix ?? "m/0"
23
- );
24
- if (!this.masterXpub && !options.addressDeriver) {
25
- throw new PaymentConfigurationError(
26
- "BaseUsdcAdapter requires masterXpub or addressDeriver."
27
- );
28
- }
29
- this.fetch = getFetch(options.fetch);
30
- this.maxStoredPaymentOptions = normalizeMaxStoredPaymentOptions(
31
- options.maxStoredPaymentOptions,
32
- "BaseUsdcAdapter maxStoredPaymentOptions"
33
- );
34
- this.allowCumulativeTransferMatching = options.allowCumulativeTransferMatching === true;
35
- this.rpcUrl = normalizeUrlString(options.rpcUrl, "BaseUsdcAdapter rpcUrl");
36
- this.usdcContractAddress = normalizeAddress(
37
- options.usdcContractAddress ?? BASE_USDC_CONTRACT
38
- );
39
- this.confirmations = normalizeConfirmationCount(
40
- options.confirmations ?? 1,
41
- "BaseUsdcAdapter confirmations"
42
- );
43
- this.configuredFromBlock = options.fromBlock === void 0 ? void 0 : normalizeFromBlock(options.fromBlock);
44
- this.x402FacilitatorUrl = options.x402FacilitatorUrl === void 0 ? void 0 : normalizeUrlString(
45
- options.x402FacilitatorUrl,
46
- "BaseUsdcAdapter x402FacilitatorUrl"
47
- ).replace(/\/$/, "");
48
- this.capabilities = {
49
- id: BASE_USDC_BACKEND_ID,
50
- displayName: "Base USDC",
51
- settlementCurrency: "USDC",
52
- supportedSettlementCurrencies: ["USDC"],
53
- chain: "base",
54
- settlementShape: "address",
55
- x402Capable: true,
56
- confirmationLatency: {
57
- expectedSeconds: 2,
58
- minConfirmations: this.confirmations,
59
- description: "Base L2 USDC transfer confirmation"
60
- },
61
- supportsRefunds: true,
62
- supportsPayouts: true,
63
- supportsWebhooks: false,
64
- metadata: {
65
- chainId: BASE_MAINNET_CHAIN_ID,
66
- caip2Network: BASE_MAINNET_CAIP2,
67
- usdcContractAddress: this.usdcContractAddress
68
- }
69
- };
70
- }
71
- capabilities;
72
- fetch;
73
- rpcUrl;
74
- usdcContractAddress;
75
- confirmations;
76
- configuredFromBlock;
77
- masterXpub;
78
- derivationPathPrefix;
79
- x402FacilitatorUrl;
80
- maxStoredPaymentOptions;
81
- allowCumulativeTransferMatching;
82
- optionsByQuote = /* @__PURE__ */ new Map();
83
- seenX402TransactionIds = /* @__PURE__ */ new Set();
84
- rpcId = 0;
85
- async createPaymentOption(input) {
86
- const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
87
- const expiresAt = normalizeFutureDate(input.expiresAt, "Base USDC expiry");
88
- const currency = normalizeCurrency(input.currency, "Base USDC");
89
- const amount = normalizePositiveMinorUnitAmount(
90
- input.amount,
91
- "Base USDC amount"
92
- );
93
- const expectedAmountAtomic = amountToUsdcAtomicUnits(
94
- amount,
95
- currency,
96
- "Base USDC amount"
97
- );
98
- const settlementAmount = decimalToMinorUnitAmount(
99
- BigInt(expectedAmountAtomic),
100
- USDC_DECIMALS,
101
- "Base USDC settlement"
102
- );
103
- const configuredIndex = this.options.addressIndexForQuote?.(quoteId);
104
- const indexes = configuredIndex === void 0 ? quoteIdToDerivationIndexes(quoteId) : [normalizeDerivationIndex(configuredIndex)];
105
- const index = indexes[0] ?? 0;
106
- const path = `${this.derivationPathPrefix}/${indexes.join("/")}`;
107
- const payTo = normalizeAddress(
108
- await this.deriveAddress({
109
- quoteId,
110
- index,
111
- indexes,
112
- path
113
- })
114
- );
115
- const searchStartBlock = await this.getCurrentBlockNumber();
116
- const option = {
117
- backendId: this.capabilities.id,
118
- quoteId,
119
- payTo,
120
- settlementShape: "address",
121
- settlementCurrency: "USDC",
122
- settlementAmount,
123
- amount,
124
- currency,
125
- expiresAt,
126
- paymentUri: buildUsdcPaymentUri({
127
- tokenAddress: this.usdcContractAddress,
128
- payTo,
129
- amountAtomic: expectedAmountAtomic
130
- }),
131
- metadata: {
132
- derivationIndex: index,
133
- derivationIndexes: indexes,
134
- derivationPath: path,
135
- caip2Network: BASE_MAINNET_CAIP2,
136
- tokenAddress: this.usdcContractAddress,
137
- searchStartBlock: toHexQuantity(searchStartBlock)
138
- }
139
- };
140
- rememberPaymentOption(
141
- this.optionsByQuote,
142
- quoteId,
143
- { ...option, expectedAmountAtomic, searchStartBlock },
144
- this.maxStoredPaymentOptions
145
- );
146
- return option;
147
- }
148
- watchPayment(input) {
149
- return pollPaymentStatus(
150
- {
151
- ...input,
152
- pollIntervalMs: input.pollIntervalMs ?? this.options.pollIntervalMs ?? 2e3
153
- },
154
- () => this.getStatus(input.quoteId, input.payTo, input.statusContext)
155
- );
156
- }
157
- async getStatus(quoteId, payTo, context = {}) {
158
- const normalizedQuoteId = normalizeNonEmptyString(
159
- quoteId,
160
- "Base USDC quoteId"
161
- );
162
- const normalizedPayTo = normalizeAddress(payTo);
163
- const option = this.optionsByQuote.get(normalizedQuoteId);
164
- const expectedAmountAtomic = resolveExpectedUsdcAmount(option, context);
165
- const contextSearchStartBlock = context.searchStartBlock === void 0 ? void 0 : parseHexQuantity(normalizeFromBlock(context.searchStartBlock));
166
- const searchStartBlock = option?.searchStartBlock ?? contextSearchStartBlock;
167
- if (!option && expectedAmountAtomic !== void 0 && searchStartBlock === void 0 && this.configuredFromBlock === void 0) {
168
- throw new PaymentConfigurationError(
169
- "Base USDC getStatus requires statusContext.searchStartBlock when checking stateless payment status without a configured fromBlock."
170
- );
171
- }
172
- const logs = await this.getTransferLogs(normalizedPayTo, searchStartBlock);
173
- const matchMinBlock = searchStartBlock ?? configuredFromBlockMinBlock(this.configuredFromBlock);
174
- const matchingTransfer = expectedAmountAtomic === void 0 ? void 0 : this.allowCumulativeTransferMatching ? pickCumulativeTransferMatch(
175
- logs,
176
- expectedAmountAtomic,
177
- void 0,
178
- matchMinBlock
179
- ) : pickSingleTransferMatch(
180
- logs,
181
- expectedAmountAtomic,
182
- void 0,
183
- matchMinBlock
184
- );
185
- if (!matchingTransfer) {
186
- const expiresAt = option?.expiresAt ?? (context.expiresAt === void 0 ? void 0 : normalizeDate(context.expiresAt));
187
- return {
188
- backendId: this.capabilities.id,
189
- quoteId: normalizedQuoteId,
190
- payTo: normalizedPayTo,
191
- status: expiresAt && expiresAt < /* @__PURE__ */ new Date() ? "expired" : "pending",
192
- settlementCurrency: "USDC",
193
- settlementAmount: option?.settlementAmount ?? (context.settlementAmount === void 0 ? void 0 : normalizeMinorUnitAmount(
194
- context.settlementAmount,
195
- "Base USDC settlement status"
196
- )),
197
- amount: option?.amount ?? (context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Base USDC status")),
198
- currency: option?.currency ?? (context.currency === void 0 ? void 0 : normalizeCurrency(context.currency, "Base USDC status")),
199
- requiredConfirmations: this.confirmations,
200
- updatedAt: /* @__PURE__ */ new Date()
201
- };
202
- }
203
- const currentBlock = await this.getCurrentBlockNumber();
204
- const blockNumber = readLogBlockNumber(matchingTransfer.latestLog);
205
- const confirmations = calculateConfirmations(currentBlock, blockNumber);
206
- const requiredConfirmations = this.confirmations;
207
- const receivedAmountAtomic = decimalToMinorUnitAmount(
208
- matchingTransfer.amountAtomic,
209
- USDC_DECIMALS,
210
- "Base USDC received"
211
- );
212
- return {
213
- backendId: this.capabilities.id,
214
- quoteId: normalizedQuoteId,
215
- payTo: normalizedPayTo,
216
- status: confirmations >= requiredConfirmations ? "confirmed" : "processing",
217
- settlementCurrency: "USDC",
218
- settlementAmount: option?.settlementAmount ?? (context.settlementAmount === void 0 ? void 0 : normalizeMinorUnitAmount(
219
- context.settlementAmount,
220
- "Base USDC settlement status"
221
- )),
222
- receivedAmount: receivedAmountAtomic,
223
- amount: option?.amount ?? (context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Base USDC status")),
224
- currency: option?.currency ?? (context.currency === void 0 ? void 0 : normalizeCurrency(context.currency, "Base USDC status")),
225
- requiredConfirmations,
226
- confirmations,
227
- transactionId: matchingTransfer.latestLog.transactionHash,
228
- updatedAt: /* @__PURE__ */ new Date(),
229
- raw: matchingTransfer.latestLog
230
- };
231
- }
232
- async verifyX402Proof(input) {
233
- const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
234
- let payload;
235
- try {
236
- payload = decodePaymentHeader(input.paymentHeader);
237
- } catch (error) {
238
- return {
239
- valid: false,
240
- backendId: this.capabilities.id,
241
- quoteId,
242
- payTo: input.payTo,
243
- reason: "x402 payment header could not be decoded.",
244
- raw: {
245
- paymentHeader: input.paymentHeader,
246
- error: error instanceof Error ? error.message : String(error)
247
- }
248
- };
249
- }
250
- const transactionId = readX402PayloadString(payload, [
251
- "transactionHash",
252
- "txHash",
253
- "tx",
254
- "hash"
255
- ])?.trim();
256
- if (this.x402FacilitatorUrl) {
257
- return this.verifyWithFacilitator(input, payload);
258
- }
259
- if (!transactionId) {
260
- return {
261
- valid: false,
262
- backendId: this.capabilities.id,
263
- quoteId,
264
- payTo: input.payTo,
265
- reason: "x402 proof did not include a transaction hash and no facilitator URL is configured.",
266
- raw: payload
267
- };
268
- }
269
- if (!isEvmTransactionHash(transactionId)) {
270
- return {
271
- valid: false,
272
- backendId: this.capabilities.id,
273
- quoteId,
274
- payTo: input.payTo,
275
- transactionId,
276
- reason: "x402 proof transaction hash was invalid.",
277
- raw: payload
278
- };
279
- }
280
- const requirementError = validateX402PayloadRequirements(payload, input);
281
- if (requirementError) {
282
- return {
283
- valid: false,
284
- backendId: this.capabilities.id,
285
- quoteId,
286
- payTo: input.payTo,
287
- transactionId,
288
- reason: requirementError,
289
- raw: payload
290
- };
291
- }
292
- const expectedAmountAtomic = amountToUsdcAtomicUnits(
293
- normalizePositiveMinorUnitAmount(input.amount, "Base USDC x402 amount"),
294
- normalizeCurrency(input.currency, "Base USDC x402"),
295
- "Base USDC x402 amount"
296
- );
297
- return this.verifyX402Receipt(
298
- input,
299
- payload,
300
- quoteId,
301
- transactionId,
302
- expectedAmountAtomic
303
- );
304
- }
305
- async sendPayout(input) {
306
- if (input.currency !== void 0) {
307
- const currency = normalizeNonEmptyString(
308
- input.currency,
309
- "Base USDC payout currency"
310
- );
311
- if (currency.toUpperCase() !== "USDC") {
312
- throw new PaymentConfigurationError(
313
- `BaseUsdcAdapter can only send USDC payouts, received ${input.currency}.`
314
- );
315
- }
316
- }
317
- if (!this.options.sendTransaction) {
318
- throw new PaymentConfigurationError(
319
- "BaseUsdcAdapter sendPayout requires a sendTransaction callback."
320
- );
321
- }
322
- const amount = normalizePositiveMinorUnitAmount(
323
- input.amount,
324
- "Base USDC payout amount"
325
- );
326
- const amountAtomic = String(amount);
327
- const destination = normalizeAddress(input.destination);
328
- const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Base USDC payout quoteId");
329
- const idempotencyKey = input.idempotencyKey === void 0 ? void 0 : normalizeNonEmptyString(
330
- input.idempotencyKey,
331
- "Base USDC payout idempotencyKey"
332
- );
333
- const result = await this.options.sendTransaction({
334
- destination,
335
- amount,
336
- amountAtomic,
337
- currency: "USDC",
338
- tokenAddress: this.usdcContractAddress,
339
- memo: input.memo,
340
- quoteId,
341
- idempotencyKey,
342
- metadata: input.metadata
343
- });
344
- return {
345
- backendId: this.capabilities.id,
346
- status: "submitted",
347
- transactionId: result.transactionId,
348
- destination,
349
- amount,
350
- currency: "USDC",
351
- raw: result.raw
352
- };
353
- }
354
- async refundPayment(input) {
355
- if (input.currency !== void 0) {
356
- const currency = normalizeNonEmptyString(
357
- input.currency,
358
- "Base USDC refund currency"
359
- );
360
- if (currency.toUpperCase() !== "USDC") {
361
- throw new PaymentConfigurationError(
362
- `BaseUsdcAdapter refunds require USDC currency when specified, received ${input.currency}.`
363
- );
364
- }
365
- }
366
- if (!input.destination) {
367
- throw new PaymentConfigurationError(
368
- "Base USDC refunds require a destination payer address."
369
- );
370
- }
371
- if (input.amount === void 0) {
372
- throw new PaymentConfigurationError(
373
- "Base USDC refunds require an amount."
374
- );
375
- }
376
- const payout = await this.sendPayout({
377
- destination: input.destination,
378
- amount: input.amount,
379
- currency: "USDC",
380
- idempotencyKey: input.idempotencyKey,
381
- memo: input.reason,
382
- metadata: input.metadata
383
- });
384
- return {
385
- backendId: this.capabilities.id,
386
- status: "submitted",
387
- transactionId: payout.transactionId,
388
- amount: payout.amount,
389
- currency: payout.currency,
390
- raw: payout.raw
391
- };
392
- }
393
- async deriveAddress(input) {
394
- if (this.options.addressDeriver) {
395
- return this.options.addressDeriver(input);
396
- }
397
- if (!this.masterXpub) {
398
- throw new PaymentConfigurationError(
399
- "BaseUsdcAdapter requires masterXpub or addressDeriver."
400
- );
401
- }
402
- return deriveBaseUsdcAddress({
403
- masterXpub: this.masterXpub,
404
- path: input.path
405
- });
406
- }
407
- async getTransferLogs(payTo, requiredFromBlock) {
408
- const fromBlock = this.configuredFromBlock ?? await this.getDefaultFromBlock(requiredFromBlock);
409
- const logs = await this.rpc("eth_getLogs", [
410
- {
411
- address: this.usdcContractAddress,
412
- fromBlock,
413
- toBlock: "latest",
414
- topics: [TRANSFER_TOPIC, null, addressToTopic(payTo)]
415
- }
416
- ]);
417
- return Array.isArray(logs) ? logs : [];
418
- }
419
- async getDefaultFromBlock(requiredFromBlock) {
420
- const currentBlock = await this.getCurrentBlockNumber();
421
- const lookbackStartBlock = currentBlock > DEFAULT_LOG_LOOKBACK_BLOCKS ? currentBlock - DEFAULT_LOG_LOOKBACK_BLOCKS : 0n;
422
- const startBlock = requiredFromBlock ?? lookbackStartBlock;
423
- return toHexQuantity(startBlock);
424
- }
425
- async getCurrentBlockNumber() {
426
- return parseHexQuantity(await this.rpc("eth_blockNumber", []));
427
- }
428
- async rpc(method, params) {
429
- const response = await this.fetch(this.rpcUrl, {
430
- method: "POST",
431
- headers: { "Content-Type": "application/json" },
432
- body: JSON.stringify({
433
- jsonrpc: "2.0",
434
- id: ++this.rpcId,
435
- method,
436
- params
437
- })
438
- });
439
- const json = await readJsonResponse(
440
- response,
441
- `Base RPC ${method}`
442
- );
443
- if (json.error) {
444
- throw new PaymentProviderError(
445
- `Base RPC ${method}: ${json.error.message}`,
446
- { retryable: json.error.code === -32005 }
447
- );
448
- }
449
- return json.result;
450
- }
451
- async verifyX402Receipt(input, payload, quoteId, transactionId, expectedAmountAtomic, extraRaw) {
452
- if (this.seenX402TransactionIds.has(transactionId)) {
453
- return {
454
- valid: false,
455
- backendId: this.capabilities.id,
456
- quoteId,
457
- payTo: input.payTo,
458
- transactionId,
459
- reason: "x402 transaction has already been verified.",
460
- raw: { ...extraRaw, payload }
461
- };
462
- }
463
- this.seenX402TransactionIds.add(transactionId);
464
- let keepReplayMarker = false;
465
- try {
466
- const receipt = await this.rpc(
467
- "eth_getTransactionReceipt",
468
- [transactionId]
469
- );
470
- const raw = { ...extraRaw, payload, receipt };
471
- if (!receipt) {
472
- return {
473
- valid: false,
474
- backendId: this.capabilities.id,
475
- quoteId,
476
- payTo: input.payTo,
477
- transactionId,
478
- reason: "x402 transaction receipt is not available yet.",
479
- raw
480
- };
481
- }
482
- if (receipt.status !== "0x1") {
483
- return {
484
- valid: false,
485
- backendId: this.capabilities.id,
486
- quoteId,
487
- payTo: input.payTo,
488
- transactionId,
489
- reason: "x402 transaction failed onchain.",
490
- raw
491
- };
492
- }
493
- const blockNumber = parseHexQuantity(receipt.blockNumber ?? "0x0");
494
- const currentBlock = blockNumber > 0n ? await this.getCurrentBlockNumber() : 0n;
495
- const confirmations = calculateConfirmations(currentBlock, blockNumber);
496
- if (confirmations < this.confirmations) {
497
- return {
498
- valid: false,
499
- backendId: this.capabilities.id,
500
- quoteId,
501
- payTo: input.payTo,
502
- transactionId,
503
- reason: `x402 transaction has ${confirmations} confirmations; ${this.confirmations} required.`,
504
- raw
505
- };
506
- }
507
- const receiptLogs = Array.isArray(receipt.logs) ? receipt.logs : [];
508
- const matchingLog = pickBestTransferLog(
509
- receiptLogs.filter(
510
- (log) => isRecord(log) && safeNormalizeAddress(log.address) === this.usdcContractAddress
511
- ),
512
- expectedAmountAtomic,
513
- input.payTo,
514
- "exact"
515
- );
516
- if (!matchingLog) {
517
- return {
518
- valid: false,
519
- backendId: this.capabilities.id,
520
- quoteId,
521
- payTo: input.payTo,
522
- transactionId,
523
- reason: "No exact USDC transfer matching the x402 proof.",
524
- raw
525
- };
526
- }
527
- keepReplayMarker = true;
528
- trimSeenX402TransactionIds(this.seenX402TransactionIds);
529
- return {
530
- valid: true,
531
- backendId: this.capabilities.id,
532
- quoteId,
533
- payTo: normalizeAddress(input.payTo),
534
- transactionId,
535
- payer: parseTransferLog(matchingLog)?.from,
536
- amount: decimalToMinorUnitAmount(
537
- parseHexQuantity(matchingLog.data),
538
- USDC_DECIMALS,
539
- "Base USDC x402 amount"
540
- ),
541
- currency: "USDC",
542
- network: input.network ?? BASE_MAINNET_CAIP2,
543
- raw
544
- };
545
- } finally {
546
- if (!keepReplayMarker) {
547
- this.seenX402TransactionIds.delete(transactionId);
548
- }
549
- }
550
- }
551
- async verifyWithFacilitator(input, payload) {
552
- const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
553
- if (!this.x402FacilitatorUrl) {
554
- throw new PaymentVerificationError("x402 facilitator URL is missing.");
555
- }
556
- const maxAmountRequired = amountToUsdcAtomicUnits(
557
- normalizePositiveMinorUnitAmount(input.amount, "Base USDC x402 amount"),
558
- normalizeCurrency(input.currency, "Base USDC x402"),
559
- "Base USDC x402 amount"
560
- );
561
- const response = await this.fetch(`${this.x402FacilitatorUrl}/verify`, {
562
- method: "POST",
563
- headers: { "Content-Type": "application/json" },
564
- body: JSON.stringify({
565
- paymentPayload: payload,
566
- paymentRequirements: {
567
- scheme: input.scheme ?? "exact",
568
- network: input.network ?? BASE_MAINNET_CAIP2,
569
- asset: this.usdcContractAddress,
570
- payTo: normalizeAddress(input.payTo),
571
- maxAmountRequired,
572
- resource: input.resource,
573
- method: input.method
574
- }
575
- })
576
- });
577
- const json = await readJsonResponse(
578
- response,
579
- "x402 facilitator verify"
580
- );
581
- const valid = readBoolean(json, "valid") ?? readBoolean(json, "isValid");
582
- const transactionId = findNestedString(json, [
583
- "transactionHash",
584
- "txHash"
585
- ])?.trim();
586
- if (valid !== true) {
587
- return {
588
- valid: false,
589
- backendId: this.capabilities.id,
590
- quoteId,
591
- payTo: normalizeAddress(input.payTo),
592
- transactionId,
593
- network: input.network ?? BASE_MAINNET_CAIP2,
594
- reason: String(json.reason ?? "facilitator rejected"),
595
- raw: json
596
- };
597
- }
598
- if (!transactionId) {
599
- return {
600
- valid: false,
601
- backendId: this.capabilities.id,
602
- quoteId,
603
- payTo: normalizeAddress(input.payTo),
604
- network: input.network ?? BASE_MAINNET_CAIP2,
605
- reason: "x402 facilitator did not return a transaction hash.",
606
- raw: json
607
- };
608
- }
609
- if (!isEvmTransactionHash(transactionId)) {
610
- return {
611
- valid: false,
612
- backendId: this.capabilities.id,
613
- quoteId,
614
- payTo: normalizeAddress(input.payTo),
615
- transactionId,
616
- network: input.network ?? BASE_MAINNET_CAIP2,
617
- reason: "x402 facilitator transaction hash was invalid.",
618
- raw: json
619
- };
620
- }
621
- const requirementError = validateX402PayloadRequirements(payload, input);
622
- if (requirementError) {
623
- return {
624
- valid: false,
625
- backendId: this.capabilities.id,
626
- quoteId,
627
- payTo: normalizeAddress(input.payTo),
628
- transactionId,
629
- network: input.network ?? BASE_MAINNET_CAIP2,
630
- reason: requirementError,
631
- raw: json
632
- };
633
- }
634
- return this.verifyX402Receipt(
635
- input,
636
- payload,
637
- quoteId,
638
- transactionId,
639
- maxAmountRequired,
640
- { facilitator: json }
641
- );
642
- }
643
- }
4
+ //#region src/adapters/base-usdc.ts
5
+ var BASE_USDC_BACKEND_ID = "base-usdc";
6
+ var BASE_MAINNET_CHAIN_ID = 8453;
7
+ var BASE_MAINNET_CAIP2 = "eip155:8453";
8
+ var BASE_USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
9
+ var USDC_DECIMALS = 6;
10
+ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
11
+ var DEFAULT_LOG_LOOKBACK_BLOCKS = 10000n;
12
+ var DEFAULT_MAX_SEEN_X402_TRANSACTION_IDS = 5e4;
13
+ var BaseUsdcAdapter = class {
14
+ options;
15
+ capabilities;
16
+ fetch;
17
+ rpcUrl;
18
+ usdcContractAddress;
19
+ confirmations;
20
+ configuredFromBlock;
21
+ masterXpub;
22
+ derivationPathPrefix;
23
+ x402FacilitatorUrl;
24
+ maxStoredPaymentOptions;
25
+ allowCumulativeTransferMatching;
26
+ optionsByQuote = /* @__PURE__ */ new Map();
27
+ seenX402TransactionIds = /* @__PURE__ */ new Set();
28
+ rpcId = 0;
29
+ constructor(options) {
30
+ this.options = options;
31
+ if (typeof options.rpcUrl !== "string" || !options.rpcUrl.trim()) throw new PaymentConfigurationError("BaseUsdcAdapter requires an RPC URL.");
32
+ this.masterXpub = options.masterXpub === void 0 ? void 0 : normalizeNonEmptyString(options.masterXpub, "Base USDC masterXpub");
33
+ this.derivationPathPrefix = normalizeDerivationPathPrefix(options.derivationPathPrefix ?? "m/0");
34
+ if (!this.masterXpub && !options.addressDeriver) throw new PaymentConfigurationError("BaseUsdcAdapter requires masterXpub or addressDeriver.");
35
+ this.fetch = getFetch(options.fetch);
36
+ this.maxStoredPaymentOptions = normalizeMaxStoredPaymentOptions(options.maxStoredPaymentOptions, "BaseUsdcAdapter maxStoredPaymentOptions");
37
+ this.allowCumulativeTransferMatching = options.allowCumulativeTransferMatching === true;
38
+ this.rpcUrl = normalizeUrlString(options.rpcUrl, "BaseUsdcAdapter rpcUrl");
39
+ this.usdcContractAddress = normalizeAddress(options.usdcContractAddress ?? "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913");
40
+ this.confirmations = normalizeConfirmationCount(options.confirmations ?? 1, "BaseUsdcAdapter confirmations");
41
+ this.configuredFromBlock = options.fromBlock === void 0 ? void 0 : normalizeFromBlock(options.fromBlock);
42
+ this.x402FacilitatorUrl = options.x402FacilitatorUrl === void 0 ? void 0 : normalizeUrlString(options.x402FacilitatorUrl, "BaseUsdcAdapter x402FacilitatorUrl").replace(/\/$/, "");
43
+ this.capabilities = {
44
+ id: BASE_USDC_BACKEND_ID,
45
+ displayName: "Base USDC",
46
+ settlementCurrency: "USDC",
47
+ supportedSettlementCurrencies: ["USDC"],
48
+ chain: "base",
49
+ settlementShape: "address",
50
+ x402Capable: true,
51
+ confirmationLatency: {
52
+ expectedSeconds: 2,
53
+ minConfirmations: this.confirmations,
54
+ description: "Base L2 USDC transfer confirmation"
55
+ },
56
+ supportsRefunds: true,
57
+ supportsPayouts: true,
58
+ supportsWebhooks: false,
59
+ metadata: {
60
+ chainId: BASE_MAINNET_CHAIN_ID,
61
+ caip2Network: BASE_MAINNET_CAIP2,
62
+ usdcContractAddress: this.usdcContractAddress
63
+ }
64
+ };
65
+ }
66
+ async createPaymentOption(input) {
67
+ const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
68
+ const expiresAt = normalizeFutureDate(input.expiresAt, "Base USDC expiry");
69
+ const currency = normalizeCurrency(input.currency, "Base USDC");
70
+ const amount = normalizePositiveMinorUnitAmount(input.amount, "Base USDC amount");
71
+ const expectedAmountAtomic = amountToUsdcAtomicUnits(amount, currency, "Base USDC amount");
72
+ const settlementAmount = decimalToMinorUnitAmount(BigInt(expectedAmountAtomic), 6, "Base USDC settlement");
73
+ const configuredIndex = this.options.addressIndexForQuote?.(quoteId);
74
+ const indexes = configuredIndex === void 0 ? quoteIdToDerivationIndexes(quoteId) : [normalizeDerivationIndex(configuredIndex)];
75
+ const index = indexes[0] ?? 0;
76
+ const path = `${this.derivationPathPrefix}/${indexes.join("/")}`;
77
+ const payTo = normalizeAddress(await this.deriveAddress({
78
+ quoteId,
79
+ index,
80
+ indexes,
81
+ path
82
+ }));
83
+ const searchStartBlock = await this.getCurrentBlockNumber();
84
+ const option = {
85
+ backendId: this.capabilities.id,
86
+ quoteId,
87
+ payTo,
88
+ settlementShape: "address",
89
+ settlementCurrency: "USDC",
90
+ settlementAmount,
91
+ amount,
92
+ currency,
93
+ expiresAt,
94
+ paymentUri: buildUsdcPaymentUri({
95
+ tokenAddress: this.usdcContractAddress,
96
+ payTo,
97
+ amountAtomic: expectedAmountAtomic
98
+ }),
99
+ metadata: {
100
+ derivationIndex: index,
101
+ derivationIndexes: indexes,
102
+ derivationPath: path,
103
+ caip2Network: BASE_MAINNET_CAIP2,
104
+ tokenAddress: this.usdcContractAddress,
105
+ searchStartBlock: toHexQuantity(searchStartBlock)
106
+ }
107
+ };
108
+ rememberPaymentOption(this.optionsByQuote, quoteId, {
109
+ ...option,
110
+ expectedAmountAtomic,
111
+ searchStartBlock
112
+ }, this.maxStoredPaymentOptions);
113
+ return option;
114
+ }
115
+ watchPayment(input) {
116
+ return pollPaymentStatus({
117
+ ...input,
118
+ pollIntervalMs: input.pollIntervalMs ?? this.options.pollIntervalMs ?? 2e3
119
+ }, () => this.getStatus(input.quoteId, input.payTo, input.statusContext));
120
+ }
121
+ async getStatus(quoteId, payTo, context = {}) {
122
+ const normalizedQuoteId = normalizeNonEmptyString(quoteId, "Base USDC quoteId");
123
+ const normalizedPayTo = normalizeAddress(payTo);
124
+ const option = this.optionsByQuote.get(normalizedQuoteId);
125
+ const expectedAmountAtomic = resolveExpectedUsdcAmount(option, context);
126
+ const contextSearchStartBlock = context.searchStartBlock === void 0 ? void 0 : parseHexQuantity(normalizeFromBlock(context.searchStartBlock));
127
+ const searchStartBlock = option?.searchStartBlock ?? contextSearchStartBlock;
128
+ if (!option && expectedAmountAtomic !== void 0 && searchStartBlock === void 0 && this.configuredFromBlock === void 0) throw new PaymentConfigurationError("Base USDC getStatus requires statusContext.searchStartBlock when checking stateless payment status without a configured fromBlock.");
129
+ const logs = await this.getTransferLogs(normalizedPayTo, searchStartBlock);
130
+ const matchMinBlock = searchStartBlock ?? configuredFromBlockMinBlock(this.configuredFromBlock);
131
+ const matchingTransfer = expectedAmountAtomic === void 0 ? void 0 : this.allowCumulativeTransferMatching ? pickCumulativeTransferMatch(logs, expectedAmountAtomic, void 0, matchMinBlock) : pickSingleTransferMatch(logs, expectedAmountAtomic, void 0, matchMinBlock);
132
+ if (!matchingTransfer) {
133
+ const expiresAt = option?.expiresAt ?? (context.expiresAt === void 0 ? void 0 : normalizeDate(context.expiresAt));
134
+ return {
135
+ backendId: this.capabilities.id,
136
+ quoteId: normalizedQuoteId,
137
+ payTo: normalizedPayTo,
138
+ status: expiresAt && expiresAt < /* @__PURE__ */ new Date() ? "expired" : "pending",
139
+ settlementCurrency: "USDC",
140
+ settlementAmount: option?.settlementAmount ?? (context.settlementAmount === void 0 ? void 0 : normalizeMinorUnitAmount(context.settlementAmount, "Base USDC settlement status")),
141
+ amount: option?.amount ?? (context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Base USDC status")),
142
+ currency: option?.currency ?? (context.currency === void 0 ? void 0 : normalizeCurrency(context.currency, "Base USDC status")),
143
+ requiredConfirmations: this.confirmations,
144
+ updatedAt: /* @__PURE__ */ new Date()
145
+ };
146
+ }
147
+ const confirmations = calculateConfirmations(await this.getCurrentBlockNumber(), readLogBlockNumber(matchingTransfer.latestLog));
148
+ const requiredConfirmations = this.confirmations;
149
+ const receivedAmountAtomic = decimalToMinorUnitAmount(matchingTransfer.amountAtomic, 6, "Base USDC received");
150
+ return {
151
+ backendId: this.capabilities.id,
152
+ quoteId: normalizedQuoteId,
153
+ payTo: normalizedPayTo,
154
+ status: confirmations >= requiredConfirmations ? "confirmed" : "processing",
155
+ settlementCurrency: "USDC",
156
+ settlementAmount: option?.settlementAmount ?? (context.settlementAmount === void 0 ? void 0 : normalizeMinorUnitAmount(context.settlementAmount, "Base USDC settlement status")),
157
+ receivedAmount: receivedAmountAtomic,
158
+ amount: option?.amount ?? (context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Base USDC status")),
159
+ currency: option?.currency ?? (context.currency === void 0 ? void 0 : normalizeCurrency(context.currency, "Base USDC status")),
160
+ requiredConfirmations,
161
+ confirmations,
162
+ transactionId: matchingTransfer.latestLog.transactionHash,
163
+ updatedAt: /* @__PURE__ */ new Date(),
164
+ raw: matchingTransfer.latestLog
165
+ };
166
+ }
167
+ async verifyX402Proof(input) {
168
+ const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
169
+ let payload;
170
+ try {
171
+ payload = decodePaymentHeader(input.paymentHeader);
172
+ } catch (error) {
173
+ return {
174
+ valid: false,
175
+ backendId: this.capabilities.id,
176
+ quoteId,
177
+ payTo: input.payTo,
178
+ reason: "x402 payment header could not be decoded.",
179
+ raw: {
180
+ paymentHeader: input.paymentHeader,
181
+ error: error instanceof Error ? error.message : String(error)
182
+ }
183
+ };
184
+ }
185
+ const transactionId = readX402PayloadString(payload, [
186
+ "transactionHash",
187
+ "txHash",
188
+ "tx",
189
+ "hash"
190
+ ])?.trim();
191
+ if (this.x402FacilitatorUrl) return this.verifyWithFacilitator(input, payload);
192
+ if (!transactionId) return {
193
+ valid: false,
194
+ backendId: this.capabilities.id,
195
+ quoteId,
196
+ payTo: input.payTo,
197
+ reason: "x402 proof did not include a transaction hash and no facilitator URL is configured.",
198
+ raw: payload
199
+ };
200
+ if (!isEvmTransactionHash(transactionId)) return {
201
+ valid: false,
202
+ backendId: this.capabilities.id,
203
+ quoteId,
204
+ payTo: input.payTo,
205
+ transactionId,
206
+ reason: "x402 proof transaction hash was invalid.",
207
+ raw: payload
208
+ };
209
+ const requirementError = validateX402PayloadRequirements(payload, input);
210
+ if (requirementError) return {
211
+ valid: false,
212
+ backendId: this.capabilities.id,
213
+ quoteId,
214
+ payTo: input.payTo,
215
+ transactionId,
216
+ reason: requirementError,
217
+ raw: payload
218
+ };
219
+ const expectedAmountAtomic = amountToUsdcAtomicUnits(normalizePositiveMinorUnitAmount(input.amount, "Base USDC x402 amount"), normalizeCurrency(input.currency, "Base USDC x402"), "Base USDC x402 amount");
220
+ return this.verifyX402Receipt(input, payload, quoteId, transactionId, expectedAmountAtomic);
221
+ }
222
+ async sendPayout(input) {
223
+ if (input.currency !== void 0) {
224
+ if (normalizeNonEmptyString(input.currency, "Base USDC payout currency").toUpperCase() !== "USDC") throw new PaymentConfigurationError(`BaseUsdcAdapter can only send USDC payouts, received ${input.currency}.`);
225
+ }
226
+ if (!this.options.sendTransaction) throw new PaymentConfigurationError("BaseUsdcAdapter sendPayout requires a sendTransaction callback.");
227
+ const amount = normalizePositiveMinorUnitAmount(input.amount, "Base USDC payout amount");
228
+ const amountAtomic = String(amount);
229
+ const destination = normalizeAddress(input.destination);
230
+ const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Base USDC payout quoteId");
231
+ const idempotencyKey = input.idempotencyKey === void 0 ? void 0 : normalizeNonEmptyString(input.idempotencyKey, "Base USDC payout idempotencyKey");
232
+ const result = await this.options.sendTransaction({
233
+ destination,
234
+ amount,
235
+ amountAtomic,
236
+ currency: "USDC",
237
+ tokenAddress: this.usdcContractAddress,
238
+ memo: input.memo,
239
+ quoteId,
240
+ idempotencyKey,
241
+ metadata: input.metadata
242
+ });
243
+ return {
244
+ backendId: this.capabilities.id,
245
+ status: "submitted",
246
+ transactionId: result.transactionId,
247
+ destination,
248
+ amount,
249
+ currency: "USDC",
250
+ raw: result.raw
251
+ };
252
+ }
253
+ async refundPayment(input) {
254
+ if (input.currency !== void 0) {
255
+ if (normalizeNonEmptyString(input.currency, "Base USDC refund currency").toUpperCase() !== "USDC") throw new PaymentConfigurationError(`BaseUsdcAdapter refunds require USDC currency when specified, received ${input.currency}.`);
256
+ }
257
+ if (!input.destination) throw new PaymentConfigurationError("Base USDC refunds require a destination payer address.");
258
+ if (input.amount === void 0) throw new PaymentConfigurationError("Base USDC refunds require an amount.");
259
+ const payout = await this.sendPayout({
260
+ destination: input.destination,
261
+ amount: input.amount,
262
+ currency: "USDC",
263
+ idempotencyKey: input.idempotencyKey,
264
+ memo: input.reason,
265
+ metadata: input.metadata
266
+ });
267
+ return {
268
+ backendId: this.capabilities.id,
269
+ status: "submitted",
270
+ transactionId: payout.transactionId,
271
+ amount: payout.amount,
272
+ currency: payout.currency,
273
+ raw: payout.raw
274
+ };
275
+ }
276
+ async deriveAddress(input) {
277
+ if (this.options.addressDeriver) return this.options.addressDeriver(input);
278
+ if (!this.masterXpub) throw new PaymentConfigurationError("BaseUsdcAdapter requires masterXpub or addressDeriver.");
279
+ return deriveBaseUsdcAddress({
280
+ masterXpub: this.masterXpub,
281
+ path: input.path
282
+ });
283
+ }
284
+ async getTransferLogs(payTo, requiredFromBlock) {
285
+ const fromBlock = this.configuredFromBlock ?? await this.getDefaultFromBlock(requiredFromBlock);
286
+ const logs = await this.rpc("eth_getLogs", [{
287
+ address: this.usdcContractAddress,
288
+ fromBlock,
289
+ toBlock: "latest",
290
+ topics: [
291
+ TRANSFER_TOPIC,
292
+ null,
293
+ addressToTopic(payTo)
294
+ ]
295
+ }]);
296
+ return Array.isArray(logs) ? logs : [];
297
+ }
298
+ async getDefaultFromBlock(requiredFromBlock) {
299
+ const currentBlock = await this.getCurrentBlockNumber();
300
+ const lookbackStartBlock = currentBlock > DEFAULT_LOG_LOOKBACK_BLOCKS ? currentBlock - DEFAULT_LOG_LOOKBACK_BLOCKS : 0n;
301
+ return toHexQuantity(requiredFromBlock ?? lookbackStartBlock);
302
+ }
303
+ async getCurrentBlockNumber() {
304
+ return parseHexQuantity(await this.rpc("eth_blockNumber", []));
305
+ }
306
+ async rpc(method, params) {
307
+ const json = await readJsonResponse(await this.fetch(this.rpcUrl, {
308
+ method: "POST",
309
+ headers: { "Content-Type": "application/json" },
310
+ body: JSON.stringify({
311
+ jsonrpc: "2.0",
312
+ id: ++this.rpcId,
313
+ method,
314
+ params
315
+ })
316
+ }), `Base RPC ${method}`);
317
+ if (json.error) throw new PaymentProviderError(`Base RPC ${method}: ${json.error.message}`, { retryable: json.error.code === -32005 });
318
+ return json.result;
319
+ }
320
+ async verifyX402Receipt(input, payload, quoteId, transactionId, expectedAmountAtomic, extraRaw) {
321
+ if (this.seenX402TransactionIds.has(transactionId)) return {
322
+ valid: false,
323
+ backendId: this.capabilities.id,
324
+ quoteId,
325
+ payTo: input.payTo,
326
+ transactionId,
327
+ reason: "x402 transaction has already been verified.",
328
+ raw: {
329
+ ...extraRaw,
330
+ payload
331
+ }
332
+ };
333
+ this.seenX402TransactionIds.add(transactionId);
334
+ let keepReplayMarker = false;
335
+ try {
336
+ const receipt = await this.rpc("eth_getTransactionReceipt", [transactionId]);
337
+ const raw = {
338
+ ...extraRaw,
339
+ payload,
340
+ receipt
341
+ };
342
+ if (!receipt) return {
343
+ valid: false,
344
+ backendId: this.capabilities.id,
345
+ quoteId,
346
+ payTo: input.payTo,
347
+ transactionId,
348
+ reason: "x402 transaction receipt is not available yet.",
349
+ raw
350
+ };
351
+ if (receipt.status !== "0x1") return {
352
+ valid: false,
353
+ backendId: this.capabilities.id,
354
+ quoteId,
355
+ payTo: input.payTo,
356
+ transactionId,
357
+ reason: "x402 transaction failed onchain.",
358
+ raw
359
+ };
360
+ const blockNumber = parseHexQuantity(receipt.blockNumber ?? "0x0");
361
+ const confirmations = calculateConfirmations(blockNumber > 0n ? await this.getCurrentBlockNumber() : 0n, blockNumber);
362
+ if (confirmations < this.confirmations) return {
363
+ valid: false,
364
+ backendId: this.capabilities.id,
365
+ quoteId,
366
+ payTo: input.payTo,
367
+ transactionId,
368
+ reason: `x402 transaction has ${confirmations} confirmations; ${this.confirmations} required.`,
369
+ raw
370
+ };
371
+ const matchingLog = pickBestTransferLog((Array.isArray(receipt.logs) ? receipt.logs : []).filter((log) => isRecord(log) && safeNormalizeAddress(log.address) === this.usdcContractAddress), expectedAmountAtomic, input.payTo, "exact");
372
+ if (!matchingLog) return {
373
+ valid: false,
374
+ backendId: this.capabilities.id,
375
+ quoteId,
376
+ payTo: input.payTo,
377
+ transactionId,
378
+ reason: "No exact USDC transfer matching the x402 proof.",
379
+ raw
380
+ };
381
+ keepReplayMarker = true;
382
+ trimSeenX402TransactionIds(this.seenX402TransactionIds);
383
+ return {
384
+ valid: true,
385
+ backendId: this.capabilities.id,
386
+ quoteId,
387
+ payTo: normalizeAddress(input.payTo),
388
+ transactionId,
389
+ payer: parseTransferLog(matchingLog)?.from,
390
+ amount: decimalToMinorUnitAmount(parseHexQuantity(matchingLog.data), 6, "Base USDC x402 amount"),
391
+ currency: "USDC",
392
+ network: input.network ?? "eip155:8453",
393
+ raw
394
+ };
395
+ } finally {
396
+ if (!keepReplayMarker) this.seenX402TransactionIds.delete(transactionId);
397
+ }
398
+ }
399
+ async verifyWithFacilitator(input, payload) {
400
+ const quoteId = normalizeNonEmptyString(input.quoteId, "Base USDC quoteId");
401
+ if (!this.x402FacilitatorUrl) throw new PaymentVerificationError("x402 facilitator URL is missing.");
402
+ const maxAmountRequired = amountToUsdcAtomicUnits(normalizePositiveMinorUnitAmount(input.amount, "Base USDC x402 amount"), normalizeCurrency(input.currency, "Base USDC x402"), "Base USDC x402 amount");
403
+ const json = await readJsonResponse(await this.fetch(`${this.x402FacilitatorUrl}/verify`, {
404
+ method: "POST",
405
+ headers: { "Content-Type": "application/json" },
406
+ body: JSON.stringify({
407
+ paymentPayload: payload,
408
+ paymentRequirements: {
409
+ scheme: input.scheme ?? "exact",
410
+ network: input.network ?? "eip155:8453",
411
+ asset: this.usdcContractAddress,
412
+ payTo: normalizeAddress(input.payTo),
413
+ maxAmountRequired,
414
+ resource: input.resource,
415
+ method: input.method
416
+ }
417
+ })
418
+ }), "x402 facilitator verify");
419
+ const valid = readBoolean(json, "valid") ?? readBoolean(json, "isValid");
420
+ const transactionId = findNestedString(json, ["transactionHash", "txHash"])?.trim();
421
+ if (valid !== true) return {
422
+ valid: false,
423
+ backendId: this.capabilities.id,
424
+ quoteId,
425
+ payTo: normalizeAddress(input.payTo),
426
+ transactionId,
427
+ network: input.network ?? "eip155:8453",
428
+ reason: String(json.reason ?? "facilitator rejected"),
429
+ raw: json
430
+ };
431
+ if (!transactionId) return {
432
+ valid: false,
433
+ backendId: this.capabilities.id,
434
+ quoteId,
435
+ payTo: normalizeAddress(input.payTo),
436
+ network: input.network ?? "eip155:8453",
437
+ reason: "x402 facilitator did not return a transaction hash.",
438
+ raw: json
439
+ };
440
+ if (!isEvmTransactionHash(transactionId)) return {
441
+ valid: false,
442
+ backendId: this.capabilities.id,
443
+ quoteId,
444
+ payTo: normalizeAddress(input.payTo),
445
+ transactionId,
446
+ network: input.network ?? "eip155:8453",
447
+ reason: "x402 facilitator transaction hash was invalid.",
448
+ raw: json
449
+ };
450
+ const requirementError = validateX402PayloadRequirements(payload, input);
451
+ if (requirementError) return {
452
+ valid: false,
453
+ backendId: this.capabilities.id,
454
+ quoteId,
455
+ payTo: normalizeAddress(input.payTo),
456
+ transactionId,
457
+ network: input.network ?? "eip155:8453",
458
+ reason: requirementError,
459
+ raw: json
460
+ };
461
+ return this.verifyX402Receipt(input, payload, quoteId, transactionId, maxAmountRequired, { facilitator: json });
462
+ }
463
+ };
644
464
  async function deriveBaseUsdcAddress(input) {
645
- const masterXpub = normalizeNonEmptyString(
646
- input.masterXpub,
647
- "Base USDC masterXpub"
648
- );
649
- const path = normalizeNonEmptyString(input.path, "Base USDC derivation path");
650
- try {
651
- const [{ HDKey }, { secp256k1 }, { keccak_256 }] = await Promise.all([
652
- import("@scure/bip32"),
653
- import("@noble/curves/secp256k1.js"),
654
- import("@noble/hashes/sha3.js")
655
- ]);
656
- const root = HDKey.fromExtendedKey(masterXpub);
657
- const child = root.derive(path);
658
- if (!child.publicKey) {
659
- throw new PaymentConfigurationError(`No public key derived for ${path}.`);
660
- }
661
- const point = secp256k1.Point.fromBytes(child.publicKey);
662
- const { x, y } = point.toAffine();
663
- const publicKey = concatBytes(bigintToFixedBytes(x), bigintToFixedBytes(y));
664
- const hash = keccak_256(publicKey);
665
- return `0x${bytesToHex(hash.slice(-20))}`;
666
- } catch (error) {
667
- if (error instanceof PaymentConfigurationError) {
668
- throw error;
669
- }
670
- throw new PaymentConfigurationError(
671
- "BIP32 xpub address derivation requires @scure/bip32, @noble/curves, and @noble/hashes.",
672
- { cause: error }
673
- );
674
- }
465
+ const masterXpub = normalizeNonEmptyString(input.masterXpub, "Base USDC masterXpub");
466
+ const path = normalizeNonEmptyString(input.path, "Base USDC derivation path");
467
+ try {
468
+ const [{ HDKey }, { secp256k1 }, { keccak_256 }] = await Promise.all([
469
+ import("@scure/bip32"),
470
+ import("@noble/curves/secp256k1.js"),
471
+ import("@noble/hashes/sha3.js")
472
+ ]);
473
+ const child = HDKey.fromExtendedKey(masterXpub).derive(path);
474
+ if (!child.publicKey) throw new PaymentConfigurationError(`No public key derived for ${path}.`);
475
+ const { x, y } = secp256k1.Point.fromBytes(child.publicKey).toAffine();
476
+ return `0x${bytesToHex(keccak_256(concatBytes(bigintToFixedBytes(x), bigintToFixedBytes(y))).slice(-20))}`;
477
+ } catch (error) {
478
+ if (error instanceof PaymentConfigurationError) throw error;
479
+ throw new PaymentConfigurationError("BIP32 xpub address derivation requires @scure/bip32, @noble/curves, and @noble/hashes.", { cause: error });
480
+ }
675
481
  }
676
482
  function quoteIdToDerivationIndex(quoteId) {
677
- return quoteIdToDerivationIndexes(quoteId)[0] ?? 0;
483
+ return quoteIdToDerivationIndexes(quoteId)[0] ?? 0;
678
484
  }
679
485
  function quoteIdToDerivationIndexes(quoteId) {
680
- const normalizedQuoteId = normalizeNonEmptyString(
681
- quoteId,
682
- "Base USDC quoteId"
683
- );
684
- const hash = createHash("sha256").update(normalizedQuoteId).digest();
685
- return [0, 4, 8, 12].map((offset) => hash.readUInt32BE(offset) & 2147483647);
486
+ const normalizedQuoteId = normalizeNonEmptyString(quoteId, "Base USDC quoteId");
487
+ const hash = createHash("sha256").update(normalizedQuoteId).digest();
488
+ return [
489
+ 0,
490
+ 4,
491
+ 8,
492
+ 12
493
+ ].map((offset) => hash.readUInt32BE(offset) & 2147483647);
686
494
  }
687
495
  function normalizeDerivationIndex(index) {
688
- if (!Number.isInteger(index) || index < 0 || index > 2147483647) {
689
- throw new PaymentConfigurationError(
690
- `BaseUsdcAdapter addressIndexForQuote must return a non-hardened BIP32 child index, received ${String(index)}.`
691
- );
692
- }
693
- return index;
496
+ if (!Number.isInteger(index) || index < 0 || index > 2147483647) throw new PaymentConfigurationError(`BaseUsdcAdapter addressIndexForQuote must return a non-hardened BIP32 child index, received ${String(index)}.`);
497
+ return index;
694
498
  }
695
499
  function normalizeDerivationPathPrefix(value) {
696
- if (typeof value !== "string") {
697
- throw new PaymentConfigurationError(
698
- "BaseUsdcAdapter derivationPathPrefix must be a string."
699
- );
700
- }
701
- const normalized = value.trim().replace(/\/+$/, "");
702
- if (!normalized) {
703
- throw new PaymentConfigurationError(
704
- "BaseUsdcAdapter derivationPathPrefix must not be empty when configured."
705
- );
706
- }
707
- return normalized;
500
+ if (typeof value !== "string") throw new PaymentConfigurationError("BaseUsdcAdapter derivationPathPrefix must be a string.");
501
+ const normalized = value.trim().replace(/\/+$/, "");
502
+ if (!normalized) throw new PaymentConfigurationError("BaseUsdcAdapter derivationPathPrefix must not be empty when configured.");
503
+ return normalized;
708
504
  }
709
505
  function normalizeConfirmationCount(value, label) {
710
- if (!Number.isInteger(value) || value < 0) {
711
- throw new PaymentConfigurationError(
712
- `${label} must be a non-negative integer, received ${String(value)}.`
713
- );
714
- }
715
- return value;
506
+ if (!Number.isInteger(value) || value < 0) throw new PaymentConfigurationError(`${label} must be a non-negative integer, received ${String(value)}.`);
507
+ return value;
716
508
  }
717
509
  function readBoolean(value, key) {
718
- const item = value[key];
719
- return typeof item === "boolean" ? item : void 0;
510
+ const item = value[key];
511
+ return typeof item === "boolean" ? item : void 0;
720
512
  }
721
513
  function normalizeFromBlock(value) {
722
- if (typeof value === "bigint") {
723
- if (value < 0n) {
724
- throw new PaymentConfigurationError(
725
- `BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`
726
- );
727
- }
728
- return toHexQuantity(value);
729
- }
730
- if (typeof value === "number") {
731
- if (!Number.isSafeInteger(value) || value < 0) {
732
- throw new PaymentConfigurationError(
733
- `BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`
734
- );
735
- }
736
- return toHexQuantity(BigInt(value));
737
- }
738
- if (typeof value !== "string") {
739
- throw new PaymentConfigurationError(
740
- `BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`
741
- );
742
- }
743
- const normalized = value.trim();
744
- if (["earliest", "latest", "pending"].includes(normalized)) {
745
- return normalized;
746
- }
747
- if (/^0x[0-9a-fA-F]+$/.test(normalized)) {
748
- return normalized.toLowerCase();
749
- }
750
- throw new PaymentConfigurationError(
751
- `BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`
752
- );
514
+ if (typeof value === "bigint") {
515
+ if (value < 0n) throw new PaymentConfigurationError(`BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`);
516
+ return toHexQuantity(value);
517
+ }
518
+ if (typeof value === "number") {
519
+ if (!Number.isSafeInteger(value) || value < 0) throw new PaymentConfigurationError(`BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`);
520
+ return toHexQuantity(BigInt(value));
521
+ }
522
+ if (typeof value !== "string") throw new PaymentConfigurationError(`BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`);
523
+ const normalized = value.trim();
524
+ if ([
525
+ "earliest",
526
+ "latest",
527
+ "pending"
528
+ ].includes(normalized)) return normalized;
529
+ if (/^0x[0-9a-fA-F]+$/.test(normalized)) return normalized.toLowerCase();
530
+ throw new PaymentConfigurationError(`BaseUsdcAdapter fromBlock must be a non-negative JSON-RPC block tag or quantity, received ${String(value)}.`);
753
531
  }
754
532
  function configuredFromBlockMinBlock(value) {
755
- if (value === void 0 || ["earliest", "latest", "pending"].includes(value)) {
756
- return 0n;
757
- }
758
- return parseHexQuantity(value);
533
+ if (value === void 0 || [
534
+ "earliest",
535
+ "latest",
536
+ "pending"
537
+ ].includes(value)) return 0n;
538
+ return parseHexQuantity(value);
759
539
  }
760
540
  function calculateConfirmations(currentBlock, blockNumber) {
761
- if (blockNumber <= 0n || currentBlock < blockNumber) {
762
- return 0;
763
- }
764
- const confirmations = currentBlock - blockNumber + 1n;
765
- return confirmations > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(confirmations);
541
+ if (blockNumber <= 0n || currentBlock < blockNumber) return 0;
542
+ const confirmations = currentBlock - blockNumber + 1n;
543
+ return confirmations > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(confirmations);
766
544
  }
767
545
  function resolveExpectedUsdcAmount(option, context) {
768
- if (option) {
769
- return option.expectedAmountAtomic;
770
- }
771
- if (context.settlementAmount !== void 0) {
772
- return String(
773
- normalizePositiveMinorUnitAmount(
774
- context.settlementAmount,
775
- "Base USDC expected amount"
776
- )
777
- );
778
- }
779
- if (context.amount === void 0 || context.currency === void 0) {
780
- return void 0;
781
- }
782
- return amountToUsdcAtomicUnits(
783
- normalizePositiveMinorUnitAmount(
784
- context.amount,
785
- "Base USDC expected amount"
786
- ),
787
- normalizeCurrency(context.currency, "Base USDC expected amount"),
788
- "Base USDC expected amount"
789
- );
546
+ if (option) return option.expectedAmountAtomic;
547
+ if (context.settlementAmount !== void 0) return String(normalizePositiveMinorUnitAmount(context.settlementAmount, "Base USDC expected amount"));
548
+ if (context.amount === void 0 || context.currency === void 0) return;
549
+ return amountToUsdcAtomicUnits(normalizePositiveMinorUnitAmount(context.amount, "Base USDC expected amount"), normalizeCurrency(context.currency, "Base USDC expected amount"), "Base USDC expected amount");
790
550
  }
791
551
  function amountToUsdcAtomicUnits(amount, currency, context) {
792
- const normalizedCurrency = normalizeCurrency(currency, context);
793
- if (!["USD", "USDC"].includes(normalizedCurrency)) {
794
- throw new PaymentConfigurationError(
795
- `${context} currency must be USD or USDC for Base USDC settlement, received ${currency}.`
796
- );
797
- }
798
- const decimalAmount = minorUnitsToDecimal(
799
- amount,
800
- currencyMinorUnitDecimals(normalizedCurrency)
801
- );
802
- return decimalToAtomicUnits(decimalAmount, USDC_DECIMALS);
552
+ const normalizedCurrency = normalizeCurrency(currency, context);
553
+ if (!["USD", "USDC"].includes(normalizedCurrency)) throw new PaymentConfigurationError(`${context} currency must be USD or USDC for Base USDC settlement, received ${currency}.`);
554
+ return decimalToAtomicUnits(minorUnitsToDecimal(amount, currencyMinorUnitDecimals(normalizedCurrency)), 6);
803
555
  }
804
556
  function pickBestTransferLog(logs, expectedAmountAtomic, payTo, comparison = "atLeast", minBlockNumber = 0n) {
805
- const expected = BigInt(expectedAmountAtomic);
806
- return logs.find((log) => {
807
- const transfer = parseTransferLog(log);
808
- if (!transfer || log.removed) {
809
- return false;
810
- }
811
- if (readLogBlockNumber(log) < minBlockNumber) {
812
- return false;
813
- }
814
- if (payTo && transfer.to !== normalizeAddress(payTo)) {
815
- return false;
816
- }
817
- return comparison === "exact" ? transfer.amountAtomic === expected : transfer.amountAtomic >= expected;
818
- });
557
+ const expected = BigInt(expectedAmountAtomic);
558
+ return logs.find((log) => {
559
+ const transfer = parseTransferLog(log);
560
+ if (!transfer || log.removed) return false;
561
+ if (readLogBlockNumber(log) < minBlockNumber) return false;
562
+ if (payTo && transfer.to !== normalizeAddress(payTo)) return false;
563
+ return comparison === "exact" ? transfer.amountAtomic === expected : transfer.amountAtomic >= expected;
564
+ });
819
565
  }
820
566
  function pickSingleTransferMatch(logs, expectedAmountAtomic, payTo, minBlockNumber = 0n) {
821
- const log = pickBestTransferLog(
822
- logs,
823
- expectedAmountAtomic,
824
- payTo,
825
- "atLeast",
826
- minBlockNumber
827
- );
828
- const transfer = log ? parseTransferLog(log) : void 0;
829
- return log && transfer ? { amountAtomic: transfer.amountAtomic, latestLog: log } : void 0;
567
+ const log = pickBestTransferLog(logs, expectedAmountAtomic, payTo, "atLeast", minBlockNumber);
568
+ const transfer = log ? parseTransferLog(log) : void 0;
569
+ return log && transfer ? {
570
+ amountAtomic: transfer.amountAtomic,
571
+ latestLog: log
572
+ } : void 0;
830
573
  }
831
574
  function pickCumulativeTransferMatch(logs, expectedAmountAtomic, payTo, minBlockNumber = 0n) {
832
- const expected = BigInt(expectedAmountAtomic);
833
- const matched = logs.map((log) => ({
834
- log,
835
- transfer: parseTransferLog(log)
836
- })).filter(
837
- (item) => {
838
- if (!item.transfer || item.log.removed) {
839
- return false;
840
- }
841
- if (readLogBlockNumber(item.log) < minBlockNumber) {
842
- return false;
843
- }
844
- return true;
845
- }
846
- );
847
- const byPayer = /* @__PURE__ */ new Map();
848
- for (const item of matched) {
849
- const existing = byPayer.get(item.transfer.from);
850
- const latestLog = existing && compareTransferLogs(existing.latestLog, item.log) >= 0 ? existing.latestLog : item.log;
851
- byPayer.set(item.transfer.from, {
852
- amountAtomic: (existing?.amountAtomic ?? 0n) + item.transfer.amountAtomic,
853
- latestLog
854
- });
855
- }
856
- const candidates = [...byPayer.values()].filter(
857
- (item) => item.amountAtomic >= expected
858
- );
859
- if (candidates.length === 0) {
860
- return void 0;
861
- }
862
- return candidates.reduce(
863
- (current, item) => compareTransferLogs(item.latestLog, current.latestLog) > 0 ? item : current
864
- );
575
+ const expected = BigInt(expectedAmountAtomic);
576
+ const matched = logs.map((log) => ({
577
+ log,
578
+ transfer: parseTransferLog(log)
579
+ })).filter((item) => {
580
+ if (!item.transfer || item.log.removed) return false;
581
+ if (readLogBlockNumber(item.log) < minBlockNumber) return false;
582
+ return payTo ? item.transfer.to === normalizeAddress(payTo) : true;
583
+ });
584
+ const byPayer = /* @__PURE__ */ new Map();
585
+ for (const item of matched) {
586
+ const existing = byPayer.get(item.transfer.from);
587
+ const latestLog = existing && compareTransferLogs(existing.latestLog, item.log) >= 0 ? existing.latestLog : item.log;
588
+ byPayer.set(item.transfer.from, {
589
+ amountAtomic: (existing?.amountAtomic ?? 0n) + item.transfer.amountAtomic,
590
+ latestLog
591
+ });
592
+ }
593
+ const candidates = [...byPayer.values()].filter((item) => item.amountAtomic >= expected);
594
+ if (candidates.length === 0) return;
595
+ return candidates.reduce((current, item) => compareTransferLogs(item.latestLog, current.latestLog) > 0 ? item : current);
865
596
  }
866
597
  function parseTransferLog(log) {
867
- if (!isRecord(log) || !Array.isArray(log.topics) || typeof log.data !== "string") {
868
- return void 0;
869
- }
870
- if (log.topics[0]?.toLowerCase() !== TRANSFER_TOPIC || log.topics.length < 3) {
871
- return void 0;
872
- }
873
- try {
874
- return {
875
- from: topicToAddress(log.topics[1] ?? ""),
876
- to: topicToAddress(log.topics[2] ?? ""),
877
- amountAtomic: parseHexQuantity(log.data)
878
- };
879
- } catch {
880
- return void 0;
881
- }
598
+ if (!isRecord(log) || !Array.isArray(log.topics) || typeof log.data !== "string") return;
599
+ if (log.topics[0]?.toLowerCase() !== TRANSFER_TOPIC || log.topics.length < 3) return;
600
+ try {
601
+ return {
602
+ from: topicToAddress(log.topics[1] ?? ""),
603
+ to: topicToAddress(log.topics[2] ?? ""),
604
+ amountAtomic: parseHexQuantity(log.data)
605
+ };
606
+ } catch {
607
+ return;
608
+ }
882
609
  }
883
610
  function readLogBlockNumber(log) {
884
- try {
885
- return parseHexQuantity(log.blockNumber ?? "0x0");
886
- } catch {
887
- return 0n;
888
- }
611
+ try {
612
+ return parseHexQuantity(log.blockNumber ?? "0x0");
613
+ } catch {
614
+ return 0n;
615
+ }
889
616
  }
890
617
  function compareTransferLogs(left, right) {
891
- const leftBlock = readLogBlockNumber(left);
892
- const rightBlock = readLogBlockNumber(right);
893
- if (leftBlock > rightBlock) {
894
- return 1;
895
- }
896
- if (leftBlock < rightBlock) {
897
- return -1;
898
- }
899
- return (left.transactionHash ?? "").toLowerCase().localeCompare((right.transactionHash ?? "").toLowerCase());
618
+ const leftBlock = readLogBlockNumber(left);
619
+ const rightBlock = readLogBlockNumber(right);
620
+ if (leftBlock > rightBlock) return 1;
621
+ if (leftBlock < rightBlock) return -1;
622
+ return (left.transactionHash ?? "").toLowerCase().localeCompare((right.transactionHash ?? "").toLowerCase());
900
623
  }
901
624
  function trimSeenX402TransactionIds(seenTransactionIds) {
902
- while (seenTransactionIds.size > DEFAULT_MAX_SEEN_X402_TRANSACTION_IDS) {
903
- const oldest = seenTransactionIds.values().next().value;
904
- if (oldest === void 0) {
905
- break;
906
- }
907
- seenTransactionIds.delete(oldest);
908
- }
625
+ while (seenTransactionIds.size > DEFAULT_MAX_SEEN_X402_TRANSACTION_IDS) {
626
+ const oldest = seenTransactionIds.values().next().value;
627
+ if (oldest === void 0) break;
628
+ seenTransactionIds.delete(oldest);
629
+ }
909
630
  }
910
631
  function buildUsdcPaymentUri(input) {
911
- return `ethereum:${input.tokenAddress}@${BASE_MAINNET_CHAIN_ID}/transfer?address=${input.payTo}&uint256=${input.amountAtomic}`;
632
+ return `ethereum:${input.tokenAddress}@${BASE_MAINNET_CHAIN_ID}/transfer?address=${input.payTo}&uint256=${input.amountAtomic}`;
912
633
  }
913
634
  function isRecord(value) {
914
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
635
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
915
636
  }
916
637
  function normalizeAddress(address) {
917
- if (typeof address !== "string") {
918
- throw new PaymentConfigurationError(
919
- `Invalid EVM address: ${String(address)}`
920
- );
921
- }
922
- const normalized = address.trim();
923
- if (!/^0x[a-fA-F0-9]{40}$/.test(normalized)) {
924
- throw new PaymentConfigurationError(`Invalid EVM address: ${address}`);
925
- }
926
- return normalized.toLowerCase();
638
+ if (typeof address !== "string") throw new PaymentConfigurationError(`Invalid EVM address: ${String(address)}`);
639
+ const normalized = address.trim();
640
+ if (!/^0x[a-fA-F0-9]{40}$/.test(normalized)) throw new PaymentConfigurationError(`Invalid EVM address: ${address}`);
641
+ return normalized.toLowerCase();
927
642
  }
928
643
  function isEvmTransactionHash(value) {
929
- return /^0x[a-fA-F0-9]{64}$/.test(value);
644
+ return /^0x[a-fA-F0-9]{64}$/.test(value);
930
645
  }
931
646
  function safeNormalizeAddress(address) {
932
- try {
933
- return normalizeAddress(address);
934
- } catch {
935
- return void 0;
936
- }
647
+ try {
648
+ return normalizeAddress(address);
649
+ } catch {
650
+ return;
651
+ }
937
652
  }
938
653
  function addressToTopic(address) {
939
- return `0x${normalizeAddress(address).slice(2).padStart(64, "0")}`;
654
+ return `0x${normalizeAddress(address).slice(2).padStart(64, "0")}`;
940
655
  }
941
656
  function topicToAddress(topic) {
942
- if (!/^0x[a-fA-F0-9]{64}$/.test(topic)) {
943
- throw new PaymentProviderError(`Invalid EVM address topic: ${topic}`);
944
- }
945
- return normalizeAddress(`0x${topic.slice(-40)}`);
657
+ if (!/^0x[a-fA-F0-9]{64}$/.test(topic)) throw new PaymentProviderError(`Invalid EVM address topic: ${topic}`);
658
+ return normalizeAddress(`0x${topic.slice(-40)}`);
946
659
  }
947
660
  function parseHexQuantity(value) {
948
- if (typeof value === "bigint") {
949
- if (value < 0n) {
950
- throw new PaymentProviderError(
951
- `Invalid JSON-RPC hex quantity: ${String(value)}`
952
- );
953
- }
954
- return value;
955
- }
956
- if (typeof value === "number") {
957
- if (!Number.isSafeInteger(value) || value < 0) {
958
- throw new PaymentProviderError(
959
- `Invalid JSON-RPC hex quantity: ${String(value)}`
960
- );
961
- }
962
- return BigInt(value);
963
- }
964
- if (typeof value !== "string") {
965
- throw new PaymentProviderError(
966
- `Invalid JSON-RPC hex quantity: ${String(value)}`
967
- );
968
- }
969
- const normalized = value.trim();
970
- if (!/^0x[0-9a-fA-F]+$/.test(normalized)) {
971
- throw new PaymentProviderError(
972
- `Invalid JSON-RPC hex quantity: ${String(value)}`
973
- );
974
- }
975
- return BigInt(normalized);
661
+ if (typeof value === "bigint") {
662
+ if (value < 0n) throw new PaymentProviderError(`Invalid JSON-RPC hex quantity: ${String(value)}`);
663
+ return value;
664
+ }
665
+ if (typeof value === "number") {
666
+ if (!Number.isSafeInteger(value) || value < 0) throw new PaymentProviderError(`Invalid JSON-RPC hex quantity: ${String(value)}`);
667
+ return BigInt(value);
668
+ }
669
+ if (typeof value !== "string") throw new PaymentProviderError(`Invalid JSON-RPC hex quantity: ${String(value)}`);
670
+ const normalized = value.trim();
671
+ if (!/^0x[0-9a-fA-F]+$/.test(normalized)) throw new PaymentProviderError(`Invalid JSON-RPC hex quantity: ${String(value)}`);
672
+ return BigInt(normalized);
976
673
  }
977
674
  function toHexQuantity(value) {
978
- return `0x${value.toString(16)}`;
675
+ return `0x${value.toString(16)}`;
979
676
  }
980
677
  function decodePaymentHeader(value) {
981
- const trimmed = value.trim();
982
- if (trimmed.length > 65536) {
983
- throw new PaymentVerificationError("x402 payment header is too large.");
984
- }
985
- const decoded = trimmed.startsWith("{") ? JSON.parse(trimmed) : JSON.parse(
986
- Buffer.from(
987
- trimmed.replace(/-/g, "+").replace(/_/g, "/").padEnd(trimmed.length + (4 - trimmed.length % 4) % 4, "="),
988
- "base64"
989
- ).toString("utf8")
990
- );
991
- if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) {
992
- throw new PaymentVerificationError(
993
- "x402 payment header must be an object."
994
- );
995
- }
996
- return decoded;
678
+ const trimmed = value.trim();
679
+ if (trimmed.length > 65536) throw new PaymentVerificationError("x402 payment header is too large.");
680
+ const decoded = trimmed.startsWith("{") ? JSON.parse(trimmed) : JSON.parse(Buffer.from(trimmed.replace(/-/g, "+").replace(/_/g, "/").padEnd(trimmed.length + (4 - trimmed.length % 4) % 4, "="), "base64").toString("utf8"));
681
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) throw new PaymentVerificationError("x402 payment header must be an object.");
682
+ return decoded;
997
683
  }
998
684
  function validateX402PayloadRequirements(payload, input) {
999
- const expectedScheme = input.scheme ?? "exact";
1000
- const actualScheme = readX402PayloadString(payload, ["scheme"]);
1001
- if (!actualScheme) {
1002
- return "x402 proof did not include the payment scheme.";
1003
- }
1004
- if (actualScheme && actualScheme !== expectedScheme) {
1005
- return `x402 proof scheme ${actualScheme} did not match ${expectedScheme}.`;
1006
- }
1007
- const expectedNetwork = normalizeX402Network(
1008
- input.network ?? BASE_MAINNET_CAIP2
1009
- );
1010
- const actualNetwork = normalizeX402Network(
1011
- readX402PayloadString(payload, ["network", "networkId"])
1012
- );
1013
- if (!actualNetwork) {
1014
- return "x402 proof did not include the payment network.";
1015
- }
1016
- if (actualNetwork && actualNetwork !== expectedNetwork) {
1017
- return `x402 proof network ${actualNetwork} did not match ${expectedNetwork}.`;
1018
- }
1019
- const expectedResource = input.resource;
1020
- if (expectedResource !== void 0) {
1021
- const actualResource = readX402PayloadString(payload, ["resource"]);
1022
- if (!actualResource) {
1023
- return "x402 proof did not include the paid resource.";
1024
- }
1025
- if (actualResource !== expectedResource) {
1026
- return "x402 proof resource did not match the requested resource.";
1027
- }
1028
- }
1029
- const expectedMethod = input.method;
1030
- if (expectedMethod !== void 0) {
1031
- const actualMethod = readX402PayloadString(payload, ["method"]);
1032
- if (!actualMethod) {
1033
- return "x402 proof did not include the HTTP method.";
1034
- }
1035
- if (actualMethod.toUpperCase() !== expectedMethod.toUpperCase()) {
1036
- return "x402 proof method did not match the requested method.";
1037
- }
1038
- }
1039
- const actualExpiry = readX402PayloadString(payload, [
1040
- "expiresAt",
1041
- "expires",
1042
- "validBefore",
1043
- "deadline"
1044
- ]);
1045
- const actualExpiryDate = actualExpiry === void 0 ? void 0 : parseX402ExpiryDate(actualExpiry);
1046
- if (actualExpiry !== void 0 && !actualExpiryDate) {
1047
- return "x402 proof expiry was invalid.";
1048
- }
1049
- if (actualExpiryDate !== void 0 && actualExpiryDate < /* @__PURE__ */ new Date()) {
1050
- return "x402 proof is expired.";
1051
- }
1052
- if (input.expiresAt !== void 0) {
1053
- const expectedExpiry = normalizeDate(input.expiresAt);
1054
- if (expectedExpiry < /* @__PURE__ */ new Date()) {
1055
- return "x402 proof is expired.";
1056
- }
1057
- if (!actualExpiry) {
1058
- return "x402 proof did not include an expiry.";
1059
- }
1060
- if (actualExpiryDate?.getTime() !== expectedExpiry.getTime()) {
1061
- return "x402 proof expiry did not match the requested expiry.";
1062
- }
1063
- }
1064
- return void 0;
685
+ const expectedScheme = input.scheme ?? "exact";
686
+ const actualScheme = readX402PayloadString(payload, ["scheme"]);
687
+ if (!actualScheme) return "x402 proof did not include the payment scheme.";
688
+ if (actualScheme && actualScheme !== expectedScheme) return `x402 proof scheme ${actualScheme} did not match ${expectedScheme}.`;
689
+ const expectedNetwork = normalizeX402Network(input.network ?? "eip155:8453");
690
+ const actualNetwork = normalizeX402Network(readX402PayloadString(payload, ["network", "networkId"]));
691
+ if (!actualNetwork) return "x402 proof did not include the payment network.";
692
+ if (actualNetwork && actualNetwork !== expectedNetwork) return `x402 proof network ${actualNetwork} did not match ${expectedNetwork}.`;
693
+ const expectedResource = input.resource;
694
+ if (expectedResource !== void 0) {
695
+ const actualResource = readX402PayloadString(payload, ["resource"]);
696
+ if (!actualResource) return "x402 proof did not include the paid resource.";
697
+ if (actualResource !== expectedResource) return "x402 proof resource did not match the requested resource.";
698
+ }
699
+ const expectedMethod = input.method;
700
+ if (expectedMethod !== void 0) {
701
+ const actualMethod = readX402PayloadString(payload, ["method"]);
702
+ if (!actualMethod) return "x402 proof did not include the HTTP method.";
703
+ if (actualMethod.toUpperCase() !== expectedMethod.toUpperCase()) return "x402 proof method did not match the requested method.";
704
+ }
705
+ const actualExpiry = readX402PayloadString(payload, [
706
+ "expiresAt",
707
+ "expires",
708
+ "validBefore",
709
+ "deadline"
710
+ ]);
711
+ const actualExpiryDate = actualExpiry === void 0 ? void 0 : parseX402ExpiryDate(actualExpiry);
712
+ if (actualExpiry !== void 0 && !actualExpiryDate) return "x402 proof expiry was invalid.";
713
+ if (actualExpiryDate !== void 0 && actualExpiryDate < /* @__PURE__ */ new Date()) return "x402 proof is expired.";
714
+ if (input.expiresAt !== void 0) {
715
+ const expectedExpiry = normalizeDate(input.expiresAt);
716
+ if (expectedExpiry < /* @__PURE__ */ new Date()) return "x402 proof is expired.";
717
+ if (!actualExpiry) return "x402 proof did not include an expiry.";
718
+ if (actualExpiryDate?.getTime() !== expectedExpiry.getTime()) return "x402 proof expiry did not match the requested expiry.";
719
+ }
1065
720
  }
1066
721
  function parseX402ExpiryDate(value) {
1067
- try {
1068
- if (/^\d+$/.test(value)) {
1069
- const timestamp = Number(value);
1070
- if (!Number.isSafeInteger(timestamp)) {
1071
- return void 0;
1072
- }
1073
- const date = new Date(
1074
- timestamp < 1e12 ? timestamp * 1e3 : timestamp
1075
- );
1076
- return Number.isNaN(date.getTime()) ? void 0 : date;
1077
- }
1078
- return normalizeDate(value);
1079
- } catch {
1080
- return void 0;
1081
- }
722
+ try {
723
+ if (/^\d+$/.test(value)) {
724
+ const timestamp = Number(value);
725
+ if (!Number.isSafeInteger(timestamp)) return;
726
+ const date = new Date(timestamp < 0xe8d4a51000 ? timestamp * 1e3 : timestamp);
727
+ return Number.isNaN(date.getTime()) ? void 0 : date;
728
+ }
729
+ return normalizeDate(value);
730
+ } catch {
731
+ return;
732
+ }
1082
733
  }
1083
734
  function normalizeX402Network(value) {
1084
- if (value === void 0) {
1085
- return void 0;
1086
- }
1087
- const normalized = value.trim().toLowerCase();
1088
- if (["base", "base-mainnet", BASE_MAINNET_CAIP2].includes(normalized)) {
1089
- return BASE_MAINNET_CAIP2;
1090
- }
1091
- return value;
735
+ if (value === void 0) return;
736
+ const normalized = value.trim().toLowerCase();
737
+ if ([
738
+ "base",
739
+ "base-mainnet",
740
+ "eip155:8453"
741
+ ].includes(normalized)) return BASE_MAINNET_CAIP2;
742
+ return value;
1092
743
  }
1093
744
  function readX402PayloadString(value, keys) {
1094
- const root = asRecord(value);
1095
- const nestedPayload = asRecord(root?.payload);
1096
- const authorization = asRecord(nestedPayload?.authorization);
1097
- for (const key of keys) {
1098
- const authorized = readLooseString(authorization, key);
1099
- if (authorized) {
1100
- return authorized;
1101
- }
1102
- const nested = readLooseString(nestedPayload, key);
1103
- if (nested) {
1104
- return nested;
1105
- }
1106
- const direct = readLooseString(root, key);
1107
- if (direct) {
1108
- return direct;
1109
- }
1110
- }
1111
- return void 0;
745
+ const root = asRecord(value);
746
+ const nestedPayload = asRecord(root?.payload);
747
+ const authorization = asRecord(nestedPayload?.authorization);
748
+ for (const key of keys) {
749
+ const authorized = readLooseString(authorization, key);
750
+ if (authorized) return authorized;
751
+ const nested = readLooseString(nestedPayload, key);
752
+ if (nested) return nested;
753
+ const direct = readLooseString(root, key);
754
+ if (direct) return direct;
755
+ }
1112
756
  }
1113
757
  function asRecord(value) {
1114
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
758
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1115
759
  }
1116
760
  function readLooseString(value, key) {
1117
- const item = value?.[key];
1118
- if (typeof item === "string") {
1119
- return item;
1120
- }
1121
- if (typeof item === "number") {
1122
- return String(item);
1123
- }
1124
- return void 0;
761
+ const item = value?.[key];
762
+ if (typeof item === "string") return item;
763
+ if (typeof item === "number") return String(item);
1125
764
  }
1126
765
  function findNestedString(value, keys, depth = 0) {
1127
- if (!value || typeof value !== "object" || depth > 16) {
1128
- return void 0;
1129
- }
1130
- const record = value;
1131
- for (const key of keys) {
1132
- const direct = record[key];
1133
- if (typeof direct === "string" && direct.length > 0) {
1134
- return direct;
1135
- }
1136
- if (typeof direct === "number" && Number.isFinite(direct)) {
1137
- return String(direct);
1138
- }
1139
- }
1140
- for (const child of Object.values(record)) {
1141
- const match = findNestedString(child, keys, depth + 1);
1142
- if (match) {
1143
- return match;
1144
- }
1145
- }
1146
- return void 0;
766
+ if (!value || typeof value !== "object" || depth > 16) return;
767
+ const record = value;
768
+ for (const key of keys) {
769
+ const direct = record[key];
770
+ if (typeof direct === "string" && direct.length > 0) return direct;
771
+ if (typeof direct === "number" && Number.isFinite(direct)) return String(direct);
772
+ }
773
+ for (const child of Object.values(record)) {
774
+ const match = findNestedString(child, keys, depth + 1);
775
+ if (match) return match;
776
+ }
1147
777
  }
1148
778
  function bigintToFixedBytes(value) {
1149
- const hex = value.toString(16).padStart(64, "0");
1150
- return hexToBytes(hex);
779
+ return hexToBytes(value.toString(16).padStart(64, "0"));
1151
780
  }
1152
781
  function concatBytes(...values) {
1153
- const totalLength = values.reduce((sum, value) => sum + value.length, 0);
1154
- const result = new Uint8Array(totalLength);
1155
- let offset = 0;
1156
- for (const value of values) {
1157
- result.set(value, offset);
1158
- offset += value.length;
1159
- }
1160
- return result;
782
+ const totalLength = values.reduce((sum, value) => sum + value.length, 0);
783
+ const result = new Uint8Array(totalLength);
784
+ let offset = 0;
785
+ for (const value of values) {
786
+ result.set(value, offset);
787
+ offset += value.length;
788
+ }
789
+ return result;
1161
790
  }
1162
791
  function bytesToHex(value) {
1163
- return Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join(
1164
- ""
1165
- );
792
+ return Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("");
1166
793
  }
1167
794
  function hexToBytes(value) {
1168
- const bytes = value.match(/.{1,2}/g) ?? [];
1169
- return Uint8Array.from(bytes.map((byte) => Number.parseInt(byte, 16)));
795
+ const bytes = value.match(/.{1,2}/g) ?? [];
796
+ return Uint8Array.from(bytes.map((byte) => Number.parseInt(byte, 16)));
1170
797
  }
1171
- export {
1172
- BASE_MAINNET_CAIP2,
1173
- BASE_MAINNET_CHAIN_ID,
1174
- BASE_USDC_BACKEND_ID,
1175
- BASE_USDC_CONTRACT,
1176
- BaseUsdcAdapter,
1177
- USDC_DECIMALS,
1178
- deriveBaseUsdcAddress,
1179
- quoteIdToDerivationIndex,
1180
- quoteIdToDerivationIndexes
1181
- };
1182
- //# sourceMappingURL=base-usdc.js.map
798
+ //#endregion
799
+ export { BASE_MAINNET_CAIP2, BASE_MAINNET_CHAIN_ID, BASE_USDC_BACKEND_ID, BASE_USDC_CONTRACT, BaseUsdcAdapter, USDC_DECIMALS, deriveBaseUsdcAddress, quoteIdToDerivationIndex, quoteIdToDerivationIndexes };
800
+
801
+ //# sourceMappingURL=base-usdc.js.map