@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/README.md
CHANGED
|
@@ -700,6 +700,33 @@ interface SponsoredRecommendation {
|
|
|
700
700
|
5. Agent's LLM sees the recommendation and can call the suggested resource
|
|
701
701
|
6. If the agent calls it, the facilitator records a conversion with both tx hashes as proof
|
|
702
702
|
|
|
703
|
+
### Advertise Your API
|
|
704
|
+
|
|
705
|
+
Want your API recommended to agents across the x402 network? Create and fund campaigns through the Agent API — no signup, no accounts. Your wallet is your identity.
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
// All campaign management is x402-gated at x402ads.io
|
|
709
|
+
const x402Fetch = wrapFetch(fetch, { walletPrivateKey: key });
|
|
710
|
+
|
|
711
|
+
// Create a campaign ($0.10 USDC)
|
|
712
|
+
const res = await x402Fetch('https://x402ads.io/v1/agent/campaigns', {
|
|
713
|
+
method: 'POST',
|
|
714
|
+
headers: { 'Content-Type': 'application/json' },
|
|
715
|
+
body: JSON.stringify({
|
|
716
|
+
name: 'Promote My API',
|
|
717
|
+
rec_resource_url: 'https://api.example.com/data',
|
|
718
|
+
rec_description: 'Real-time market data',
|
|
719
|
+
rec_sponsor_name: 'Example Corp',
|
|
720
|
+
target_categories: ['defi', 'data'],
|
|
721
|
+
bid_strategy: 'cpa',
|
|
722
|
+
max_bid_amount: '50000',
|
|
723
|
+
budget_daily: '5000000',
|
|
724
|
+
}),
|
|
725
|
+
});
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Full advertiser guide: [docs.dexter.cash/docs/sponsored-access/for-advertisers](https://docs.dexter.cash/docs/sponsored-access/for-advertisers)
|
|
729
|
+
|
|
703
730
|
---
|
|
704
731
|
|
|
705
732
|
## API Reference
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -34,9 +34,14 @@ __export(adapters_exports, {
|
|
|
34
34
|
AVALANCHE: () => AVALANCHE,
|
|
35
35
|
BASE_MAINNET: () => BASE_MAINNET,
|
|
36
36
|
BASE_SEPOLIA: () => BASE_SEPOLIA,
|
|
37
|
+
BSC_MAINNET: () => BSC_MAINNET,
|
|
38
|
+
BSC_STABLECOIN_ADDRESSES: () => BSC_STABLECOIN_ADDRESSES,
|
|
39
|
+
BSC_USDC: () => BSC_USDC,
|
|
40
|
+
BSC_USDT: () => BSC_USDT,
|
|
37
41
|
ETHEREUM_MAINNET: () => ETHEREUM_MAINNET,
|
|
38
42
|
EvmAdapter: () => EvmAdapter,
|
|
39
43
|
OPTIMISM: () => OPTIMISM,
|
|
44
|
+
PERMIT2_ADDRESS: () => PERMIT2_ADDRESS,
|
|
40
45
|
POLYGON: () => POLYGON,
|
|
41
46
|
SKALE_BASE: () => SKALE_BASE,
|
|
42
47
|
SKALE_BASE_SEPOLIA: () => SKALE_BASE_SEPOLIA,
|
|
@@ -45,6 +50,7 @@ __export(adapters_exports, {
|
|
|
45
50
|
SOLANA_TESTNET: () => SOLANA_TESTNET,
|
|
46
51
|
SolanaAdapter: () => SolanaAdapter,
|
|
47
52
|
USDC_ADDRESSES: () => USDC_ADDRESSES,
|
|
53
|
+
X402_EXACT_PERMIT2_PROXY: () => X402_EXACT_PERMIT2_PROXY,
|
|
48
54
|
createDefaultAdapters: () => createDefaultAdapters,
|
|
49
55
|
createEvmAdapter: () => createEvmAdapter,
|
|
50
56
|
createSolanaAdapter: () => createSolanaAdapter,
|
|
@@ -244,16 +250,40 @@ function createSolanaAdapter(config) {
|
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
// src/adapters/evm.ts
|
|
253
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
254
|
+
var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
255
|
+
var PERMIT2_WITNESS_TYPES = {
|
|
256
|
+
PermitWitnessTransferFrom: [
|
|
257
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
258
|
+
{ name: "spender", type: "address" },
|
|
259
|
+
{ name: "nonce", type: "uint256" },
|
|
260
|
+
{ name: "deadline", type: "uint256" },
|
|
261
|
+
{ name: "witness", type: "Witness" }
|
|
262
|
+
],
|
|
263
|
+
TokenPermissions: [
|
|
264
|
+
{ name: "token", type: "address" },
|
|
265
|
+
{ name: "amount", type: "uint256" }
|
|
266
|
+
],
|
|
267
|
+
Witness: [
|
|
268
|
+
{ name: "to", type: "address" },
|
|
269
|
+
{ name: "validAfter", type: "uint256" }
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
247
273
|
var BASE_MAINNET = "eip155:8453";
|
|
248
274
|
var BASE_SEPOLIA = "eip155:84532";
|
|
249
275
|
var ARBITRUM_ONE = "eip155:42161";
|
|
250
276
|
var POLYGON = "eip155:137";
|
|
251
277
|
var OPTIMISM = "eip155:10";
|
|
252
278
|
var AVALANCHE = "eip155:43114";
|
|
279
|
+
var BSC_MAINNET = "eip155:56";
|
|
253
280
|
var SKALE_BASE = "eip155:1187947933";
|
|
254
281
|
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
255
282
|
var ETHEREUM_MAINNET = "eip155:1";
|
|
283
|
+
var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
|
|
284
|
+
var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
256
285
|
var CHAIN_IDS = {
|
|
286
|
+
[BSC_MAINNET]: 56,
|
|
257
287
|
[BASE_MAINNET]: 8453,
|
|
258
288
|
[BASE_SEPOLIA]: 84532,
|
|
259
289
|
[ARBITRUM_ONE]: 42161,
|
|
@@ -265,6 +295,7 @@ var CHAIN_IDS = {
|
|
|
265
295
|
[ETHEREUM_MAINNET]: 1
|
|
266
296
|
};
|
|
267
297
|
var DEFAULT_RPC_URLS2 = {
|
|
298
|
+
[BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
|
|
268
299
|
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
269
300
|
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
270
301
|
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
@@ -276,6 +307,7 @@ var DEFAULT_RPC_URLS2 = {
|
|
|
276
307
|
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
277
308
|
};
|
|
278
309
|
var USDC_ADDRESSES = {
|
|
310
|
+
[BSC_MAINNET]: BSC_USDC,
|
|
279
311
|
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
280
312
|
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
281
313
|
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
@@ -286,6 +318,10 @@ var USDC_ADDRESSES = {
|
|
|
286
318
|
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
287
319
|
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
288
320
|
};
|
|
321
|
+
var BSC_STABLECOIN_ADDRESSES = {
|
|
322
|
+
[BSC_USDT]: { symbol: "USDT", decimals: 18 },
|
|
323
|
+
[BSC_USDC]: { symbol: "USDC", decimals: 18 }
|
|
324
|
+
};
|
|
289
325
|
function isEvmWallet(wallet) {
|
|
290
326
|
if (!wallet || typeof wallet !== "object") return false;
|
|
291
327
|
const w = wallet;
|
|
@@ -293,7 +329,7 @@ function isEvmWallet(wallet) {
|
|
|
293
329
|
}
|
|
294
330
|
var EvmAdapter = class {
|
|
295
331
|
name = "EVM";
|
|
296
|
-
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
332
|
+
networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
297
333
|
config;
|
|
298
334
|
log;
|
|
299
335
|
constructor(config = {}) {
|
|
@@ -304,6 +340,7 @@ var EvmAdapter = class {
|
|
|
304
340
|
canHandle(network) {
|
|
305
341
|
if (this.networks.includes(network)) return true;
|
|
306
342
|
if (network === "base") return true;
|
|
343
|
+
if (network === "bsc") return true;
|
|
307
344
|
if (network === "ethereum") return true;
|
|
308
345
|
if (network === "arbitrum") return true;
|
|
309
346
|
if (network.startsWith("eip155:")) return true;
|
|
@@ -317,6 +354,7 @@ var EvmAdapter = class {
|
|
|
317
354
|
return DEFAULT_RPC_URLS2[network];
|
|
318
355
|
}
|
|
319
356
|
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
357
|
+
if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
|
|
320
358
|
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
321
359
|
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
322
360
|
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
@@ -336,6 +374,7 @@ var EvmAdapter = class {
|
|
|
336
374
|
return parseInt(chainIdStr, 10);
|
|
337
375
|
}
|
|
338
376
|
if (network === "base") return 8453;
|
|
377
|
+
if (network === "bsc") return 56;
|
|
339
378
|
if (network === "ethereum") return 1;
|
|
340
379
|
if (network === "arbitrum") return 42161;
|
|
341
380
|
return 8453;
|
|
@@ -385,13 +424,19 @@ var EvmAdapter = class {
|
|
|
385
424
|
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
386
425
|
return selector + paddedAddress;
|
|
387
426
|
}
|
|
388
|
-
async buildTransaction(accept, wallet,
|
|
427
|
+
async buildTransaction(accept, wallet, rpcUrl) {
|
|
389
428
|
if (!isEvmWallet(wallet)) {
|
|
390
429
|
throw new Error("Invalid EVM wallet");
|
|
391
430
|
}
|
|
392
431
|
if (!wallet.address) {
|
|
393
432
|
throw new Error("Wallet not connected");
|
|
394
433
|
}
|
|
434
|
+
if (accept.scheme === "exact-approval") {
|
|
435
|
+
return this.buildApprovalTransaction(accept, wallet, rpcUrl);
|
|
436
|
+
}
|
|
437
|
+
if (accept.extra?.assetTransferMethod === "permit2") {
|
|
438
|
+
return this.buildPermit2Transaction(accept, wallet, rpcUrl);
|
|
439
|
+
}
|
|
395
440
|
const { payTo, asset, extra } = accept;
|
|
396
441
|
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
397
442
|
if (!amount) {
|
|
@@ -462,6 +507,393 @@ var EvmAdapter = class {
|
|
|
462
507
|
signature
|
|
463
508
|
};
|
|
464
509
|
}
|
|
510
|
+
// ===========================================================================
|
|
511
|
+
// exact-approval: BSC and other chains without EIP-3009
|
|
512
|
+
// ===========================================================================
|
|
513
|
+
/**
|
|
514
|
+
* Build a payment transaction for chains that use the approval-based scheme.
|
|
515
|
+
* The facilitator's /supported response provides the EIP-712 domain and types
|
|
516
|
+
* in accept.extra, so the client doesn't hardcode any contract addresses.
|
|
517
|
+
*/
|
|
518
|
+
async buildApprovalTransaction(accept, wallet, rpcUrl) {
|
|
519
|
+
const { payTo, asset, extra } = accept;
|
|
520
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
521
|
+
if (!amount) {
|
|
522
|
+
throw new Error("Missing amount in payment requirements");
|
|
523
|
+
}
|
|
524
|
+
const facilitatorContract = extra?.facilitatorContract;
|
|
525
|
+
if (!facilitatorContract) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
"exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (!wallet.signTypedData) {
|
|
531
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
532
|
+
}
|
|
533
|
+
this.log("Building approval-based transaction:", {
|
|
534
|
+
from: wallet.address,
|
|
535
|
+
to: payTo,
|
|
536
|
+
amount,
|
|
537
|
+
asset,
|
|
538
|
+
network: accept.network,
|
|
539
|
+
facilitatorContract
|
|
540
|
+
});
|
|
541
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
542
|
+
const fee = extra?.fee ?? "0";
|
|
543
|
+
const totalNeeded = BigInt(amount) + BigInt(fee);
|
|
544
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
|
|
545
|
+
if (currentAllowance < totalNeeded) {
|
|
546
|
+
if (!wallet.sendTransaction) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
"BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
|
|
552
|
+
this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
|
|
553
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
554
|
+
to: asset,
|
|
555
|
+
data: this.encodeApprove(facilitatorContract, approvalAmount),
|
|
556
|
+
value: 0n
|
|
557
|
+
});
|
|
558
|
+
this.log(`Approval tx sent: ${approveTxHash}`);
|
|
559
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
560
|
+
this.log("Approval confirmed");
|
|
561
|
+
} else {
|
|
562
|
+
this.log("Sufficient allowance, skipping approval");
|
|
563
|
+
}
|
|
564
|
+
const nonceBytes = new Uint8Array(16);
|
|
565
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
566
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
|
|
567
|
+
const paymentIdBytes = new Uint8Array(32);
|
|
568
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
|
|
569
|
+
const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
570
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
571
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
572
|
+
const eip712Domain = extra?.eip712Domain;
|
|
573
|
+
const domain = eip712Domain ? {
|
|
574
|
+
name: eip712Domain.name,
|
|
575
|
+
version: eip712Domain.version,
|
|
576
|
+
chainId: BigInt(eip712Domain.chainId),
|
|
577
|
+
verifyingContract: eip712Domain.verifyingContract
|
|
578
|
+
} : {
|
|
579
|
+
name: "DexterBSCFacilitator",
|
|
580
|
+
version: "1",
|
|
581
|
+
chainId: BigInt(this.getChainId(accept.network)),
|
|
582
|
+
verifyingContract: facilitatorContract
|
|
583
|
+
};
|
|
584
|
+
const types = extra?.eip712Types ?? {
|
|
585
|
+
Payment: [
|
|
586
|
+
{ name: "from", type: "address" },
|
|
587
|
+
{ name: "to", type: "address" },
|
|
588
|
+
{ name: "token", type: "address" },
|
|
589
|
+
{ name: "amount", type: "uint256" },
|
|
590
|
+
{ name: "fee", type: "uint256" },
|
|
591
|
+
{ name: "nonce", type: "uint256" },
|
|
592
|
+
{ name: "deadline", type: "uint256" },
|
|
593
|
+
{ name: "paymentId", type: "bytes32" }
|
|
594
|
+
]
|
|
595
|
+
};
|
|
596
|
+
const message = {
|
|
597
|
+
from: wallet.address,
|
|
598
|
+
to: payTo,
|
|
599
|
+
token: asset,
|
|
600
|
+
amount: BigInt(amount),
|
|
601
|
+
fee: BigInt(fee),
|
|
602
|
+
nonce: BigInt(nonce),
|
|
603
|
+
deadline: BigInt(deadline),
|
|
604
|
+
paymentId
|
|
605
|
+
};
|
|
606
|
+
const signature = await wallet.signTypedData({
|
|
607
|
+
domain,
|
|
608
|
+
types,
|
|
609
|
+
primaryType: "Payment",
|
|
610
|
+
message
|
|
611
|
+
});
|
|
612
|
+
this.log("EIP-712 Payment signature obtained");
|
|
613
|
+
const payload = {
|
|
614
|
+
from: wallet.address,
|
|
615
|
+
to: payTo,
|
|
616
|
+
token: asset,
|
|
617
|
+
amount,
|
|
618
|
+
fee,
|
|
619
|
+
nonce,
|
|
620
|
+
deadline,
|
|
621
|
+
paymentId,
|
|
622
|
+
signature
|
|
623
|
+
};
|
|
624
|
+
return {
|
|
625
|
+
serialized: JSON.stringify(payload),
|
|
626
|
+
signature
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
// ===========================================================================
|
|
630
|
+
// Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
|
|
631
|
+
// ===========================================================================
|
|
632
|
+
/**
|
|
633
|
+
* Build a Permit2 payment transaction. Used when the facilitator signals
|
|
634
|
+
* assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
|
|
635
|
+
*
|
|
636
|
+
* Flow:
|
|
637
|
+
* 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
|
|
638
|
+
* 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
|
|
639
|
+
* 3. Return { permit2Authorization, signature } payload for the facilitator.
|
|
640
|
+
*/
|
|
641
|
+
async buildPermit2Transaction(accept, wallet, rpcUrl) {
|
|
642
|
+
const { payTo, asset } = accept;
|
|
643
|
+
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
644
|
+
if (!amount) {
|
|
645
|
+
throw new Error("Missing amount in payment requirements");
|
|
646
|
+
}
|
|
647
|
+
if (!wallet.signTypedData) {
|
|
648
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
649
|
+
}
|
|
650
|
+
this.log("Building Permit2 transaction:", {
|
|
651
|
+
from: wallet.address,
|
|
652
|
+
to: payTo,
|
|
653
|
+
amount,
|
|
654
|
+
asset,
|
|
655
|
+
network: accept.network
|
|
656
|
+
});
|
|
657
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
658
|
+
const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
|
|
659
|
+
let approvalExtension;
|
|
660
|
+
if (currentAllowance < BigInt(amount)) {
|
|
661
|
+
const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
|
|
662
|
+
if (wallet.signTransaction) {
|
|
663
|
+
this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
|
|
664
|
+
const chainId2 = this.getChainId(accept.network);
|
|
665
|
+
const gasPrice = await this.readGasPrice(url);
|
|
666
|
+
const nonce2 = await this.readNonce(url, wallet.address);
|
|
667
|
+
const signedTx = await wallet.signTransaction({
|
|
668
|
+
to: asset,
|
|
669
|
+
data: approveData,
|
|
670
|
+
chainId: chainId2,
|
|
671
|
+
gas: 50000n,
|
|
672
|
+
// standard ERC-20 approve
|
|
673
|
+
gasPrice,
|
|
674
|
+
nonce: nonce2
|
|
675
|
+
});
|
|
676
|
+
approvalExtension = {
|
|
677
|
+
erc20ApprovalGasSponsoring: {
|
|
678
|
+
info: {
|
|
679
|
+
from: wallet.address,
|
|
680
|
+
asset,
|
|
681
|
+
spender: PERMIT2_ADDRESS,
|
|
682
|
+
amount: MAX_UINT256.toString(),
|
|
683
|
+
signedTransaction: signedTx,
|
|
684
|
+
version: "1"
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
this.log("Permit2 approval signed for facilitator relay");
|
|
689
|
+
} else if (wallet.sendTransaction) {
|
|
690
|
+
this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
|
|
691
|
+
const approveTxHash = await wallet.sendTransaction({
|
|
692
|
+
to: asset,
|
|
693
|
+
data: approveData,
|
|
694
|
+
value: 0n
|
|
695
|
+
});
|
|
696
|
+
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
697
|
+
await this.waitForReceipt(url, approveTxHash);
|
|
698
|
+
this.log("Permit2 approval confirmed");
|
|
699
|
+
} else {
|
|
700
|
+
throw new Error(
|
|
701
|
+
"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."
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
706
|
+
}
|
|
707
|
+
const nonceBytes = new Uint8Array(32);
|
|
708
|
+
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
709
|
+
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
710
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
711
|
+
const validAfter = now - 600;
|
|
712
|
+
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
713
|
+
const chainId = this.getChainId(accept.network);
|
|
714
|
+
const domain = {
|
|
715
|
+
name: "Permit2",
|
|
716
|
+
chainId: BigInt(chainId),
|
|
717
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
718
|
+
};
|
|
719
|
+
const message = {
|
|
720
|
+
permitted: {
|
|
721
|
+
token: asset,
|
|
722
|
+
amount: BigInt(amount)
|
|
723
|
+
},
|
|
724
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
725
|
+
nonce,
|
|
726
|
+
deadline: BigInt(deadline),
|
|
727
|
+
witness: {
|
|
728
|
+
to: payTo,
|
|
729
|
+
validAfter: BigInt(validAfter)
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
const signature = await wallet.signTypedData({
|
|
733
|
+
domain,
|
|
734
|
+
types: PERMIT2_WITNESS_TYPES,
|
|
735
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
736
|
+
message
|
|
737
|
+
});
|
|
738
|
+
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
739
|
+
const payload = {
|
|
740
|
+
signature,
|
|
741
|
+
permit2Authorization: {
|
|
742
|
+
from: wallet.address,
|
|
743
|
+
permitted: {
|
|
744
|
+
token: asset,
|
|
745
|
+
amount
|
|
746
|
+
},
|
|
747
|
+
spender: X402_EXACT_PERMIT2_PROXY,
|
|
748
|
+
nonce: nonce.toString(),
|
|
749
|
+
deadline: String(deadline),
|
|
750
|
+
witness: {
|
|
751
|
+
to: payTo,
|
|
752
|
+
validAfter: String(validAfter)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
return {
|
|
757
|
+
serialized: JSON.stringify(payload),
|
|
758
|
+
signature,
|
|
759
|
+
extensions: approvalExtension
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
764
|
+
*/
|
|
765
|
+
async readAllowance(rpcUrl, token, owner, spender) {
|
|
766
|
+
const selector = "0xdd62ed3e";
|
|
767
|
+
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
768
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
769
|
+
const data = selector + paddedOwner + paddedSpender;
|
|
770
|
+
try {
|
|
771
|
+
const response = await fetch(rpcUrl, {
|
|
772
|
+
method: "POST",
|
|
773
|
+
headers: { "Content-Type": "application/json" },
|
|
774
|
+
body: JSON.stringify({
|
|
775
|
+
jsonrpc: "2.0",
|
|
776
|
+
id: 1,
|
|
777
|
+
method: "eth_call",
|
|
778
|
+
params: [{ to: token, data }, "latest"]
|
|
779
|
+
})
|
|
780
|
+
});
|
|
781
|
+
const result = await response.json();
|
|
782
|
+
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
783
|
+
return BigInt(result.result);
|
|
784
|
+
} catch {
|
|
785
|
+
return 0n;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Encode ERC-20 approve(address,uint256) calldata.
|
|
790
|
+
*/
|
|
791
|
+
encodeApprove(spender, amount) {
|
|
792
|
+
const selector = "0x095ea7b3";
|
|
793
|
+
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
794
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
795
|
+
return selector + paddedSpender + paddedAmount;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
799
|
+
*/
|
|
800
|
+
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
801
|
+
const start = Date.now();
|
|
802
|
+
while (Date.now() - start < timeoutMs) {
|
|
803
|
+
try {
|
|
804
|
+
const response = await fetch(rpcUrl, {
|
|
805
|
+
method: "POST",
|
|
806
|
+
headers: { "Content-Type": "application/json" },
|
|
807
|
+
body: JSON.stringify({
|
|
808
|
+
jsonrpc: "2.0",
|
|
809
|
+
id: 1,
|
|
810
|
+
method: "eth_getTransactionReceipt",
|
|
811
|
+
params: [txHash]
|
|
812
|
+
})
|
|
813
|
+
});
|
|
814
|
+
const result = await response.json();
|
|
815
|
+
if (result.result) {
|
|
816
|
+
if (result.result.status === "0x0") {
|
|
817
|
+
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
818
|
+
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
} catch (err) {
|
|
822
|
+
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
823
|
+
}
|
|
824
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
825
|
+
}
|
|
826
|
+
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Read gas price via eth_gasPrice RPC call.
|
|
830
|
+
*/
|
|
831
|
+
async readGasPrice(rpcUrl) {
|
|
832
|
+
try {
|
|
833
|
+
const response = await fetch(rpcUrl, {
|
|
834
|
+
method: "POST",
|
|
835
|
+
headers: { "Content-Type": "application/json" },
|
|
836
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
|
|
837
|
+
});
|
|
838
|
+
const result = await response.json();
|
|
839
|
+
return result.result ? BigInt(result.result) : 50000000n;
|
|
840
|
+
} catch {
|
|
841
|
+
return 50000000n;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Read transaction count (nonce) via eth_getTransactionCount RPC call.
|
|
846
|
+
*/
|
|
847
|
+
async readNonce(rpcUrl, address) {
|
|
848
|
+
try {
|
|
849
|
+
const response = await fetch(rpcUrl, {
|
|
850
|
+
method: "POST",
|
|
851
|
+
headers: { "Content-Type": "application/json" },
|
|
852
|
+
body: JSON.stringify({
|
|
853
|
+
jsonrpc: "2.0",
|
|
854
|
+
id: 1,
|
|
855
|
+
method: "eth_getTransactionCount",
|
|
856
|
+
params: [address, "latest"]
|
|
857
|
+
})
|
|
858
|
+
});
|
|
859
|
+
const result = await response.json();
|
|
860
|
+
return result.result ? parseInt(result.result, 16) : 0;
|
|
861
|
+
} catch {
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
867
|
+
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
868
|
+
*/
|
|
869
|
+
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
870
|
+
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
871
|
+
if (!strategy || strategy.mode === "exact") {
|
|
872
|
+
return total;
|
|
873
|
+
}
|
|
874
|
+
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
875
|
+
const buffered = total * multiple;
|
|
876
|
+
if (strategy.maxCapUsd) {
|
|
877
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
878
|
+
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
879
|
+
if (buffered > maxCap) return maxCap;
|
|
880
|
+
}
|
|
881
|
+
if (strategy.exactAboveUsd) {
|
|
882
|
+
const decimals = this.inferDecimals(paymentAmount);
|
|
883
|
+
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
884
|
+
if (BigInt(paymentAmount) > threshold) return total;
|
|
885
|
+
}
|
|
886
|
+
return buffered;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Infer token decimals from payment amount magnitude.
|
|
890
|
+
* BSC stablecoins use 18 decimals, all others use 6.
|
|
891
|
+
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
892
|
+
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
893
|
+
*/
|
|
894
|
+
inferDecimals(amount) {
|
|
895
|
+
return amount.length > 12 ? 18 : 6;
|
|
896
|
+
}
|
|
465
897
|
};
|
|
466
898
|
function createEvmAdapter(config) {
|
|
467
899
|
return new EvmAdapter(config);
|
|
@@ -475,6 +907,9 @@ function isKnownUSDC(asset) {
|
|
|
475
907
|
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
476
908
|
if (addr.toLowerCase() === lc) return true;
|
|
477
909
|
}
|
|
910
|
+
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
911
|
+
if (addr.toLowerCase() === lc) return true;
|
|
912
|
+
}
|
|
478
913
|
return false;
|
|
479
914
|
}
|
|
480
915
|
function createDefaultAdapters(verbose = false) {
|
|
@@ -492,9 +927,14 @@ function findAdapter(adapters, network) {
|
|
|
492
927
|
AVALANCHE,
|
|
493
928
|
BASE_MAINNET,
|
|
494
929
|
BASE_SEPOLIA,
|
|
930
|
+
BSC_MAINNET,
|
|
931
|
+
BSC_STABLECOIN_ADDRESSES,
|
|
932
|
+
BSC_USDC,
|
|
933
|
+
BSC_USDT,
|
|
495
934
|
ETHEREUM_MAINNET,
|
|
496
935
|
EvmAdapter,
|
|
497
936
|
OPTIMISM,
|
|
937
|
+
PERMIT2_ADDRESS,
|
|
498
938
|
POLYGON,
|
|
499
939
|
SKALE_BASE,
|
|
500
940
|
SKALE_BASE_SEPOLIA,
|
|
@@ -503,6 +943,7 @@ function findAdapter(adapters, network) {
|
|
|
503
943
|
SOLANA_TESTNET,
|
|
504
944
|
SolanaAdapter,
|
|
505
945
|
USDC_ADDRESSES,
|
|
946
|
+
X402_EXACT_PERMIT2_PROXY,
|
|
506
947
|
createDefaultAdapters,
|
|
507
948
|
createEvmAdapter,
|
|
508
949
|
createSolanaAdapter,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-
|
|
2
|
-
export {
|
|
3
|
-
import '../types-
|
|
1
|
+
import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-C_aQh02s.cjs';
|
|
2
|
+
export { q as ARBITRUM_ONE, s as AVALANCHE, A as AdapterConfig, B as BASE_MAINNET, p as BASE_SEPOLIA, l as BSC_MAINNET, o as BSC_STABLECOIN_ADDRESSES, n as BSC_USDC, m as BSC_USDT, d as BalanceInfo, v as ETHEREUM_MAINNET, E as EvmWallet, G as GenericWallet, O as OPTIMISM, P as PERMIT2_ADDRESS, r as POLYGON, t as SKALE_BASE, u as SKALE_BASE_SEPOLIA, h as SOLANA_DEVNET, b as SOLANA_MAINNET, j as SOLANA_TESTNET, g as SignedTransaction, S as SolanaWallet, U as USDC_ADDRESSES, W as WalletSet, X as X402_EXACT_PERMIT2_PROXY, a as createEvmAdapter, c as createSolanaAdapter, k as isEvmWallet, i as isSolanaWallet } from '../types-C_aQh02s.cjs';
|
|
3
|
+
import '../types-_iT11DL0.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if an asset address is a known USDC contract (any chain).
|
|
7
7
|
* Single source of truth for decimal inference in the client.
|
|
8
|
+
* Also recognizes BSC stablecoins (USDT + USDC, both 18 decimals).
|
|
8
9
|
*/
|
|
9
10
|
declare function isKnownUSDC(asset: string): boolean;
|
|
10
11
|
/**
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-
|
|
2
|
-
export {
|
|
3
|
-
import '../types-
|
|
1
|
+
import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-DBS0XOsH.js';
|
|
2
|
+
export { q as ARBITRUM_ONE, s as AVALANCHE, A as AdapterConfig, B as BASE_MAINNET, p as BASE_SEPOLIA, l as BSC_MAINNET, o as BSC_STABLECOIN_ADDRESSES, n as BSC_USDC, m as BSC_USDT, d as BalanceInfo, v as ETHEREUM_MAINNET, E as EvmWallet, G as GenericWallet, O as OPTIMISM, P as PERMIT2_ADDRESS, r as POLYGON, t as SKALE_BASE, u as SKALE_BASE_SEPOLIA, h as SOLANA_DEVNET, b as SOLANA_MAINNET, j as SOLANA_TESTNET, g as SignedTransaction, S as SolanaWallet, U as USDC_ADDRESSES, W as WalletSet, X as X402_EXACT_PERMIT2_PROXY, a as createEvmAdapter, c as createSolanaAdapter, k as isEvmWallet, i as isSolanaWallet } from '../types-DBS0XOsH.js';
|
|
3
|
+
import '../types-_iT11DL0.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if an asset address is a known USDC contract (any chain).
|
|
7
7
|
* Single source of truth for decimal inference in the client.
|
|
8
|
+
* Also recognizes BSC stablecoins (USDT + USDC, both 18 decimals).
|
|
8
9
|
*/
|
|
9
10
|
declare function isKnownUSDC(asset: string): boolean;
|
|
10
11
|
/**
|