@dexterai/x402 2.0.0 → 3.0.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 +443 -2
- package/dist/adapters/index.d.cts +4 -3
- package/dist/adapters/index.d.ts +4 -3
- package/dist/adapters/index.js +437 -2
- package/dist/client/index.cjs +532 -46
- package/dist/client/index.d.cts +184 -63
- package/dist/client/index.d.ts +184 -63
- package/dist/client/index.js +531 -45
- package/dist/react/index.cjs +452 -4
- package/dist/react/index.d.cts +5 -5
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +452 -4
- package/dist/server/index.cjs +3 -3
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +3 -3
- package/dist/{sponsored-access-DjLEKhOV.d.ts → sponsored-access-BVoucsEW.d.ts} +2 -2
- package/dist/{sponsored-access-BgEDLg_H.d.cts → sponsored-access-CE7WpV5b.d.cts} +2 -2
- package/dist/{types-DWhpiOBD.d.cts → types-C_aQh02s.d.cts} +86 -3
- package/dist/{types-D1TGACsL.d.ts → types-DBS0XOsH.d.ts} +86 -3
- 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/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -249,16 +249,40 @@ function createSolanaAdapter(config) {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// src/adapters/evm.ts
|
|
252
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
253
|
+
var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
254
|
+
var PERMIT2_WITNESS_TYPES = {
|
|
255
|
+
PermitWitnessTransferFrom: [
|
|
256
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
257
|
+
{ name: "spender", type: "address" },
|
|
258
|
+
{ name: "nonce", type: "uint256" },
|
|
259
|
+
{ name: "deadline", type: "uint256" },
|
|
260
|
+
{ name: "witness", type: "Witness" }
|
|
261
|
+
],
|
|
262
|
+
TokenPermissions: [
|
|
263
|
+
{ name: "token", type: "address" },
|
|
264
|
+
{ name: "amount", type: "uint256" }
|
|
265
|
+
],
|
|
266
|
+
Witness: [
|
|
267
|
+
{ name: "to", type: "address" },
|
|
268
|
+
{ name: "validAfter", type: "uint256" }
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
252
272
|
var BASE_MAINNET = "eip155:8453";
|
|
253
273
|
var BASE_SEPOLIA = "eip155:84532";
|
|
254
274
|
var ARBITRUM_ONE = "eip155:42161";
|
|
255
275
|
var POLYGON = "eip155:137";
|
|
256
276
|
var OPTIMISM = "eip155:10";
|
|
257
277
|
var AVALANCHE = "eip155:43114";
|
|
278
|
+
var BSC_MAINNET = "eip155:56";
|
|
258
279
|
var SKALE_BASE = "eip155:1187947933";
|
|
259
280
|
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
260
281
|
var ETHEREUM_MAINNET = "eip155:1";
|
|
282
|
+
var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
|
|
283
|
+
var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
261
284
|
var CHAIN_IDS = {
|
|
285
|
+
[BSC_MAINNET]: 56,
|
|
262
286
|
[BASE_MAINNET]: 8453,
|
|
263
287
|
[BASE_SEPOLIA]: 84532,
|
|
264
288
|
[ARBITRUM_ONE]: 42161,
|
|
@@ -270,6 +294,7 @@ var CHAIN_IDS = {
|
|
|
270
294
|
[ETHEREUM_MAINNET]: 1
|
|
271
295
|
};
|
|
272
296
|
var DEFAULT_RPC_URLS2 = {
|
|
297
|
+
[BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
|
|
273
298
|
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
274
299
|
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
275
300
|
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
@@ -281,6 +306,7 @@ var DEFAULT_RPC_URLS2 = {
|
|
|
281
306
|
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
282
307
|
};
|
|
283
308
|
var USDC_ADDRESSES = {
|
|
309
|
+
[BSC_MAINNET]: BSC_USDC,
|
|
284
310
|
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
285
311
|
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
286
312
|
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
@@ -291,6 +317,10 @@ var USDC_ADDRESSES = {
|
|
|
291
317
|
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
292
318
|
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
293
319
|
};
|
|
320
|
+
var BSC_STABLECOIN_ADDRESSES = {
|
|
321
|
+
[BSC_USDT]: { symbol: "USDT", decimals: 18 },
|
|
322
|
+
[BSC_USDC]: { symbol: "USDC", decimals: 18 }
|
|
323
|
+
};
|
|
294
324
|
function isEvmWallet(wallet) {
|
|
295
325
|
if (!wallet || typeof wallet !== "object") return false;
|
|
296
326
|
const w = wallet;
|
|
@@ -298,7 +328,7 @@ function isEvmWallet(wallet) {
|
|
|
298
328
|
}
|
|
299
329
|
var EvmAdapter = class {
|
|
300
330
|
name = "EVM";
|
|
301
|
-
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
331
|
+
networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
302
332
|
config;
|
|
303
333
|
log;
|
|
304
334
|
constructor(config = {}) {
|
|
@@ -309,6 +339,7 @@ var EvmAdapter = class {
|
|
|
309
339
|
canHandle(network) {
|
|
310
340
|
if (this.networks.includes(network)) return true;
|
|
311
341
|
if (network === "base") return true;
|
|
342
|
+
if (network === "bsc") return true;
|
|
312
343
|
if (network === "ethereum") return true;
|
|
313
344
|
if (network === "arbitrum") return true;
|
|
314
345
|
if (network.startsWith("eip155:")) return true;
|
|
@@ -322,6 +353,7 @@ var EvmAdapter = class {
|
|
|
322
353
|
return DEFAULT_RPC_URLS2[network];
|
|
323
354
|
}
|
|
324
355
|
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
356
|
+
if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
|
|
325
357
|
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
326
358
|
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
327
359
|
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
@@ -341,6 +373,7 @@ var EvmAdapter = class {
|
|
|
341
373
|
return parseInt(chainIdStr, 10);
|
|
342
374
|
}
|
|
343
375
|
if (network === "base") return 8453;
|
|
376
|
+
if (network === "bsc") return 56;
|
|
344
377
|
if (network === "ethereum") return 1;
|
|
345
378
|
if (network === "arbitrum") return 42161;
|
|
346
379
|
return 8453;
|
|
@@ -390,13 +423,19 @@ var EvmAdapter = class {
|
|
|
390
423
|
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
391
424
|
return selector + paddedAddress;
|
|
392
425
|
}
|
|
393
|
-
async buildTransaction(accept, wallet,
|
|
426
|
+
async buildTransaction(accept, wallet, rpcUrl) {
|
|
394
427
|
if (!isEvmWallet(wallet)) {
|
|
395
428
|
throw new Error("Invalid EVM wallet");
|
|
396
429
|
}
|
|
397
430
|
if (!wallet.address) {
|
|
398
431
|
throw new Error("Wallet not connected");
|
|
399
432
|
}
|
|
433
|
+
if (accept.scheme === "exact-approval") {
|
|
434
|
+
return this.buildApprovalTransaction(accept, wallet, rpcUrl);
|
|
435
|
+
}
|
|
436
|
+
if (accept.extra?.assetTransferMethod === "permit2") {
|
|
437
|
+
return this.buildPermit2Transaction(accept, wallet, rpcUrl);
|
|
438
|
+
}
|
|
400
439
|
const { payTo, asset, extra } = accept;
|
|
401
440
|
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
402
441
|
if (!amount) {
|
|
@@ -467,6 +506,393 @@ var EvmAdapter = class {
|
|
|
467
506
|
signature
|
|
468
507
|
};
|
|
469
508
|
}
|
|
509
|
+
// ===========================================================================
|
|
510
|
+
// exact-approval: BSC and other chains without EIP-3009
|
|
511
|
+
// ===========================================================================
|
|
512
|
+
/**
|
|
513
|
+
* Build a payment transaction for chains that use the approval-based scheme.
|
|
514
|
+
* The facilitator's /supported response provides the EIP-712 domain and types
|
|
515
|
+
* in accept.extra, so the client doesn't hardcode any contract addresses.
|
|
516
|
+
*/
|
|
517
|
+
async buildApprovalTransaction(accept, wallet, rpcUrl) {
|
|
518
|
+
const { payTo, asset, extra } = accept;
|
|
519
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
520
|
+
if (!amount) {
|
|
521
|
+
throw new Error("Missing amount in payment requirements");
|
|
522
|
+
}
|
|
523
|
+
const facilitatorContract = extra?.facilitatorContract;
|
|
524
|
+
if (!facilitatorContract) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
"exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (!wallet.signTypedData) {
|
|
530
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
531
|
+
}
|
|
532
|
+
this.log("Building approval-based transaction:", {
|
|
533
|
+
from: wallet.address,
|
|
534
|
+
to: payTo,
|
|
535
|
+
amount,
|
|
536
|
+
asset,
|
|
537
|
+
network: accept.network,
|
|
538
|
+
facilitatorContract
|
|
539
|
+
});
|
|
540
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
541
|
+
const fee = extra?.fee ?? "0";
|
|
542
|
+
const totalNeeded = BigInt(amount) + BigInt(fee);
|
|
543
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
|
|
544
|
+
if (currentAllowance < totalNeeded) {
|
|
545
|
+
if (!wallet.sendTransaction) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
"BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
|
|
551
|
+
this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
|
|
552
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
553
|
+
to: asset,
|
|
554
|
+
data: this.encodeApprove(facilitatorContract, approvalAmount),
|
|
555
|
+
value: 0n
|
|
556
|
+
});
|
|
557
|
+
this.log(`Approval tx sent: ${approveTxHash}`);
|
|
558
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
559
|
+
this.log("Approval confirmed");
|
|
560
|
+
} else {
|
|
561
|
+
this.log("Sufficient allowance, skipping approval");
|
|
562
|
+
}
|
|
563
|
+
const nonceBytes = new Uint8Array(16);
|
|
564
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
565
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
|
|
566
|
+
const paymentIdBytes = new Uint8Array(32);
|
|
567
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
|
|
568
|
+
const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
569
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
570
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
571
|
+
const eip712Domain = extra?.eip712Domain;
|
|
572
|
+
const domain = eip712Domain ? {
|
|
573
|
+
name: eip712Domain.name,
|
|
574
|
+
version: eip712Domain.version,
|
|
575
|
+
chainId: BigInt(eip712Domain.chainId),
|
|
576
|
+
verifyingContract: eip712Domain.verifyingContract
|
|
577
|
+
} : {
|
|
578
|
+
name: "DexterBSCFacilitator",
|
|
579
|
+
version: "1",
|
|
580
|
+
chainId: BigInt(this.getChainId(accept.network)),
|
|
581
|
+
verifyingContract: facilitatorContract
|
|
582
|
+
};
|
|
583
|
+
const types = extra?.eip712Types ?? {
|
|
584
|
+
Payment: [
|
|
585
|
+
{ name: "from", type: "address" },
|
|
586
|
+
{ name: "to", type: "address" },
|
|
587
|
+
{ name: "token", type: "address" },
|
|
588
|
+
{ name: "amount", type: "uint256" },
|
|
589
|
+
{ name: "fee", type: "uint256" },
|
|
590
|
+
{ name: "nonce", type: "uint256" },
|
|
591
|
+
{ name: "deadline", type: "uint256" },
|
|
592
|
+
{ name: "paymentId", type: "bytes32" }
|
|
593
|
+
]
|
|
594
|
+
};
|
|
595
|
+
const message = {
|
|
596
|
+
from: wallet.address,
|
|
597
|
+
to: payTo,
|
|
598
|
+
token: asset,
|
|
599
|
+
amount: BigInt(amount),
|
|
600
|
+
fee: BigInt(fee),
|
|
601
|
+
nonce: BigInt(nonce),
|
|
602
|
+
deadline: BigInt(deadline),
|
|
603
|
+
paymentId
|
|
604
|
+
};
|
|
605
|
+
const signature = await wallet.signTypedData({
|
|
606
|
+
domain,
|
|
607
|
+
types,
|
|
608
|
+
primaryType: "Payment",
|
|
609
|
+
message
|
|
610
|
+
});
|
|
611
|
+
this.log("EIP-712 Payment signature obtained");
|
|
612
|
+
const payload = {
|
|
613
|
+
from: wallet.address,
|
|
614
|
+
to: payTo,
|
|
615
|
+
token: asset,
|
|
616
|
+
amount,
|
|
617
|
+
fee,
|
|
618
|
+
nonce,
|
|
619
|
+
deadline,
|
|
620
|
+
paymentId,
|
|
621
|
+
signature
|
|
622
|
+
};
|
|
623
|
+
return {
|
|
624
|
+
serialized: JSON.stringify(payload),
|
|
625
|
+
signature
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
// ===========================================================================
|
|
629
|
+
// Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
|
|
630
|
+
// ===========================================================================
|
|
631
|
+
/**
|
|
632
|
+
* Build a Permit2 payment transaction. Used when the facilitator signals
|
|
633
|
+
* assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
|
|
634
|
+
*
|
|
635
|
+
* Flow:
|
|
636
|
+
* 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
|
|
637
|
+
* 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
|
|
638
|
+
* 3. Return { permit2Authorization, signature } payload for the facilitator.
|
|
639
|
+
*/
|
|
640
|
+
async buildPermit2Transaction(accept, wallet, rpcUrl) {
|
|
641
|
+
const { payTo, asset } = accept;
|
|
642
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
643
|
+
if (!amount) {
|
|
644
|
+
throw new Error("Missing amount in payment requirements");
|
|
645
|
+
}
|
|
646
|
+
if (!wallet.signTypedData) {
|
|
647
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
648
|
+
}
|
|
649
|
+
this.log("Building Permit2 transaction:", {
|
|
650
|
+
from: wallet.address,
|
|
651
|
+
to: payTo,
|
|
652
|
+
amount,
|
|
653
|
+
asset,
|
|
654
|
+
network: accept.network
|
|
655
|
+
});
|
|
656
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
657
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
|
|
658
|
+
let approvalExtension;
|
|
659
|
+
if (currentAllowance < BigInt(amount)) {
|
|
660
|
+
const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
|
|
661
|
+
if (wallet.signTransaction) {
|
|
662
|
+
this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
|
|
663
|
+
const chainId2 = this.getChainId(accept.network);
|
|
664
|
+
const gasPrice = await this.readGasPrice(url);
|
|
665
|
+
const nonce2 = await this.readNonce(url, wallet.address);
|
|
666
|
+
const signedTx = await wallet.signTransaction({
|
|
667
|
+
to: asset,
|
|
668
|
+
data: approveData,
|
|
669
|
+
chainId: chainId2,
|
|
670
|
+
gas: 50000n,
|
|
671
|
+
// standard ERC-20 approve
|
|
672
|
+
gasPrice,
|
|
673
|
+
nonce: nonce2
|
|
674
|
+
});
|
|
675
|
+
approvalExtension = {
|
|
676
|
+
erc20ApprovalGasSponsoring: {
|
|
677
|
+
info: {
|
|
678
|
+
from: wallet.address,
|
|
679
|
+
asset,
|
|
680
|
+
spender: PERMIT2_ADDRESS,
|
|
681
|
+
amount: MAX_UINT256.toString(),
|
|
682
|
+
signedTransaction: signedTx,
|
|
683
|
+
version: "1"
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
this.log("Permit2 approval signed for facilitator relay");
|
|
688
|
+
} else if (wallet.sendTransaction) {
|
|
689
|
+
this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
|
|
690
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
691
|
+
to: asset,
|
|
692
|
+
data: approveData,
|
|
693
|
+
value: 0n
|
|
694
|
+
});
|
|
695
|
+
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
696
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
697
|
+
this.log("Permit2 approval confirmed");
|
|
698
|
+
} else {
|
|
699
|
+
throw new Error(
|
|
700
|
+
"Permit2 payments require a wallet that supports signTransaction or sendTransaction for the one-time Permit2 approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
705
|
+
}
|
|
706
|
+
const nonceBytes = new Uint8Array(32);
|
|
707
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
708
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
709
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
710
|
+
const validAfter = now - 600;
|
|
711
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
712
|
+
const chainId = this.getChainId(accept.network);
|
|
713
|
+
const domain = {
|
|
714
|
+
name: "Permit2",
|
|
715
|
+
chainId: BigInt(chainId),
|
|
716
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
717
|
+
};
|
|
718
|
+
const message = {
|
|
719
|
+
permitted: {
|
|
720
|
+
token: asset,
|
|
721
|
+
amount: BigInt(amount)
|
|
722
|
+
},
|
|
723
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
724
|
+
nonce,
|
|
725
|
+
deadline: BigInt(deadline),
|
|
726
|
+
witness: {
|
|
727
|
+
to: payTo,
|
|
728
|
+
validAfter: BigInt(validAfter)
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const signature = await wallet.signTypedData({
|
|
732
|
+
domain,
|
|
733
|
+
types: PERMIT2_WITNESS_TYPES,
|
|
734
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
735
|
+
message
|
|
736
|
+
});
|
|
737
|
+
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
738
|
+
const payload = {
|
|
739
|
+
signature,
|
|
740
|
+
permit2Authorization: {
|
|
741
|
+
from: wallet.address,
|
|
742
|
+
permitted: {
|
|
743
|
+
token: asset,
|
|
744
|
+
amount
|
|
745
|
+
},
|
|
746
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
747
|
+
nonce: nonce.toString(),
|
|
748
|
+
deadline: String(deadline),
|
|
749
|
+
witness: {
|
|
750
|
+
to: payTo,
|
|
751
|
+
validAfter: String(validAfter)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
return {
|
|
756
|
+
serialized: JSON.stringify(payload),
|
|
757
|
+
signature,
|
|
758
|
+
extensions: approvalExtension
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
763
|
+
*/
|
|
764
|
+
async readAllowance(rpcUrl, token, owner, spender) {
|
|
765
|
+
const selector = "0xdd62ed3e";
|
|
766
|
+
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
767
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
768
|
+
const data = selector + paddedOwner + paddedSpender;
|
|
769
|
+
try {
|
|
770
|
+
const response = await fetch(rpcUrl, {
|
|
771
|
+
method: "POST",
|
|
772
|
+
headers: { "Content-Type": "application/json" },
|
|
773
|
+
body: JSON.stringify({
|
|
774
|
+
jsonrpc: "2.0",
|
|
775
|
+
id: 1,
|
|
776
|
+
method: "eth_call",
|
|
777
|
+
params: [{ to: token, data }, "latest"]
|
|
778
|
+
})
|
|
779
|
+
});
|
|
780
|
+
const result = await response.json();
|
|
781
|
+
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
782
|
+
return BigInt(result.result);
|
|
783
|
+
} catch {
|
|
784
|
+
return 0n;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Encode ERC-20 approve(address,uint256) calldata.
|
|
789
|
+
*/
|
|
790
|
+
encodeApprove(spender, amount) {
|
|
791
|
+
const selector = "0x095ea7b3";
|
|
792
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
793
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
794
|
+
return selector + paddedSpender + paddedAmount;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
798
|
+
*/
|
|
799
|
+
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
800
|
+
const start = Date.now();
|
|
801
|
+
while (Date.now() - start < timeoutMs) {
|
|
802
|
+
try {
|
|
803
|
+
const response = await fetch(rpcUrl, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers: { "Content-Type": "application/json" },
|
|
806
|
+
body: JSON.stringify({
|
|
807
|
+
jsonrpc: "2.0",
|
|
808
|
+
id: 1,
|
|
809
|
+
method: "eth_getTransactionReceipt",
|
|
810
|
+
params: [txHash]
|
|
811
|
+
})
|
|
812
|
+
});
|
|
813
|
+
const result = await response.json();
|
|
814
|
+
if (result.result) {
|
|
815
|
+
if (result.result.status === "0x0") {
|
|
816
|
+
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
817
|
+
}
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
} catch (err) {
|
|
821
|
+
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
822
|
+
}
|
|
823
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
824
|
+
}
|
|
825
|
+
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Read gas price via eth_gasPrice RPC call.
|
|
829
|
+
*/
|
|
830
|
+
async readGasPrice(rpcUrl) {
|
|
831
|
+
try {
|
|
832
|
+
const response = await fetch(rpcUrl, {
|
|
833
|
+
method: "POST",
|
|
834
|
+
headers: { "Content-Type": "application/json" },
|
|
835
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
|
|
836
|
+
});
|
|
837
|
+
const result = await response.json();
|
|
838
|
+
return result.result ? BigInt(result.result) : 50000000n;
|
|
839
|
+
} catch {
|
|
840
|
+
return 50000000n;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Read transaction count (nonce) via eth_getTransactionCount RPC call.
|
|
845
|
+
*/
|
|
846
|
+
async readNonce(rpcUrl, address) {
|
|
847
|
+
try {
|
|
848
|
+
const response = await fetch(rpcUrl, {
|
|
849
|
+
method: "POST",
|
|
850
|
+
headers: { "Content-Type": "application/json" },
|
|
851
|
+
body: JSON.stringify({
|
|
852
|
+
jsonrpc: "2.0",
|
|
853
|
+
id: 1,
|
|
854
|
+
method: "eth_getTransactionCount",
|
|
855
|
+
params: [address, "latest"]
|
|
856
|
+
})
|
|
857
|
+
});
|
|
858
|
+
const result = await response.json();
|
|
859
|
+
return result.result ? parseInt(result.result, 16) : 0;
|
|
860
|
+
} catch {
|
|
861
|
+
return 0;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
866
|
+
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
867
|
+
*/
|
|
868
|
+
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
869
|
+
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
870
|
+
if (!strategy || strategy.mode === "exact") {
|
|
871
|
+
return total;
|
|
872
|
+
}
|
|
873
|
+
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
874
|
+
const buffered = total * multiple;
|
|
875
|
+
if (strategy.maxCapUsd) {
|
|
876
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
877
|
+
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
878
|
+
if (buffered > maxCap) return maxCap;
|
|
879
|
+
}
|
|
880
|
+
if (strategy.exactAboveUsd) {
|
|
881
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
882
|
+
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
883
|
+
if (BigInt(paymentAmount) > threshold) return total;
|
|
884
|
+
}
|
|
885
|
+
return buffered;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Infer token decimals from payment amount magnitude.
|
|
889
|
+
* BSC stablecoins use 18 decimals, all others use 6.
|
|
890
|
+
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
891
|
+
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
892
|
+
*/
|
|
893
|
+
inferDecimals(amount) {
|
|
894
|
+
return amount.length > 12 ? 18 : 6;
|
|
895
|
+
}
|
|
470
896
|
};
|
|
471
897
|
function createEvmAdapter(config) {
|
|
472
898
|
return new EvmAdapter(config);
|
|
@@ -480,6 +906,9 @@ function isKnownUSDC(asset) {
|
|
|
480
906
|
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
481
907
|
if (addr.toLowerCase() === lc) return true;
|
|
482
908
|
}
|
|
909
|
+
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
910
|
+
if (addr.toLowerCase() === lc) return true;
|
|
911
|
+
}
|
|
483
912
|
return false;
|
|
484
913
|
}
|
|
485
914
|
|
|
@@ -588,6 +1017,19 @@ function createX402Client(config) {
|
|
|
588
1017
|
}
|
|
589
1018
|
return candidates[0];
|
|
590
1019
|
}
|
|
1020
|
+
function getChainDisplayName(network, adapterName) {
|
|
1021
|
+
const names = {
|
|
1022
|
+
"eip155:56": "BSC",
|
|
1023
|
+
"eip155:8453": "Base",
|
|
1024
|
+
"eip155:84532": "Base Sepolia",
|
|
1025
|
+
"eip155:42161": "Arbitrum",
|
|
1026
|
+
"eip155:137": "Polygon",
|
|
1027
|
+
"eip155:10": "Optimism",
|
|
1028
|
+
"eip155:43114": "Avalanche",
|
|
1029
|
+
"eip155:1": "Ethereum"
|
|
1030
|
+
};
|
|
1031
|
+
return names[network] || adapterName;
|
|
1032
|
+
}
|
|
591
1033
|
function getRpcUrl(network, adapter) {
|
|
592
1034
|
return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
|
|
593
1035
|
}
|
|
@@ -680,6 +1122,9 @@ function createX402Client(config) {
|
|
|
680
1122
|
accepted: accept,
|
|
681
1123
|
payload
|
|
682
1124
|
};
|
|
1125
|
+
if (signedTx.extensions) {
|
|
1126
|
+
paymentSignature.extensions = signedTx.extensions;
|
|
1127
|
+
}
|
|
683
1128
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
684
1129
|
const passResponse = await customFetch(passUrl, {
|
|
685
1130
|
...init,
|
|
@@ -802,10 +1247,10 @@ function createX402Client(config) {
|
|
|
802
1247
|
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
803
1248
|
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
804
1249
|
if (balance < requiredAmount) {
|
|
805
|
-
const
|
|
1250
|
+
const chainName = getChainDisplayName(accept.network, adapter.name);
|
|
806
1251
|
throw new X402Error(
|
|
807
1252
|
"insufficient_balance",
|
|
808
|
-
`Insufficient
|
|
1253
|
+
`Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
809
1254
|
);
|
|
810
1255
|
}
|
|
811
1256
|
log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
|
|
@@ -858,6 +1303,9 @@ function createX402Client(config) {
|
|
|
858
1303
|
accepted: accept,
|
|
859
1304
|
payload
|
|
860
1305
|
};
|
|
1306
|
+
if (signedTx.extensions) {
|
|
1307
|
+
paymentSignature.extensions = signedTx.extensions;
|
|
1308
|
+
}
|
|
861
1309
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
862
1310
|
log("Retrying request with payment...");
|
|
863
1311
|
const retryResponse = await fetchWithRetry(input, {
|
package/dist/react/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as X402Client } from '../sponsored-access-
|
|
2
|
-
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-
|
|
3
|
-
import { W as WalletSet, d as BalanceInfo } from '../types-
|
|
1
|
+
import { a as X402Client } from '../sponsored-access-CE7WpV5b.cjs';
|
|
2
|
+
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-CE7WpV5b.cjs';
|
|
3
|
+
import { W as WalletSet, d as BalanceInfo } from '../types-C_aQh02s.cjs';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
6
|
-
import { a as AccessPassTier } from '../types-
|
|
7
|
-
export { A as AccessPassClientConfig, b as AccessPassInfo, X as X402Error } from '../types-
|
|
6
|
+
import { a as AccessPassTier } from '../types-_iT11DL0.cjs';
|
|
7
|
+
export { A as AccessPassClientConfig, b as AccessPassInfo, X as X402Error } from '../types-_iT11DL0.cjs';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* React Hook for x402 v2 Payments
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as X402Client } from '../sponsored-access-
|
|
2
|
-
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-
|
|
3
|
-
import { W as WalletSet, d as BalanceInfo } from '../types-
|
|
1
|
+
import { a as X402Client } from '../sponsored-access-BVoucsEW.js';
|
|
2
|
+
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-BVoucsEW.js';
|
|
3
|
+
import { W as WalletSet, d as BalanceInfo } from '../types-DBS0XOsH.js';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
6
|
-
import { a as AccessPassTier } from '../types-
|
|
7
|
-
export { A as AccessPassClientConfig, b as AccessPassInfo, X as X402Error } from '../types-
|
|
6
|
+
import { a as AccessPassTier } from '../types-_iT11DL0.js';
|
|
7
|
+
export { A as AccessPassClientConfig, b as AccessPassInfo, X as X402Error } from '../types-_iT11DL0.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* React Hook for x402 v2 Payments
|