@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.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,393 @@ 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
|
+
let approvalExtension;
|
|
632
|
+
if (currentAllowance < BigInt(amount)) {
|
|
633
|
+
const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
|
|
634
|
+
if (wallet.signTransaction) {
|
|
635
|
+
this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
|
|
636
|
+
const chainId2 = this.getChainId(accept.network);
|
|
637
|
+
const gasPrice = await this.readGasPrice(url);
|
|
638
|
+
const nonce2 = await this.readNonce(url, wallet.address);
|
|
639
|
+
const signedTx = await wallet.signTransaction({
|
|
640
|
+
to: asset,
|
|
641
|
+
data: approveData,
|
|
642
|
+
chainId: chainId2,
|
|
643
|
+
gas: 50000n,
|
|
644
|
+
// standard ERC-20 approve
|
|
645
|
+
gasPrice,
|
|
646
|
+
nonce: nonce2
|
|
647
|
+
});
|
|
648
|
+
approvalExtension = {
|
|
649
|
+
erc20ApprovalGasSponsoring: {
|
|
650
|
+
info: {
|
|
651
|
+
from: wallet.address,
|
|
652
|
+
asset,
|
|
653
|
+
spender: PERMIT2_ADDRESS,
|
|
654
|
+
amount: MAX_UINT256.toString(),
|
|
655
|
+
signedTransaction: signedTx,
|
|
656
|
+
version: "1"
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
this.log("Permit2 approval signed for facilitator relay");
|
|
661
|
+
} else if (wallet.sendTransaction) {
|
|
662
|
+
this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
|
|
663
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
664
|
+
to: asset,
|
|
665
|
+
data: approveData,
|
|
666
|
+
value: 0n
|
|
667
|
+
});
|
|
668
|
+
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
669
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
670
|
+
this.log("Permit2 approval confirmed");
|
|
671
|
+
} else {
|
|
672
|
+
throw new Error(
|
|
673
|
+
"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."
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
678
|
+
}
|
|
679
|
+
const nonceBytes = new Uint8Array(32);
|
|
680
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
681
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
682
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
683
|
+
const validAfter = now - 600;
|
|
684
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
685
|
+
const chainId = this.getChainId(accept.network);
|
|
686
|
+
const domain = {
|
|
687
|
+
name: "Permit2",
|
|
688
|
+
chainId: BigInt(chainId),
|
|
689
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
690
|
+
};
|
|
691
|
+
const message = {
|
|
692
|
+
permitted: {
|
|
693
|
+
token: asset,
|
|
694
|
+
amount: BigInt(amount)
|
|
695
|
+
},
|
|
696
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
697
|
+
nonce,
|
|
698
|
+
deadline: BigInt(deadline),
|
|
699
|
+
witness: {
|
|
700
|
+
to: payTo,
|
|
701
|
+
validAfter: BigInt(validAfter)
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
const signature = await wallet.signTypedData({
|
|
705
|
+
domain,
|
|
706
|
+
types: PERMIT2_WITNESS_TYPES,
|
|
707
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
708
|
+
message
|
|
709
|
+
});
|
|
710
|
+
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
711
|
+
const payload = {
|
|
712
|
+
signature,
|
|
713
|
+
permit2Authorization: {
|
|
714
|
+
from: wallet.address,
|
|
715
|
+
permitted: {
|
|
716
|
+
token: asset,
|
|
717
|
+
amount
|
|
718
|
+
},
|
|
719
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
720
|
+
nonce: nonce.toString(),
|
|
721
|
+
deadline: String(deadline),
|
|
722
|
+
witness: {
|
|
723
|
+
to: payTo,
|
|
724
|
+
validAfter: String(validAfter)
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
return {
|
|
729
|
+
serialized: JSON.stringify(payload),
|
|
730
|
+
signature,
|
|
731
|
+
extensions: approvalExtension
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
736
|
+
*/
|
|
737
|
+
async readAllowance(rpcUrl, token, owner, spender) {
|
|
738
|
+
const selector = "0xdd62ed3e";
|
|
739
|
+
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
740
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
741
|
+
const data = selector + paddedOwner + paddedSpender;
|
|
742
|
+
try {
|
|
743
|
+
const response = await fetch(rpcUrl, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: { "Content-Type": "application/json" },
|
|
746
|
+
body: JSON.stringify({
|
|
747
|
+
jsonrpc: "2.0",
|
|
748
|
+
id: 1,
|
|
749
|
+
method: "eth_call",
|
|
750
|
+
params: [{ to: token, data }, "latest"]
|
|
751
|
+
})
|
|
752
|
+
});
|
|
753
|
+
const result = await response.json();
|
|
754
|
+
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
755
|
+
return BigInt(result.result);
|
|
756
|
+
} catch {
|
|
757
|
+
return 0n;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Encode ERC-20 approve(address,uint256) calldata.
|
|
762
|
+
*/
|
|
763
|
+
encodeApprove(spender, amount) {
|
|
764
|
+
const selector = "0x095ea7b3";
|
|
765
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
766
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
767
|
+
return selector + paddedSpender + paddedAmount;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
771
|
+
*/
|
|
772
|
+
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
773
|
+
const start = Date.now();
|
|
774
|
+
while (Date.now() - start < timeoutMs) {
|
|
775
|
+
try {
|
|
776
|
+
const response = await fetch(rpcUrl, {
|
|
777
|
+
method: "POST",
|
|
778
|
+
headers: { "Content-Type": "application/json" },
|
|
779
|
+
body: JSON.stringify({
|
|
780
|
+
jsonrpc: "2.0",
|
|
781
|
+
id: 1,
|
|
782
|
+
method: "eth_getTransactionReceipt",
|
|
783
|
+
params: [txHash]
|
|
784
|
+
})
|
|
785
|
+
});
|
|
786
|
+
const result = await response.json();
|
|
787
|
+
if (result.result) {
|
|
788
|
+
if (result.result.status === "0x0") {
|
|
789
|
+
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
790
|
+
}
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
} catch (err) {
|
|
794
|
+
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
795
|
+
}
|
|
796
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
797
|
+
}
|
|
798
|
+
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Read gas price via eth_gasPrice RPC call.
|
|
802
|
+
*/
|
|
803
|
+
async readGasPrice(rpcUrl) {
|
|
804
|
+
try {
|
|
805
|
+
const response = await fetch(rpcUrl, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: { "Content-Type": "application/json" },
|
|
808
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
|
|
809
|
+
});
|
|
810
|
+
const result = await response.json();
|
|
811
|
+
return result.result ? BigInt(result.result) : 50000000n;
|
|
812
|
+
} catch {
|
|
813
|
+
return 50000000n;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Read transaction count (nonce) via eth_getTransactionCount RPC call.
|
|
818
|
+
*/
|
|
819
|
+
async readNonce(rpcUrl, address) {
|
|
820
|
+
try {
|
|
821
|
+
const response = await fetch(rpcUrl, {
|
|
822
|
+
method: "POST",
|
|
823
|
+
headers: { "Content-Type": "application/json" },
|
|
824
|
+
body: JSON.stringify({
|
|
825
|
+
jsonrpc: "2.0",
|
|
826
|
+
id: 1,
|
|
827
|
+
method: "eth_getTransactionCount",
|
|
828
|
+
params: [address, "latest"]
|
|
829
|
+
})
|
|
830
|
+
});
|
|
831
|
+
const result = await response.json();
|
|
832
|
+
return result.result ? parseInt(result.result, 16) : 0;
|
|
833
|
+
} catch {
|
|
834
|
+
return 0;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
839
|
+
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
840
|
+
*/
|
|
841
|
+
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
842
|
+
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
843
|
+
if (!strategy || strategy.mode === "exact") {
|
|
844
|
+
return total;
|
|
845
|
+
}
|
|
846
|
+
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
847
|
+
const buffered = total * multiple;
|
|
848
|
+
if (strategy.maxCapUsd) {
|
|
849
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
850
|
+
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
851
|
+
if (buffered > maxCap) return maxCap;
|
|
852
|
+
}
|
|
853
|
+
if (strategy.exactAboveUsd) {
|
|
854
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
855
|
+
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
856
|
+
if (BigInt(paymentAmount) > threshold) return total;
|
|
857
|
+
}
|
|
858
|
+
return buffered;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Infer token decimals from payment amount magnitude.
|
|
862
|
+
* BSC stablecoins use 18 decimals, all others use 6.
|
|
863
|
+
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
864
|
+
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
865
|
+
*/
|
|
866
|
+
inferDecimals(amount) {
|
|
867
|
+
return amount.length > 12 ? 18 : 6;
|
|
868
|
+
}
|
|
443
869
|
};
|
|
444
870
|
function createEvmAdapter(config) {
|
|
445
871
|
return new EvmAdapter(config);
|
|
@@ -453,6 +879,9 @@ function isKnownUSDC(asset) {
|
|
|
453
879
|
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
454
880
|
if (addr.toLowerCase() === lc) return true;
|
|
455
881
|
}
|
|
882
|
+
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
883
|
+
if (addr.toLowerCase() === lc) return true;
|
|
884
|
+
}
|
|
456
885
|
return false;
|
|
457
886
|
}
|
|
458
887
|
|
|
@@ -561,6 +990,19 @@ function createX402Client(config) {
|
|
|
561
990
|
}
|
|
562
991
|
return candidates[0];
|
|
563
992
|
}
|
|
993
|
+
function getChainDisplayName(network, adapterName) {
|
|
994
|
+
const names = {
|
|
995
|
+
"eip155:56": "BSC",
|
|
996
|
+
"eip155:8453": "Base",
|
|
997
|
+
"eip155:84532": "Base Sepolia",
|
|
998
|
+
"eip155:42161": "Arbitrum",
|
|
999
|
+
"eip155:137": "Polygon",
|
|
1000
|
+
"eip155:10": "Optimism",
|
|
1001
|
+
"eip155:43114": "Avalanche",
|
|
1002
|
+
"eip155:1": "Ethereum"
|
|
1003
|
+
};
|
|
1004
|
+
return names[network] || adapterName;
|
|
1005
|
+
}
|
|
564
1006
|
function getRpcUrl(network, adapter) {
|
|
565
1007
|
return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
|
|
566
1008
|
}
|
|
@@ -653,6 +1095,9 @@ function createX402Client(config) {
|
|
|
653
1095
|
accepted: accept,
|
|
654
1096
|
payload
|
|
655
1097
|
};
|
|
1098
|
+
if (signedTx.extensions) {
|
|
1099
|
+
paymentSignature.extensions = signedTx.extensions;
|
|
1100
|
+
}
|
|
656
1101
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
657
1102
|
const passResponse = await customFetch(passUrl, {
|
|
658
1103
|
...init,
|
|
@@ -775,10 +1220,10 @@ function createX402Client(config) {
|
|
|
775
1220
|
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
776
1221
|
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
777
1222
|
if (balance < requiredAmount) {
|
|
778
|
-
const
|
|
1223
|
+
const chainName = getChainDisplayName(accept.network, adapter.name);
|
|
779
1224
|
throw new X402Error(
|
|
780
1225
|
"insufficient_balance",
|
|
781
|
-
`Insufficient
|
|
1226
|
+
`Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
782
1227
|
);
|
|
783
1228
|
}
|
|
784
1229
|
log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
|
|
@@ -831,6 +1276,9 @@ function createX402Client(config) {
|
|
|
831
1276
|
accepted: accept,
|
|
832
1277
|
payload
|
|
833
1278
|
};
|
|
1279
|
+
if (signedTx.extensions) {
|
|
1280
|
+
paymentSignature.extensions = signedTx.extensions;
|
|
1281
|
+
}
|
|
834
1282
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
835
1283
|
log("Retrying request with payment...");
|
|
836
1284
|
const retryResponse = await fetchWithRetry(input, {
|
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
|
}
|
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
|
}
|
|
@@ -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-DBS0XOsH.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
|
/**
|
|
@@ -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-C_aQh02s.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
|
/**
|