@dexterai/x402 1.9.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/adapters/index.cjs +375 -3
- package/dist/adapters/index.d.cts +4 -5
- package/dist/adapters/index.d.ts +4 -5
- package/dist/adapters/index.js +369 -3
- package/dist/client/index.cjs +570 -10
- package/dist/client/index.d.cts +200 -36
- package/dist/client/index.d.ts +200 -36
- package/dist/client/index.js +568 -10
- package/dist/react/index.cjs +404 -8
- package/dist/react/index.d.cts +5 -5
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +404 -8
- package/dist/server/index.cjs +3 -4
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +3 -4
- package/dist/{sponsored-access-D1_mINs4.d.ts → sponsored-access-DAVzu4x6.d.cts} +13 -2
- package/dist/{sponsored-access-Br6YPA-m.d.cts → sponsored-access-Lxa11w_X.d.ts} +13 -2
- package/dist/types-D1u7iu8n.d.cts +304 -0
- package/dist/types-YQlJI5E3.d.ts +304 -0
- package/dist/{types-CjLMR7qs.d.cts → types-_iT11DL0.d.cts} +2 -2
- package/dist/{types-CjLMR7qs.d.ts → types-_iT11DL0.d.ts} +2 -2
- package/dist/utils/index.cjs +0 -1
- package/dist/utils/index.js +0 -1
- package/package.json +1 -1
- package/dist/adapters/index.cjs.map +0 -1
- package/dist/adapters/index.js.map +0 -1
- package/dist/client/index.cjs.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.js.map +0 -1
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/solana-BcOfK6Eq.d.cts +0 -132
- package/dist/solana-Cxr5byPa.d.ts +0 -132
- package/dist/types-BIHhO2-I.d.ts +0 -123
- package/dist/types-CfKflCZO.d.cts +0 -123
- package/dist/utils/index.cjs.map +0 -1
- package/dist/utils/index.js.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -222,16 +222,40 @@ function createSolanaAdapter(config) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
// src/adapters/evm.ts
|
|
225
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
226
|
+
var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
227
|
+
var PERMIT2_WITNESS_TYPES = {
|
|
228
|
+
PermitWitnessTransferFrom: [
|
|
229
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
230
|
+
{ name: "spender", type: "address" },
|
|
231
|
+
{ name: "nonce", type: "uint256" },
|
|
232
|
+
{ name: "deadline", type: "uint256" },
|
|
233
|
+
{ name: "witness", type: "Witness" }
|
|
234
|
+
],
|
|
235
|
+
TokenPermissions: [
|
|
236
|
+
{ name: "token", type: "address" },
|
|
237
|
+
{ name: "amount", type: "uint256" }
|
|
238
|
+
],
|
|
239
|
+
Witness: [
|
|
240
|
+
{ name: "to", type: "address" },
|
|
241
|
+
{ name: "validAfter", type: "uint256" }
|
|
242
|
+
]
|
|
243
|
+
};
|
|
244
|
+
var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
225
245
|
var BASE_MAINNET = "eip155:8453";
|
|
226
246
|
var BASE_SEPOLIA = "eip155:84532";
|
|
227
247
|
var ARBITRUM_ONE = "eip155:42161";
|
|
228
248
|
var POLYGON = "eip155:137";
|
|
229
249
|
var OPTIMISM = "eip155:10";
|
|
230
250
|
var AVALANCHE = "eip155:43114";
|
|
251
|
+
var BSC_MAINNET = "eip155:56";
|
|
231
252
|
var SKALE_BASE = "eip155:1187947933";
|
|
232
253
|
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
233
254
|
var ETHEREUM_MAINNET = "eip155:1";
|
|
255
|
+
var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
|
|
256
|
+
var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
234
257
|
var CHAIN_IDS = {
|
|
258
|
+
[BSC_MAINNET]: 56,
|
|
235
259
|
[BASE_MAINNET]: 8453,
|
|
236
260
|
[BASE_SEPOLIA]: 84532,
|
|
237
261
|
[ARBITRUM_ONE]: 42161,
|
|
@@ -243,6 +267,7 @@ var CHAIN_IDS = {
|
|
|
243
267
|
[ETHEREUM_MAINNET]: 1
|
|
244
268
|
};
|
|
245
269
|
var DEFAULT_RPC_URLS2 = {
|
|
270
|
+
[BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
|
|
246
271
|
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
247
272
|
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
248
273
|
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
@@ -254,6 +279,7 @@ var DEFAULT_RPC_URLS2 = {
|
|
|
254
279
|
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
255
280
|
};
|
|
256
281
|
var USDC_ADDRESSES = {
|
|
282
|
+
[BSC_MAINNET]: BSC_USDC,
|
|
257
283
|
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
258
284
|
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
259
285
|
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
@@ -264,6 +290,10 @@ var USDC_ADDRESSES = {
|
|
|
264
290
|
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
265
291
|
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
266
292
|
};
|
|
293
|
+
var BSC_STABLECOIN_ADDRESSES = {
|
|
294
|
+
[BSC_USDT]: { symbol: "USDT", decimals: 18 },
|
|
295
|
+
[BSC_USDC]: { symbol: "USDC", decimals: 18 }
|
|
296
|
+
};
|
|
267
297
|
function isEvmWallet(wallet) {
|
|
268
298
|
if (!wallet || typeof wallet !== "object") return false;
|
|
269
299
|
const w = wallet;
|
|
@@ -271,7 +301,7 @@ function isEvmWallet(wallet) {
|
|
|
271
301
|
}
|
|
272
302
|
var EvmAdapter = class {
|
|
273
303
|
name = "EVM";
|
|
274
|
-
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
304
|
+
networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
275
305
|
config;
|
|
276
306
|
log;
|
|
277
307
|
constructor(config = {}) {
|
|
@@ -282,6 +312,7 @@ var EvmAdapter = class {
|
|
|
282
312
|
canHandle(network) {
|
|
283
313
|
if (this.networks.includes(network)) return true;
|
|
284
314
|
if (network === "base") return true;
|
|
315
|
+
if (network === "bsc") return true;
|
|
285
316
|
if (network === "ethereum") return true;
|
|
286
317
|
if (network === "arbitrum") return true;
|
|
287
318
|
if (network.startsWith("eip155:")) return true;
|
|
@@ -295,6 +326,7 @@ var EvmAdapter = class {
|
|
|
295
326
|
return DEFAULT_RPC_URLS2[network];
|
|
296
327
|
}
|
|
297
328
|
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
329
|
+
if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
|
|
298
330
|
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
299
331
|
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
300
332
|
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
@@ -314,6 +346,7 @@ var EvmAdapter = class {
|
|
|
314
346
|
return parseInt(chainIdStr, 10);
|
|
315
347
|
}
|
|
316
348
|
if (network === "base") return 8453;
|
|
349
|
+
if (network === "bsc") return 56;
|
|
317
350
|
if (network === "ethereum") return 1;
|
|
318
351
|
if (network === "arbitrum") return 42161;
|
|
319
352
|
return 8453;
|
|
@@ -363,13 +396,19 @@ var EvmAdapter = class {
|
|
|
363
396
|
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
364
397
|
return selector + paddedAddress;
|
|
365
398
|
}
|
|
366
|
-
async buildTransaction(accept, wallet,
|
|
399
|
+
async buildTransaction(accept, wallet, rpcUrl) {
|
|
367
400
|
if (!isEvmWallet(wallet)) {
|
|
368
401
|
throw new Error("Invalid EVM wallet");
|
|
369
402
|
}
|
|
370
403
|
if (!wallet.address) {
|
|
371
404
|
throw new Error("Wallet not connected");
|
|
372
405
|
}
|
|
406
|
+
if (accept.scheme === "exact-approval") {
|
|
407
|
+
return this.buildApprovalTransaction(accept, wallet, rpcUrl);
|
|
408
|
+
}
|
|
409
|
+
if (accept.extra?.assetTransferMethod === "permit2") {
|
|
410
|
+
return this.buildPermit2Transaction(accept, wallet, rpcUrl);
|
|
411
|
+
}
|
|
373
412
|
const { payTo, asset, extra } = accept;
|
|
374
413
|
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
375
414
|
if (!amount) {
|
|
@@ -440,6 +479,325 @@ var EvmAdapter = class {
|
|
|
440
479
|
signature
|
|
441
480
|
};
|
|
442
481
|
}
|
|
482
|
+
// ===========================================================================
|
|
483
|
+
// exact-approval: BSC and other chains without EIP-3009
|
|
484
|
+
// ===========================================================================
|
|
485
|
+
/**
|
|
486
|
+
* Build a payment transaction for chains that use the approval-based scheme.
|
|
487
|
+
* The facilitator's /supported response provides the EIP-712 domain and types
|
|
488
|
+
* in accept.extra, so the client doesn't hardcode any contract addresses.
|
|
489
|
+
*/
|
|
490
|
+
async buildApprovalTransaction(accept, wallet, rpcUrl) {
|
|
491
|
+
const { payTo, asset, extra } = accept;
|
|
492
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
493
|
+
if (!amount) {
|
|
494
|
+
throw new Error("Missing amount in payment requirements");
|
|
495
|
+
}
|
|
496
|
+
const facilitatorContract = extra?.facilitatorContract;
|
|
497
|
+
if (!facilitatorContract) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
"exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (!wallet.signTypedData) {
|
|
503
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
504
|
+
}
|
|
505
|
+
this.log("Building approval-based transaction:", {
|
|
506
|
+
from: wallet.address,
|
|
507
|
+
to: payTo,
|
|
508
|
+
amount,
|
|
509
|
+
asset,
|
|
510
|
+
network: accept.network,
|
|
511
|
+
facilitatorContract
|
|
512
|
+
});
|
|
513
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
514
|
+
const fee = extra?.fee ?? "0";
|
|
515
|
+
const totalNeeded = BigInt(amount) + BigInt(fee);
|
|
516
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
|
|
517
|
+
if (currentAllowance < totalNeeded) {
|
|
518
|
+
if (!wallet.sendTransaction) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
"BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
|
|
524
|
+
this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
|
|
525
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
526
|
+
to: asset,
|
|
527
|
+
data: this.encodeApprove(facilitatorContract, approvalAmount),
|
|
528
|
+
value: 0n
|
|
529
|
+
});
|
|
530
|
+
this.log(`Approval tx sent: ${approveTxHash}`);
|
|
531
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
532
|
+
this.log("Approval confirmed");
|
|
533
|
+
} else {
|
|
534
|
+
this.log("Sufficient allowance, skipping approval");
|
|
535
|
+
}
|
|
536
|
+
const nonceBytes = new Uint8Array(16);
|
|
537
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
538
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
|
|
539
|
+
const paymentIdBytes = new Uint8Array(32);
|
|
540
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
|
|
541
|
+
const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
542
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
543
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
544
|
+
const eip712Domain = extra?.eip712Domain;
|
|
545
|
+
const domain = eip712Domain ? {
|
|
546
|
+
name: eip712Domain.name,
|
|
547
|
+
version: eip712Domain.version,
|
|
548
|
+
chainId: BigInt(eip712Domain.chainId),
|
|
549
|
+
verifyingContract: eip712Domain.verifyingContract
|
|
550
|
+
} : {
|
|
551
|
+
name: "DexterBSCFacilitator",
|
|
552
|
+
version: "1",
|
|
553
|
+
chainId: BigInt(this.getChainId(accept.network)),
|
|
554
|
+
verifyingContract: facilitatorContract
|
|
555
|
+
};
|
|
556
|
+
const types = extra?.eip712Types ?? {
|
|
557
|
+
Payment: [
|
|
558
|
+
{ name: "from", type: "address" },
|
|
559
|
+
{ name: "to", type: "address" },
|
|
560
|
+
{ name: "token", type: "address" },
|
|
561
|
+
{ name: "amount", type: "uint256" },
|
|
562
|
+
{ name: "fee", type: "uint256" },
|
|
563
|
+
{ name: "nonce", type: "uint256" },
|
|
564
|
+
{ name: "deadline", type: "uint256" },
|
|
565
|
+
{ name: "paymentId", type: "bytes32" }
|
|
566
|
+
]
|
|
567
|
+
};
|
|
568
|
+
const message = {
|
|
569
|
+
from: wallet.address,
|
|
570
|
+
to: payTo,
|
|
571
|
+
token: asset,
|
|
572
|
+
amount: BigInt(amount),
|
|
573
|
+
fee: BigInt(fee),
|
|
574
|
+
nonce: BigInt(nonce),
|
|
575
|
+
deadline: BigInt(deadline),
|
|
576
|
+
paymentId
|
|
577
|
+
};
|
|
578
|
+
const signature = await wallet.signTypedData({
|
|
579
|
+
domain,
|
|
580
|
+
types,
|
|
581
|
+
primaryType: "Payment",
|
|
582
|
+
message
|
|
583
|
+
});
|
|
584
|
+
this.log("EIP-712 Payment signature obtained");
|
|
585
|
+
const payload = {
|
|
586
|
+
from: wallet.address,
|
|
587
|
+
to: payTo,
|
|
588
|
+
token: asset,
|
|
589
|
+
amount,
|
|
590
|
+
fee,
|
|
591
|
+
nonce,
|
|
592
|
+
deadline,
|
|
593
|
+
paymentId,
|
|
594
|
+
signature
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
serialized: JSON.stringify(payload),
|
|
598
|
+
signature
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
// ===========================================================================
|
|
602
|
+
// Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
|
|
603
|
+
// ===========================================================================
|
|
604
|
+
/**
|
|
605
|
+
* Build a Permit2 payment transaction. Used when the facilitator signals
|
|
606
|
+
* assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
|
|
607
|
+
*
|
|
608
|
+
* Flow:
|
|
609
|
+
* 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
|
|
610
|
+
* 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
|
|
611
|
+
* 3. Return { permit2Authorization, signature } payload for the facilitator.
|
|
612
|
+
*/
|
|
613
|
+
async buildPermit2Transaction(accept, wallet, rpcUrl) {
|
|
614
|
+
const { payTo, asset } = accept;
|
|
615
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
616
|
+
if (!amount) {
|
|
617
|
+
throw new Error("Missing amount in payment requirements");
|
|
618
|
+
}
|
|
619
|
+
if (!wallet.signTypedData) {
|
|
620
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
621
|
+
}
|
|
622
|
+
this.log("Building Permit2 transaction:", {
|
|
623
|
+
from: wallet.address,
|
|
624
|
+
to: payTo,
|
|
625
|
+
amount,
|
|
626
|
+
asset,
|
|
627
|
+
network: accept.network
|
|
628
|
+
});
|
|
629
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
630
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
|
|
631
|
+
if (currentAllowance < BigInt(amount)) {
|
|
632
|
+
if (!wallet.sendTransaction) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
"Permit2 payments require a wallet that supports sendTransaction for the one-time Permit2 approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
this.log(`Approving Permit2 for ${asset} (current allowance: ${currentAllowance})`);
|
|
638
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
639
|
+
to: asset,
|
|
640
|
+
data: this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256),
|
|
641
|
+
value: 0n
|
|
642
|
+
});
|
|
643
|
+
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
644
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
645
|
+
this.log("Permit2 approval confirmed");
|
|
646
|
+
} else {
|
|
647
|
+
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
648
|
+
}
|
|
649
|
+
const nonceBytes = new Uint8Array(32);
|
|
650
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
651
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
652
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
653
|
+
const validAfter = now - 600;
|
|
654
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
655
|
+
const chainId = this.getChainId(accept.network);
|
|
656
|
+
const domain = {
|
|
657
|
+
name: "Permit2",
|
|
658
|
+
chainId: BigInt(chainId),
|
|
659
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
660
|
+
};
|
|
661
|
+
const message = {
|
|
662
|
+
permitted: {
|
|
663
|
+
token: asset,
|
|
664
|
+
amount: BigInt(amount)
|
|
665
|
+
},
|
|
666
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
667
|
+
nonce,
|
|
668
|
+
deadline: BigInt(deadline),
|
|
669
|
+
witness: {
|
|
670
|
+
to: payTo,
|
|
671
|
+
validAfter: BigInt(validAfter)
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const signature = await wallet.signTypedData({
|
|
675
|
+
domain,
|
|
676
|
+
types: PERMIT2_WITNESS_TYPES,
|
|
677
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
678
|
+
message
|
|
679
|
+
});
|
|
680
|
+
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
681
|
+
const payload = {
|
|
682
|
+
signature,
|
|
683
|
+
permit2Authorization: {
|
|
684
|
+
from: wallet.address,
|
|
685
|
+
permitted: {
|
|
686
|
+
token: asset,
|
|
687
|
+
amount
|
|
688
|
+
},
|
|
689
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
690
|
+
nonce: nonce.toString(),
|
|
691
|
+
deadline: String(deadline),
|
|
692
|
+
witness: {
|
|
693
|
+
to: payTo,
|
|
694
|
+
validAfter: String(validAfter)
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
return {
|
|
699
|
+
serialized: JSON.stringify(payload),
|
|
700
|
+
signature
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
705
|
+
*/
|
|
706
|
+
async readAllowance(rpcUrl, token, owner, spender) {
|
|
707
|
+
const selector = "0xdd62ed3e";
|
|
708
|
+
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
709
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
710
|
+
const data = selector + paddedOwner + paddedSpender;
|
|
711
|
+
try {
|
|
712
|
+
const response = await fetch(rpcUrl, {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({
|
|
716
|
+
jsonrpc: "2.0",
|
|
717
|
+
id: 1,
|
|
718
|
+
method: "eth_call",
|
|
719
|
+
params: [{ to: token, data }, "latest"]
|
|
720
|
+
})
|
|
721
|
+
});
|
|
722
|
+
const result = await response.json();
|
|
723
|
+
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
724
|
+
return BigInt(result.result);
|
|
725
|
+
} catch {
|
|
726
|
+
return 0n;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Encode ERC-20 approve(address,uint256) calldata.
|
|
731
|
+
*/
|
|
732
|
+
encodeApprove(spender, amount) {
|
|
733
|
+
const selector = "0x095ea7b3";
|
|
734
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
735
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
736
|
+
return selector + paddedSpender + paddedAmount;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
740
|
+
*/
|
|
741
|
+
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
742
|
+
const start = Date.now();
|
|
743
|
+
while (Date.now() - start < timeoutMs) {
|
|
744
|
+
try {
|
|
745
|
+
const response = await fetch(rpcUrl, {
|
|
746
|
+
method: "POST",
|
|
747
|
+
headers: { "Content-Type": "application/json" },
|
|
748
|
+
body: JSON.stringify({
|
|
749
|
+
jsonrpc: "2.0",
|
|
750
|
+
id: 1,
|
|
751
|
+
method: "eth_getTransactionReceipt",
|
|
752
|
+
params: [txHash]
|
|
753
|
+
})
|
|
754
|
+
});
|
|
755
|
+
const result = await response.json();
|
|
756
|
+
if (result.result) {
|
|
757
|
+
if (result.result.status === "0x0") {
|
|
758
|
+
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
759
|
+
}
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
} catch (err) {
|
|
763
|
+
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
764
|
+
}
|
|
765
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
766
|
+
}
|
|
767
|
+
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
771
|
+
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
772
|
+
*/
|
|
773
|
+
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
774
|
+
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
775
|
+
if (!strategy || strategy.mode === "exact") {
|
|
776
|
+
return total;
|
|
777
|
+
}
|
|
778
|
+
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
779
|
+
const buffered = total * multiple;
|
|
780
|
+
if (strategy.maxCapUsd) {
|
|
781
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
782
|
+
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
783
|
+
if (buffered > maxCap) return maxCap;
|
|
784
|
+
}
|
|
785
|
+
if (strategy.exactAboveUsd) {
|
|
786
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
787
|
+
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
788
|
+
if (BigInt(paymentAmount) > threshold) return total;
|
|
789
|
+
}
|
|
790
|
+
return buffered;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Infer token decimals from payment amount magnitude.
|
|
794
|
+
* BSC stablecoins use 18 decimals, all others use 6.
|
|
795
|
+
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
796
|
+
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
797
|
+
*/
|
|
798
|
+
inferDecimals(amount) {
|
|
799
|
+
return amount.length > 12 ? 18 : 6;
|
|
800
|
+
}
|
|
443
801
|
};
|
|
444
802
|
function createEvmAdapter(config) {
|
|
445
803
|
return new EvmAdapter(config);
|
|
@@ -453,6 +811,9 @@ function isKnownUSDC(asset) {
|
|
|
453
811
|
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
454
812
|
if (addr.toLowerCase() === lc) return true;
|
|
455
813
|
}
|
|
814
|
+
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
815
|
+
if (addr.toLowerCase() === lc) return true;
|
|
816
|
+
}
|
|
456
817
|
return false;
|
|
457
818
|
}
|
|
458
819
|
|
|
@@ -472,10 +833,33 @@ function createX402Client(config) {
|
|
|
472
833
|
fetch: customFetch = globalThis.fetch,
|
|
473
834
|
verbose = false,
|
|
474
835
|
accessPass: accessPassConfig,
|
|
475
|
-
onPaymentRequired
|
|
836
|
+
onPaymentRequired,
|
|
837
|
+
maxRetries = 0,
|
|
838
|
+
retryDelayMs = 500
|
|
476
839
|
} = config;
|
|
477
840
|
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
478
841
|
};
|
|
842
|
+
async function fetchWithRetry(input, init) {
|
|
843
|
+
let lastError;
|
|
844
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
845
|
+
try {
|
|
846
|
+
const response = await customFetch(input, init);
|
|
847
|
+
if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
|
|
848
|
+
log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
|
|
849
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
return response;
|
|
853
|
+
} catch (err) {
|
|
854
|
+
lastError = err;
|
|
855
|
+
if (attempt < maxRetries) {
|
|
856
|
+
log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
|
|
857
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
throw lastError;
|
|
862
|
+
}
|
|
479
863
|
const passCache = /* @__PURE__ */ new Map();
|
|
480
864
|
function getCachedPass(url) {
|
|
481
865
|
try {
|
|
@@ -538,6 +922,19 @@ function createX402Client(config) {
|
|
|
538
922
|
}
|
|
539
923
|
return candidates[0];
|
|
540
924
|
}
|
|
925
|
+
function getChainDisplayName(network, adapterName) {
|
|
926
|
+
const names = {
|
|
927
|
+
"eip155:56": "BSC",
|
|
928
|
+
"eip155:8453": "Base",
|
|
929
|
+
"eip155:84532": "Base Sepolia",
|
|
930
|
+
"eip155:42161": "Arbitrum",
|
|
931
|
+
"eip155:137": "Polygon",
|
|
932
|
+
"eip155:10": "Optimism",
|
|
933
|
+
"eip155:43114": "Avalanche",
|
|
934
|
+
"eip155:1": "Ethereum"
|
|
935
|
+
};
|
|
936
|
+
return names[network] || adapterName;
|
|
937
|
+
}
|
|
541
938
|
function getRpcUrl(network, adapter) {
|
|
542
939
|
return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
|
|
543
940
|
}
|
|
@@ -675,7 +1072,7 @@ function createX402Client(config) {
|
|
|
675
1072
|
}
|
|
676
1073
|
}
|
|
677
1074
|
}
|
|
678
|
-
const response = await
|
|
1075
|
+
const response = await fetchWithRetry(input, init);
|
|
679
1076
|
if (response.status !== 402) {
|
|
680
1077
|
return response;
|
|
681
1078
|
}
|
|
@@ -752,10 +1149,10 @@ function createX402Client(config) {
|
|
|
752
1149
|
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
753
1150
|
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
754
1151
|
if (balance < requiredAmount) {
|
|
755
|
-
const
|
|
1152
|
+
const chainName = getChainDisplayName(accept.network, adapter.name);
|
|
756
1153
|
throw new X402Error(
|
|
757
1154
|
"insufficient_balance",
|
|
758
|
-
`Insufficient
|
|
1155
|
+
`Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
759
1156
|
);
|
|
760
1157
|
}
|
|
761
1158
|
log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
|
|
@@ -810,7 +1207,7 @@ function createX402Client(config) {
|
|
|
810
1207
|
};
|
|
811
1208
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
812
1209
|
log("Retrying request with payment...");
|
|
813
|
-
const retryResponse = await
|
|
1210
|
+
const retryResponse = await fetchWithRetry(input, {
|
|
814
1211
|
...init,
|
|
815
1212
|
headers: {
|
|
816
1213
|
...init?.headers || {},
|
|
@@ -1319,4 +1716,3 @@ export {
|
|
|
1319
1716
|
useAccessPass,
|
|
1320
1717
|
useX402Payment
|
|
1321
1718
|
};
|
|
1322
|
-
//# sourceMappingURL=index.js.map
|
package/dist/server/index.cjs
CHANGED
|
@@ -196,11 +196,11 @@ var FacilitatorClient = class {
|
|
|
196
196
|
async getFeePayer(network) {
|
|
197
197
|
const supported = await this.getSupported();
|
|
198
198
|
const kind = supported.kinds.find(
|
|
199
|
-
(k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
|
|
199
|
+
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
200
200
|
);
|
|
201
201
|
if (!kind) {
|
|
202
202
|
throw new Error(
|
|
203
|
-
`Facilitator does not support network "${network}" with scheme
|
|
203
|
+
`Facilitator does not support network "${network}" with a recognized scheme`
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
return kind.extra?.feePayer;
|
|
@@ -211,7 +211,7 @@ var FacilitatorClient = class {
|
|
|
211
211
|
async getNetworkExtra(network) {
|
|
212
212
|
const supported = await this.getSupported();
|
|
213
213
|
const kind = supported.kinds.find(
|
|
214
|
-
(k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
|
|
214
|
+
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
215
215
|
);
|
|
216
216
|
return kind?.extra;
|
|
217
217
|
}
|
|
@@ -2162,4 +2162,3 @@ var import_x402_ads_types = require("@dexterai/x402-ads-types");
|
|
|
2162
2162
|
x402BrowserSupport,
|
|
2163
2163
|
x402Middleware
|
|
2164
2164
|
});
|
|
2165
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-
|
|
2
|
-
export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-
|
|
1
|
+
import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-_iT11DL0.cjs';
|
|
2
|
+
export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-_iT11DL0.cjs';
|
|
3
3
|
import { Request, RequestHandler } from 'express';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SPONSORED_ACCESS_EXTENSION_KEY, SponsoredAccessClientConsent, SponsoredAccessPaymentRequiredInfo, SponsoredAccessSettlementInfo, SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-
|
|
2
|
-
export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-
|
|
1
|
+
import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-_iT11DL0.js';
|
|
2
|
+
export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-_iT11DL0.js';
|
|
3
3
|
import { Request, RequestHandler } from 'express';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SPONSORED_ACCESS_EXTENSION_KEY, SponsoredAccessClientConsent, SponsoredAccessPaymentRequiredInfo, SponsoredAccessSettlementInfo, SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
package/dist/server/index.js
CHANGED
|
@@ -127,11 +127,11 @@ var FacilitatorClient = class {
|
|
|
127
127
|
async getFeePayer(network) {
|
|
128
128
|
const supported = await this.getSupported();
|
|
129
129
|
const kind = supported.kinds.find(
|
|
130
|
-
(k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
|
|
130
|
+
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
131
131
|
);
|
|
132
132
|
if (!kind) {
|
|
133
133
|
throw new Error(
|
|
134
|
-
`Facilitator does not support network "${network}" with scheme
|
|
134
|
+
`Facilitator does not support network "${network}" with a recognized scheme`
|
|
135
135
|
);
|
|
136
136
|
}
|
|
137
137
|
return kind.extra?.feePayer;
|
|
@@ -142,7 +142,7 @@ var FacilitatorClient = class {
|
|
|
142
142
|
async getNetworkExtra(network) {
|
|
143
143
|
const supported = await this.getSupported();
|
|
144
144
|
const kind = supported.kinds.find(
|
|
145
|
-
(k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
|
|
145
|
+
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
146
146
|
);
|
|
147
147
|
return kind?.extra;
|
|
148
148
|
}
|
|
@@ -2092,4 +2092,3 @@ export {
|
|
|
2092
2092
|
x402BrowserSupport,
|
|
2093
2093
|
x402Middleware
|
|
2094
2094
|
};
|
|
2095
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ChainAdapter, W as WalletSet } from './types-
|
|
2
|
-
import { A as AccessPassClientConfig, P as PaymentAccept } from './types-
|
|
1
|
+
import { C as ChainAdapter, W as WalletSet } from './types-D1u7iu8n.cjs';
|
|
2
|
+
import { A as AccessPassClientConfig, P as PaymentAccept } from './types-_iT11DL0.cjs';
|
|
3
3
|
import { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402-ads-types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -112,6 +112,17 @@ interface X402ClientConfig {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Maximum retry attempts for transient failures (network errors, 502/503).
|
|
117
|
+
* Does not cause double payments — EIP-3009 nonces prevent replay.
|
|
118
|
+
* @default 0 (no retry)
|
|
119
|
+
*/
|
|
120
|
+
maxRetries?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Base delay between retries in milliseconds (doubles each attempt).
|
|
123
|
+
* @default 500
|
|
124
|
+
*/
|
|
125
|
+
retryDelayMs?: number;
|
|
115
126
|
}
|
|
116
127
|
/**
|
|
117
128
|
* x402 Client interface
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ChainAdapter, W as WalletSet } from './types-
|
|
2
|
-
import { A as AccessPassClientConfig, P as PaymentAccept } from './types-
|
|
1
|
+
import { C as ChainAdapter, W as WalletSet } from './types-YQlJI5E3.js';
|
|
2
|
+
import { A as AccessPassClientConfig, P as PaymentAccept } from './types-_iT11DL0.js';
|
|
3
3
|
import { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402-ads-types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -112,6 +112,17 @@ interface X402ClientConfig {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Maximum retry attempts for transient failures (network errors, 502/503).
|
|
117
|
+
* Does not cause double payments — EIP-3009 nonces prevent replay.
|
|
118
|
+
* @default 0 (no retry)
|
|
119
|
+
*/
|
|
120
|
+
maxRetries?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Base delay between retries in milliseconds (doubles each attempt).
|
|
123
|
+
* @default 500
|
|
124
|
+
*/
|
|
125
|
+
retryDelayMs?: number;
|
|
115
126
|
}
|
|
116
127
|
/**
|
|
117
128
|
* x402 Client interface
|