@dexterai/x402 1.4.0 → 1.5.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 +1 -1
- package/dist/adapters/index.d.cts +4 -4
- package/dist/adapters/index.d.ts +4 -4
- package/dist/client/index.cjs +182 -4
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +23 -5
- package/dist/client/index.d.ts +23 -5
- package/dist/client/index.js +182 -4
- package/dist/client/index.js.map +1 -1
- package/dist/{evm-ZDwQi4QL.d.ts → evm-71SZ7cjW.d.ts} +2 -2
- package/dist/{evm-BaoETN1Y.d.cts → evm-BYjwU6ZW.d.cts} +2 -2
- package/dist/react/index.cjs +323 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +108 -4
- package/dist/react/index.d.ts +108 -4
- package/dist/react/index.js +322 -3
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +263 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +101 -3
- package/dist/server/index.d.ts +101 -3
- package/dist/server/index.js +250 -0
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CQGDK_7X.d.cts → types--r7urkVI.d.cts} +1 -1
- package/dist/{types-DNx7-QUN.d.ts → types-BtpD4ULe.d.ts} +1 -1
- package/dist/{types-B7T6dZ-y.d.cts → types-CcVAaoro.d.cts} +64 -2
- package/dist/{types-B7T6dZ-y.d.ts → types-CcVAaoro.d.ts} +64 -2
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/{x402-client-COrn-FQk.d.ts → x402-client-BxQWcK2Z.d.ts} +8 -1
- package/dist/{x402-client-DHmpVhEK.d.cts → x402-client-Dcm2FQ9f.d.cts} +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ This SDK handles the entire flow automatically—you just call `fetch()` and pay
|
|
|
40
40
|
|
|
41
41
|
**Multi-chain.** Solana and Base (Ethereum L2) with the same API. Add wallets for both and the SDK picks the right one automatically.
|
|
42
42
|
|
|
43
|
-
**Works out of the box.** Built-in RPC proxy, pre-flight balance checks, automatic retry on 402. Uses the [Dexter facilitator](https://x402.dexter.cash) by default—
|
|
43
|
+
**Works out of the box.** Built-in RPC proxy, pre-flight balance checks, automatic retry on 402. Uses the [Dexter facilitator](https://x402.dexter.cash) by default—Solana's most feature-rich x402 facilitator.
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as ChainAdapter } from '../types
|
|
2
|
-
export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types
|
|
3
|
-
export { A as ARBITRUM_ONE, B as BASE_MAINNET, h as BASE_SEPOLIA, j as ETHEREUM_MAINNET, E as EvmAdapter, k as EvmWallet, d as SOLANA_DEVNET, S as SOLANA_MAINNET, e as SOLANA_TESTNET, b as SolanaAdapter, f as SolanaWallet, a as createEvmAdapter, c as createSolanaAdapter, g as isEvmWallet, i as isSolanaWallet } from '../evm-
|
|
4
|
-
import '../types-
|
|
1
|
+
import { C as ChainAdapter } from '../types--r7urkVI.cjs';
|
|
2
|
+
export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types--r7urkVI.cjs';
|
|
3
|
+
export { A as ARBITRUM_ONE, B as BASE_MAINNET, h as BASE_SEPOLIA, j as ETHEREUM_MAINNET, E as EvmAdapter, k as EvmWallet, d as SOLANA_DEVNET, S as SOLANA_MAINNET, e as SOLANA_TESTNET, b as SolanaAdapter, f as SolanaWallet, a as createEvmAdapter, c as createSolanaAdapter, g as isEvmWallet, i as isSolanaWallet } from '../evm-BYjwU6ZW.cjs';
|
|
4
|
+
import '../types-CcVAaoro.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create all default adapters
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as ChainAdapter } from '../types-
|
|
2
|
-
export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types-
|
|
3
|
-
export { A as ARBITRUM_ONE, B as BASE_MAINNET, h as BASE_SEPOLIA, j as ETHEREUM_MAINNET, E as EvmAdapter, k as EvmWallet, d as SOLANA_DEVNET, S as SOLANA_MAINNET, e as SOLANA_TESTNET, b as SolanaAdapter, f as SolanaWallet, a as createEvmAdapter, c as createSolanaAdapter, g as isEvmWallet, i as isSolanaWallet } from '../evm-
|
|
4
|
-
import '../types-
|
|
1
|
+
import { C as ChainAdapter } from '../types-BtpD4ULe.js';
|
|
2
|
+
export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types-BtpD4ULe.js';
|
|
3
|
+
export { A as ARBITRUM_ONE, B as BASE_MAINNET, h as BASE_SEPOLIA, j as ETHEREUM_MAINNET, E as EvmAdapter, k as EvmWallet, d as SOLANA_DEVNET, S as SOLANA_MAINNET, e as SOLANA_TESTNET, b as SolanaAdapter, f as SolanaWallet, a as createEvmAdapter, c as createSolanaAdapter, g as isEvmWallet, i as isSolanaWallet } from '../evm-71SZ7cjW.js';
|
|
4
|
+
import '../types-CcVAaoro.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create all default adapters
|
package/dist/client/index.cjs
CHANGED
|
@@ -614,10 +614,41 @@ function createX402Client(config) {
|
|
|
614
614
|
rpcUrls = {},
|
|
615
615
|
maxAmountAtomic,
|
|
616
616
|
fetch: customFetch = globalThis.fetch,
|
|
617
|
-
verbose = false
|
|
617
|
+
verbose = false,
|
|
618
|
+
accessPass: accessPassConfig
|
|
618
619
|
} = config;
|
|
619
620
|
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
620
621
|
};
|
|
622
|
+
const passCache = /* @__PURE__ */ new Map();
|
|
623
|
+
function getCachedPass(url) {
|
|
624
|
+
try {
|
|
625
|
+
const host = new URL(url).host;
|
|
626
|
+
const cached = passCache.get(host);
|
|
627
|
+
if (cached && cached.expiresAt > Date.now() / 1e3 + 10) {
|
|
628
|
+
return cached.jwt;
|
|
629
|
+
}
|
|
630
|
+
if (cached) {
|
|
631
|
+
passCache.delete(host);
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
function cachePass(url, jwt) {
|
|
638
|
+
try {
|
|
639
|
+
const host = new URL(url).host;
|
|
640
|
+
const parts = jwt.split(".");
|
|
641
|
+
if (parts.length === 3) {
|
|
642
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
643
|
+
if (payload.exp) {
|
|
644
|
+
passCache.set(host, { jwt, expiresAt: payload.exp });
|
|
645
|
+
log("Access pass cached for", host, "| expires:", new Date(payload.exp * 1e3).toISOString());
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
log("Failed to cache access pass");
|
|
650
|
+
}
|
|
651
|
+
}
|
|
621
652
|
const wallets = walletSet || {};
|
|
622
653
|
if (legacyWallet && !wallets.solana && isSolanaWallet(legacyWallet)) {
|
|
623
654
|
wallets.solana = legacyWallet;
|
|
@@ -652,13 +683,158 @@ function createX402Client(config) {
|
|
|
652
683
|
function getRpcUrl(network, adapter) {
|
|
653
684
|
return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
|
|
654
685
|
}
|
|
686
|
+
async function purchaseAccessPass(input, init, originalResponse, passInfo, url) {
|
|
687
|
+
let tierQuery = "";
|
|
688
|
+
if (accessPassConfig?.preferTier && passInfo.tiers) {
|
|
689
|
+
const match2 = passInfo.tiers.find((t) => t.id === accessPassConfig.preferTier);
|
|
690
|
+
if (match2) {
|
|
691
|
+
if (accessPassConfig.maxSpend && parseFloat(match2.price) > parseFloat(accessPassConfig.maxSpend)) {
|
|
692
|
+
throw new X402Error(
|
|
693
|
+
"access_pass_exceeds_max_spend",
|
|
694
|
+
`Access pass tier "${match2.id}" costs $${match2.price}, exceeds max spend $${accessPassConfig.maxSpend}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
tierQuery = `tier=${match2.id}`;
|
|
698
|
+
}
|
|
699
|
+
} else if (accessPassConfig?.preferDuration && passInfo.ratePerHour) {
|
|
700
|
+
tierQuery = `duration=${accessPassConfig.preferDuration}`;
|
|
701
|
+
} else if (passInfo.tiers && passInfo.tiers.length > 0) {
|
|
702
|
+
const cheapest = passInfo.tiers[0];
|
|
703
|
+
if (accessPassConfig?.maxSpend && parseFloat(cheapest.price) > parseFloat(accessPassConfig.maxSpend)) {
|
|
704
|
+
throw new X402Error(
|
|
705
|
+
"access_pass_exceeds_max_spend",
|
|
706
|
+
`Cheapest access pass costs $${cheapest.price}, exceeds max spend $${accessPassConfig?.maxSpend}`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
tierQuery = `tier=${cheapest.id}`;
|
|
710
|
+
}
|
|
711
|
+
const passUrl = tierQuery ? url.includes("?") ? `${url}&${tierQuery}` : `${url}?${tierQuery}` : url;
|
|
712
|
+
log("Purchasing access pass:", tierQuery || "default tier");
|
|
713
|
+
const paymentRequiredHeader = originalResponse.headers.get("PAYMENT-REQUIRED");
|
|
714
|
+
if (!paymentRequiredHeader) return null;
|
|
715
|
+
let requirements;
|
|
716
|
+
try {
|
|
717
|
+
requirements = JSON.parse(atob(paymentRequiredHeader));
|
|
718
|
+
} catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
const match = findPaymentOption(requirements.accepts);
|
|
722
|
+
if (!match) return null;
|
|
723
|
+
const { accept, adapter, wallet } = match;
|
|
724
|
+
if (adapter.name === "Solana" && !accept.extra?.feePayer) return null;
|
|
725
|
+
const USDC_MINTS = [
|
|
726
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
727
|
+
"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
728
|
+
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
729
|
+
"0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
730
|
+
];
|
|
731
|
+
const decimals = accept.extra?.decimals ?? (USDC_MINTS.includes(accept.asset) ? 6 : void 0);
|
|
732
|
+
if (typeof decimals !== "number") return null;
|
|
733
|
+
const paymentAmount = accept.amount || accept.maxAmountRequired;
|
|
734
|
+
if (!paymentAmount) return null;
|
|
735
|
+
const rpcUrl = getRpcUrl(accept.network, adapter);
|
|
736
|
+
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
737
|
+
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
738
|
+
if (balance < requiredAmount) {
|
|
739
|
+
throw new X402Error(
|
|
740
|
+
"insufficient_balance",
|
|
741
|
+
`Insufficient balance for access pass. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
|
|
745
|
+
let payload;
|
|
746
|
+
if (adapter.name === "EVM") {
|
|
747
|
+
payload = JSON.parse(signedTx.serialized);
|
|
748
|
+
} else {
|
|
749
|
+
payload = { transaction: signedTx.serialized };
|
|
750
|
+
}
|
|
751
|
+
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
752
|
+
let resolvedResource = requirements.resource;
|
|
753
|
+
if (typeof requirements.resource === "string") {
|
|
754
|
+
try {
|
|
755
|
+
resolvedResource = new URL(requirements.resource, originalUrl).toString();
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
} else if (requirements.resource && typeof requirements.resource === "object" && "url" in requirements.resource) {
|
|
759
|
+
const rObj = requirements.resource;
|
|
760
|
+
try {
|
|
761
|
+
resolvedResource = { ...rObj, url: new URL(rObj.url, originalUrl).toString() };
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const paymentSignature = {
|
|
766
|
+
x402Version: accept.x402Version ?? 2,
|
|
767
|
+
resource: resolvedResource,
|
|
768
|
+
accepted: accept,
|
|
769
|
+
payload
|
|
770
|
+
};
|
|
771
|
+
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
772
|
+
const passResponse = await customFetch(passUrl, {
|
|
773
|
+
...init,
|
|
774
|
+
headers: {
|
|
775
|
+
...init?.headers || {},
|
|
776
|
+
"PAYMENT-SIGNATURE": paymentSignatureHeader
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
if (!passResponse.ok) {
|
|
780
|
+
log("Pass purchase failed:", passResponse.status);
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
const accessPassJwt = passResponse.headers.get("ACCESS-PASS");
|
|
784
|
+
if (!accessPassJwt) {
|
|
785
|
+
return passResponse;
|
|
786
|
+
}
|
|
787
|
+
cachePass(url, accessPassJwt);
|
|
788
|
+
log("Access pass purchased and cached");
|
|
789
|
+
const retryResponse = await customFetch(input, {
|
|
790
|
+
...init,
|
|
791
|
+
headers: {
|
|
792
|
+
...init?.headers || {},
|
|
793
|
+
"Authorization": `Bearer ${accessPassJwt}`
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
return retryResponse;
|
|
797
|
+
}
|
|
655
798
|
async function x402Fetch(input, init) {
|
|
656
|
-
|
|
799
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
800
|
+
log("Making request:", url);
|
|
801
|
+
if (accessPassConfig) {
|
|
802
|
+
const cachedJwt = getCachedPass(url);
|
|
803
|
+
if (cachedJwt) {
|
|
804
|
+
log("Using cached access pass");
|
|
805
|
+
const passResponse = await customFetch(input, {
|
|
806
|
+
...init,
|
|
807
|
+
headers: {
|
|
808
|
+
...init?.headers || {},
|
|
809
|
+
"Authorization": `Bearer ${cachedJwt}`
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
if (passResponse.status !== 401 && passResponse.status !== 402) {
|
|
813
|
+
return passResponse;
|
|
814
|
+
}
|
|
815
|
+
log("Cached pass rejected (status", passResponse.status, "), purchasing new pass");
|
|
816
|
+
try {
|
|
817
|
+
passCache.delete(new URL(url).host);
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
657
822
|
const response = await customFetch(input, init);
|
|
658
823
|
if (response.status !== 402) {
|
|
659
824
|
return response;
|
|
660
825
|
}
|
|
661
826
|
log("Received 402 Payment Required");
|
|
827
|
+
const passTiersHeader = response.headers.get("X-ACCESS-PASS-TIERS");
|
|
828
|
+
if (accessPassConfig && passTiersHeader) {
|
|
829
|
+
log("Server offers access passes, purchasing...");
|
|
830
|
+
try {
|
|
831
|
+
const passInfo = JSON.parse(atob(passTiersHeader));
|
|
832
|
+
const passResponse = await purchaseAccessPass(input, init, response, passInfo, url);
|
|
833
|
+
if (passResponse) return passResponse;
|
|
834
|
+
} catch (e) {
|
|
835
|
+
log("Access pass purchase failed, falling back to per-request payment:", e);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
662
838
|
const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
|
|
663
839
|
if (!paymentRequiredHeader) {
|
|
664
840
|
throw new X402Error(
|
|
@@ -875,7 +1051,8 @@ function wrapFetch(fetchImpl, options) {
|
|
|
875
1051
|
// facilitatorUrl is reserved for future use when we add facilitator selection
|
|
876
1052
|
rpcUrls,
|
|
877
1053
|
maxAmountAtomic,
|
|
878
|
-
verbose
|
|
1054
|
+
verbose,
|
|
1055
|
+
accessPass
|
|
879
1056
|
} = options;
|
|
880
1057
|
if (!walletPrivateKey && !evmPrivateKey) {
|
|
881
1058
|
throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
|
|
@@ -893,7 +1070,8 @@ function wrapFetch(fetchImpl, options) {
|
|
|
893
1070
|
rpcUrls,
|
|
894
1071
|
maxAmountAtomic,
|
|
895
1072
|
fetch: fetchImpl,
|
|
896
|
-
verbose
|
|
1073
|
+
verbose,
|
|
1074
|
+
accessPass
|
|
897
1075
|
};
|
|
898
1076
|
const client = createX402Client(clientConfig);
|
|
899
1077
|
return client.fetch.bind(client);
|