@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/adapters/index.js
CHANGED
|
@@ -200,16 +200,40 @@ function createSolanaAdapter(config) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// src/adapters/evm.ts
|
|
203
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
204
|
+
var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
205
|
+
var PERMIT2_WITNESS_TYPES = {
|
|
206
|
+
PermitWitnessTransferFrom: [
|
|
207
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
208
|
+
{ name: "spender", type: "address" },
|
|
209
|
+
{ name: "nonce", type: "uint256" },
|
|
210
|
+
{ name: "deadline", type: "uint256" },
|
|
211
|
+
{ name: "witness", type: "Witness" }
|
|
212
|
+
],
|
|
213
|
+
TokenPermissions: [
|
|
214
|
+
{ name: "token", type: "address" },
|
|
215
|
+
{ name: "amount", type: "uint256" }
|
|
216
|
+
],
|
|
217
|
+
Witness: [
|
|
218
|
+
{ name: "to", type: "address" },
|
|
219
|
+
{ name: "validAfter", type: "uint256" }
|
|
220
|
+
]
|
|
221
|
+
};
|
|
222
|
+
var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
203
223
|
var BASE_MAINNET = "eip155:8453";
|
|
204
224
|
var BASE_SEPOLIA = "eip155:84532";
|
|
205
225
|
var ARBITRUM_ONE = "eip155:42161";
|
|
206
226
|
var POLYGON = "eip155:137";
|
|
207
227
|
var OPTIMISM = "eip155:10";
|
|
208
228
|
var AVALANCHE = "eip155:43114";
|
|
229
|
+
var BSC_MAINNET = "eip155:56";
|
|
209
230
|
var SKALE_BASE = "eip155:1187947933";
|
|
210
231
|
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
211
232
|
var ETHEREUM_MAINNET = "eip155:1";
|
|
233
|
+
var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
|
|
234
|
+
var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
212
235
|
var CHAIN_IDS = {
|
|
236
|
+
[BSC_MAINNET]: 56,
|
|
213
237
|
[BASE_MAINNET]: 8453,
|
|
214
238
|
[BASE_SEPOLIA]: 84532,
|
|
215
239
|
[ARBITRUM_ONE]: 42161,
|
|
@@ -221,6 +245,7 @@ var CHAIN_IDS = {
|
|
|
221
245
|
[ETHEREUM_MAINNET]: 1
|
|
222
246
|
};
|
|
223
247
|
var DEFAULT_RPC_URLS2 = {
|
|
248
|
+
[BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
|
|
224
249
|
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
225
250
|
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
226
251
|
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
@@ -232,6 +257,7 @@ var DEFAULT_RPC_URLS2 = {
|
|
|
232
257
|
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
233
258
|
};
|
|
234
259
|
var USDC_ADDRESSES = {
|
|
260
|
+
[BSC_MAINNET]: BSC_USDC,
|
|
235
261
|
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
236
262
|
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
237
263
|
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
@@ -242,6 +268,10 @@ var USDC_ADDRESSES = {
|
|
|
242
268
|
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
243
269
|
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
244
270
|
};
|
|
271
|
+
var BSC_STABLECOIN_ADDRESSES = {
|
|
272
|
+
[BSC_USDT]: { symbol: "USDT", decimals: 18 },
|
|
273
|
+
[BSC_USDC]: { symbol: "USDC", decimals: 18 }
|
|
274
|
+
};
|
|
245
275
|
function isEvmWallet(wallet) {
|
|
246
276
|
if (!wallet || typeof wallet !== "object") return false;
|
|
247
277
|
const w = wallet;
|
|
@@ -249,7 +279,7 @@ function isEvmWallet(wallet) {
|
|
|
249
279
|
}
|
|
250
280
|
var EvmAdapter = class {
|
|
251
281
|
name = "EVM";
|
|
252
|
-
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
282
|
+
networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
253
283
|
config;
|
|
254
284
|
log;
|
|
255
285
|
constructor(config = {}) {
|
|
@@ -260,6 +290,7 @@ var EvmAdapter = class {
|
|
|
260
290
|
canHandle(network) {
|
|
261
291
|
if (this.networks.includes(network)) return true;
|
|
262
292
|
if (network === "base") return true;
|
|
293
|
+
if (network === "bsc") return true;
|
|
263
294
|
if (network === "ethereum") return true;
|
|
264
295
|
if (network === "arbitrum") return true;
|
|
265
296
|
if (network.startsWith("eip155:")) return true;
|
|
@@ -273,6 +304,7 @@ var EvmAdapter = class {
|
|
|
273
304
|
return DEFAULT_RPC_URLS2[network];
|
|
274
305
|
}
|
|
275
306
|
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
307
|
+
if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
|
|
276
308
|
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
277
309
|
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
278
310
|
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
@@ -292,6 +324,7 @@ var EvmAdapter = class {
|
|
|
292
324
|
return parseInt(chainIdStr, 10);
|
|
293
325
|
}
|
|
294
326
|
if (network === "base") return 8453;
|
|
327
|
+
if (network === "bsc") return 56;
|
|
295
328
|
if (network === "ethereum") return 1;
|
|
296
329
|
if (network === "arbitrum") return 42161;
|
|
297
330
|
return 8453;
|
|
@@ -341,13 +374,19 @@ var EvmAdapter = class {
|
|
|
341
374
|
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
342
375
|
return selector + paddedAddress;
|
|
343
376
|
}
|
|
344
|
-
async buildTransaction(accept, wallet,
|
|
377
|
+
async buildTransaction(accept, wallet, rpcUrl) {
|
|
345
378
|
if (!isEvmWallet(wallet)) {
|
|
346
379
|
throw new Error("Invalid EVM wallet");
|
|
347
380
|
}
|
|
348
381
|
if (!wallet.address) {
|
|
349
382
|
throw new Error("Wallet not connected");
|
|
350
383
|
}
|
|
384
|
+
if (accept.scheme === "exact-approval") {
|
|
385
|
+
return this.buildApprovalTransaction(accept, wallet, rpcUrl);
|
|
386
|
+
}
|
|
387
|
+
if (accept.extra?.assetTransferMethod === "permit2") {
|
|
388
|
+
return this.buildPermit2Transaction(accept, wallet, rpcUrl);
|
|
389
|
+
}
|
|
351
390
|
const { payTo, asset, extra } = accept;
|
|
352
391
|
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
353
392
|
if (!amount) {
|
|
@@ -418,6 +457,393 @@ var EvmAdapter = class {
|
|
|
418
457
|
signature
|
|
419
458
|
};
|
|
420
459
|
}
|
|
460
|
+
// ===========================================================================
|
|
461
|
+
// exact-approval: BSC and other chains without EIP-3009
|
|
462
|
+
// ===========================================================================
|
|
463
|
+
/**
|
|
464
|
+
* Build a payment transaction for chains that use the approval-based scheme.
|
|
465
|
+
* The facilitator's /supported response provides the EIP-712 domain and types
|
|
466
|
+
* in accept.extra, so the client doesn't hardcode any contract addresses.
|
|
467
|
+
*/
|
|
468
|
+
async buildApprovalTransaction(accept, wallet, rpcUrl) {
|
|
469
|
+
const { payTo, asset, extra } = accept;
|
|
470
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
471
|
+
if (!amount) {
|
|
472
|
+
throw new Error("Missing amount in payment requirements");
|
|
473
|
+
}
|
|
474
|
+
const facilitatorContract = extra?.facilitatorContract;
|
|
475
|
+
if (!facilitatorContract) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
"exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (!wallet.signTypedData) {
|
|
481
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
482
|
+
}
|
|
483
|
+
this.log("Building approval-based transaction:", {
|
|
484
|
+
from: wallet.address,
|
|
485
|
+
to: payTo,
|
|
486
|
+
amount,
|
|
487
|
+
asset,
|
|
488
|
+
network: accept.network,
|
|
489
|
+
facilitatorContract
|
|
490
|
+
});
|
|
491
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
492
|
+
const fee = extra?.fee ?? "0";
|
|
493
|
+
const totalNeeded = BigInt(amount) + BigInt(fee);
|
|
494
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
|
|
495
|
+
if (currentAllowance < totalNeeded) {
|
|
496
|
+
if (!wallet.sendTransaction) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
"BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
|
|
502
|
+
this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
|
|
503
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
504
|
+
to: asset,
|
|
505
|
+
data: this.encodeApprove(facilitatorContract, approvalAmount),
|
|
506
|
+
value: 0n
|
|
507
|
+
});
|
|
508
|
+
this.log(`Approval tx sent: ${approveTxHash}`);
|
|
509
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
510
|
+
this.log("Approval confirmed");
|
|
511
|
+
} else {
|
|
512
|
+
this.log("Sufficient allowance, skipping approval");
|
|
513
|
+
}
|
|
514
|
+
const nonceBytes = new Uint8Array(16);
|
|
515
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
516
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
|
|
517
|
+
const paymentIdBytes = new Uint8Array(32);
|
|
518
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
|
|
519
|
+
const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
520
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
521
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
522
|
+
const eip712Domain = extra?.eip712Domain;
|
|
523
|
+
const domain = eip712Domain ? {
|
|
524
|
+
name: eip712Domain.name,
|
|
525
|
+
version: eip712Domain.version,
|
|
526
|
+
chainId: BigInt(eip712Domain.chainId),
|
|
527
|
+
verifyingContract: eip712Domain.verifyingContract
|
|
528
|
+
} : {
|
|
529
|
+
name: "DexterBSCFacilitator",
|
|
530
|
+
version: "1",
|
|
531
|
+
chainId: BigInt(this.getChainId(accept.network)),
|
|
532
|
+
verifyingContract: facilitatorContract
|
|
533
|
+
};
|
|
534
|
+
const types = extra?.eip712Types ?? {
|
|
535
|
+
Payment: [
|
|
536
|
+
{ name: "from", type: "address" },
|
|
537
|
+
{ name: "to", type: "address" },
|
|
538
|
+
{ name: "token", type: "address" },
|
|
539
|
+
{ name: "amount", type: "uint256" },
|
|
540
|
+
{ name: "fee", type: "uint256" },
|
|
541
|
+
{ name: "nonce", type: "uint256" },
|
|
542
|
+
{ name: "deadline", type: "uint256" },
|
|
543
|
+
{ name: "paymentId", type: "bytes32" }
|
|
544
|
+
]
|
|
545
|
+
};
|
|
546
|
+
const message = {
|
|
547
|
+
from: wallet.address,
|
|
548
|
+
to: payTo,
|
|
549
|
+
token: asset,
|
|
550
|
+
amount: BigInt(amount),
|
|
551
|
+
fee: BigInt(fee),
|
|
552
|
+
nonce: BigInt(nonce),
|
|
553
|
+
deadline: BigInt(deadline),
|
|
554
|
+
paymentId
|
|
555
|
+
};
|
|
556
|
+
const signature = await wallet.signTypedData({
|
|
557
|
+
domain,
|
|
558
|
+
types,
|
|
559
|
+
primaryType: "Payment",
|
|
560
|
+
message
|
|
561
|
+
});
|
|
562
|
+
this.log("EIP-712 Payment signature obtained");
|
|
563
|
+
const payload = {
|
|
564
|
+
from: wallet.address,
|
|
565
|
+
to: payTo,
|
|
566
|
+
token: asset,
|
|
567
|
+
amount,
|
|
568
|
+
fee,
|
|
569
|
+
nonce,
|
|
570
|
+
deadline,
|
|
571
|
+
paymentId,
|
|
572
|
+
signature
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
serialized: JSON.stringify(payload),
|
|
576
|
+
signature
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
// ===========================================================================
|
|
580
|
+
// Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
|
|
581
|
+
// ===========================================================================
|
|
582
|
+
/**
|
|
583
|
+
* Build a Permit2 payment transaction. Used when the facilitator signals
|
|
584
|
+
* assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
|
|
585
|
+
*
|
|
586
|
+
* Flow:
|
|
587
|
+
* 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
|
|
588
|
+
* 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
|
|
589
|
+
* 3. Return { permit2Authorization, signature } payload for the facilitator.
|
|
590
|
+
*/
|
|
591
|
+
async buildPermit2Transaction(accept, wallet, rpcUrl) {
|
|
592
|
+
const { payTo, asset } = accept;
|
|
593
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
594
|
+
if (!amount) {
|
|
595
|
+
throw new Error("Missing amount in payment requirements");
|
|
596
|
+
}
|
|
597
|
+
if (!wallet.signTypedData) {
|
|
598
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
599
|
+
}
|
|
600
|
+
this.log("Building Permit2 transaction:", {
|
|
601
|
+
from: wallet.address,
|
|
602
|
+
to: payTo,
|
|
603
|
+
amount,
|
|
604
|
+
asset,
|
|
605
|
+
network: accept.network
|
|
606
|
+
});
|
|
607
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
608
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
|
|
609
|
+
let approvalExtension;
|
|
610
|
+
if (currentAllowance < BigInt(amount)) {
|
|
611
|
+
const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
|
|
612
|
+
if (wallet.signTransaction) {
|
|
613
|
+
this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
|
|
614
|
+
const chainId2 = this.getChainId(accept.network);
|
|
615
|
+
const gasPrice = await this.readGasPrice(url);
|
|
616
|
+
const nonce2 = await this.readNonce(url, wallet.address);
|
|
617
|
+
const signedTx = await wallet.signTransaction({
|
|
618
|
+
to: asset,
|
|
619
|
+
data: approveData,
|
|
620
|
+
chainId: chainId2,
|
|
621
|
+
gas: 50000n,
|
|
622
|
+
// standard ERC-20 approve
|
|
623
|
+
gasPrice,
|
|
624
|
+
nonce: nonce2
|
|
625
|
+
});
|
|
626
|
+
approvalExtension = {
|
|
627
|
+
erc20ApprovalGasSponsoring: {
|
|
628
|
+
info: {
|
|
629
|
+
from: wallet.address,
|
|
630
|
+
asset,
|
|
631
|
+
spender: PERMIT2_ADDRESS,
|
|
632
|
+
amount: MAX_UINT256.toString(),
|
|
633
|
+
signedTransaction: signedTx,
|
|
634
|
+
version: "1"
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
this.log("Permit2 approval signed for facilitator relay");
|
|
639
|
+
} else if (wallet.sendTransaction) {
|
|
640
|
+
this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
|
|
641
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
642
|
+
to: asset,
|
|
643
|
+
data: approveData,
|
|
644
|
+
value: 0n
|
|
645
|
+
});
|
|
646
|
+
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
647
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
648
|
+
this.log("Permit2 approval confirmed");
|
|
649
|
+
} else {
|
|
650
|
+
throw new Error(
|
|
651
|
+
"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."
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
656
|
+
}
|
|
657
|
+
const nonceBytes = new Uint8Array(32);
|
|
658
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
659
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
660
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
661
|
+
const validAfter = now - 600;
|
|
662
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
663
|
+
const chainId = this.getChainId(accept.network);
|
|
664
|
+
const domain = {
|
|
665
|
+
name: "Permit2",
|
|
666
|
+
chainId: BigInt(chainId),
|
|
667
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
668
|
+
};
|
|
669
|
+
const message = {
|
|
670
|
+
permitted: {
|
|
671
|
+
token: asset,
|
|
672
|
+
amount: BigInt(amount)
|
|
673
|
+
},
|
|
674
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
675
|
+
nonce,
|
|
676
|
+
deadline: BigInt(deadline),
|
|
677
|
+
witness: {
|
|
678
|
+
to: payTo,
|
|
679
|
+
validAfter: BigInt(validAfter)
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const signature = await wallet.signTypedData({
|
|
683
|
+
domain,
|
|
684
|
+
types: PERMIT2_WITNESS_TYPES,
|
|
685
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
686
|
+
message
|
|
687
|
+
});
|
|
688
|
+
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
689
|
+
const payload = {
|
|
690
|
+
signature,
|
|
691
|
+
permit2Authorization: {
|
|
692
|
+
from: wallet.address,
|
|
693
|
+
permitted: {
|
|
694
|
+
token: asset,
|
|
695
|
+
amount
|
|
696
|
+
},
|
|
697
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
698
|
+
nonce: nonce.toString(),
|
|
699
|
+
deadline: String(deadline),
|
|
700
|
+
witness: {
|
|
701
|
+
to: payTo,
|
|
702
|
+
validAfter: String(validAfter)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
return {
|
|
707
|
+
serialized: JSON.stringify(payload),
|
|
708
|
+
signature,
|
|
709
|
+
extensions: approvalExtension
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
714
|
+
*/
|
|
715
|
+
async readAllowance(rpcUrl, token, owner, spender) {
|
|
716
|
+
const selector = "0xdd62ed3e";
|
|
717
|
+
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
718
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
719
|
+
const data = selector + paddedOwner + paddedSpender;
|
|
720
|
+
try {
|
|
721
|
+
const response = await fetch(rpcUrl, {
|
|
722
|
+
method: "POST",
|
|
723
|
+
headers: { "Content-Type": "application/json" },
|
|
724
|
+
body: JSON.stringify({
|
|
725
|
+
jsonrpc: "2.0",
|
|
726
|
+
id: 1,
|
|
727
|
+
method: "eth_call",
|
|
728
|
+
params: [{ to: token, data }, "latest"]
|
|
729
|
+
})
|
|
730
|
+
});
|
|
731
|
+
const result = await response.json();
|
|
732
|
+
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
733
|
+
return BigInt(result.result);
|
|
734
|
+
} catch {
|
|
735
|
+
return 0n;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Encode ERC-20 approve(address,uint256) calldata.
|
|
740
|
+
*/
|
|
741
|
+
encodeApprove(spender, amount) {
|
|
742
|
+
const selector = "0x095ea7b3";
|
|
743
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
744
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
745
|
+
return selector + paddedSpender + paddedAmount;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
749
|
+
*/
|
|
750
|
+
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
751
|
+
const start = Date.now();
|
|
752
|
+
while (Date.now() - start < timeoutMs) {
|
|
753
|
+
try {
|
|
754
|
+
const response = await fetch(rpcUrl, {
|
|
755
|
+
method: "POST",
|
|
756
|
+
headers: { "Content-Type": "application/json" },
|
|
757
|
+
body: JSON.stringify({
|
|
758
|
+
jsonrpc: "2.0",
|
|
759
|
+
id: 1,
|
|
760
|
+
method: "eth_getTransactionReceipt",
|
|
761
|
+
params: [txHash]
|
|
762
|
+
})
|
|
763
|
+
});
|
|
764
|
+
const result = await response.json();
|
|
765
|
+
if (result.result) {
|
|
766
|
+
if (result.result.status === "0x0") {
|
|
767
|
+
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
768
|
+
}
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
} catch (err) {
|
|
772
|
+
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
773
|
+
}
|
|
774
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
775
|
+
}
|
|
776
|
+
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Read gas price via eth_gasPrice RPC call.
|
|
780
|
+
*/
|
|
781
|
+
async readGasPrice(rpcUrl) {
|
|
782
|
+
try {
|
|
783
|
+
const response = await fetch(rpcUrl, {
|
|
784
|
+
method: "POST",
|
|
785
|
+
headers: { "Content-Type": "application/json" },
|
|
786
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
|
|
787
|
+
});
|
|
788
|
+
const result = await response.json();
|
|
789
|
+
return result.result ? BigInt(result.result) : 50000000n;
|
|
790
|
+
} catch {
|
|
791
|
+
return 50000000n;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Read transaction count (nonce) via eth_getTransactionCount RPC call.
|
|
796
|
+
*/
|
|
797
|
+
async readNonce(rpcUrl, address) {
|
|
798
|
+
try {
|
|
799
|
+
const response = await fetch(rpcUrl, {
|
|
800
|
+
method: "POST",
|
|
801
|
+
headers: { "Content-Type": "application/json" },
|
|
802
|
+
body: JSON.stringify({
|
|
803
|
+
jsonrpc: "2.0",
|
|
804
|
+
id: 1,
|
|
805
|
+
method: "eth_getTransactionCount",
|
|
806
|
+
params: [address, "latest"]
|
|
807
|
+
})
|
|
808
|
+
});
|
|
809
|
+
const result = await response.json();
|
|
810
|
+
return result.result ? parseInt(result.result, 16) : 0;
|
|
811
|
+
} catch {
|
|
812
|
+
return 0;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
817
|
+
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
818
|
+
*/
|
|
819
|
+
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
820
|
+
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
821
|
+
if (!strategy || strategy.mode === "exact") {
|
|
822
|
+
return total;
|
|
823
|
+
}
|
|
824
|
+
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
825
|
+
const buffered = total * multiple;
|
|
826
|
+
if (strategy.maxCapUsd) {
|
|
827
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
828
|
+
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
829
|
+
if (buffered > maxCap) return maxCap;
|
|
830
|
+
}
|
|
831
|
+
if (strategy.exactAboveUsd) {
|
|
832
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
833
|
+
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
834
|
+
if (BigInt(paymentAmount) > threshold) return total;
|
|
835
|
+
}
|
|
836
|
+
return buffered;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Infer token decimals from payment amount magnitude.
|
|
840
|
+
* BSC stablecoins use 18 decimals, all others use 6.
|
|
841
|
+
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
842
|
+
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
843
|
+
*/
|
|
844
|
+
inferDecimals(amount) {
|
|
845
|
+
return amount.length > 12 ? 18 : 6;
|
|
846
|
+
}
|
|
421
847
|
};
|
|
422
848
|
function createEvmAdapter(config) {
|
|
423
849
|
return new EvmAdapter(config);
|
|
@@ -431,6 +857,9 @@ function isKnownUSDC(asset) {
|
|
|
431
857
|
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
432
858
|
if (addr.toLowerCase() === lc) return true;
|
|
433
859
|
}
|
|
860
|
+
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
861
|
+
if (addr.toLowerCase() === lc) return true;
|
|
862
|
+
}
|
|
434
863
|
return false;
|
|
435
864
|
}
|
|
436
865
|
function createDefaultAdapters(verbose = false) {
|
|
@@ -447,9 +876,14 @@ export {
|
|
|
447
876
|
AVALANCHE,
|
|
448
877
|
BASE_MAINNET,
|
|
449
878
|
BASE_SEPOLIA,
|
|
879
|
+
BSC_MAINNET,
|
|
880
|
+
BSC_STABLECOIN_ADDRESSES,
|
|
881
|
+
BSC_USDC,
|
|
882
|
+
BSC_USDT,
|
|
450
883
|
ETHEREUM_MAINNET,
|
|
451
884
|
EvmAdapter,
|
|
452
885
|
OPTIMISM,
|
|
886
|
+
PERMIT2_ADDRESS,
|
|
453
887
|
POLYGON,
|
|
454
888
|
SKALE_BASE,
|
|
455
889
|
SKALE_BASE_SEPOLIA,
|
|
@@ -458,6 +892,7 @@ export {
|
|
|
458
892
|
SOLANA_TESTNET,
|
|
459
893
|
SolanaAdapter,
|
|
460
894
|
USDC_ADDRESSES,
|
|
895
|
+
X402_EXACT_PERMIT2_PROXY,
|
|
461
896
|
createDefaultAdapters,
|
|
462
897
|
createEvmAdapter,
|
|
463
898
|
createSolanaAdapter,
|