@dexterai/x402 3.0.0 → 3.1.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/dist/adapters/index.cjs +1 -954
- package/dist/adapters/index.js +1 -903
- package/dist/client/index.cjs +1 -1907
- package/dist/client/index.d.cts +2 -223
- package/dist/client/index.d.ts +2 -223
- package/dist/client/index.js +1 -1871
- package/dist/react/index.cjs +1 -1820
- package/dist/react/index.js +1 -1792
- package/dist/server/index.cjs +23 -1901
- package/dist/server/index.js +23 -1831
- package/dist/utils/index.cjs +1 -111
- package/dist/utils/index.js +1 -80
- package/package.json +3 -2
package/dist/client/index.cjs
CHANGED
|
@@ -1,1907 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
|
-
var __export = (target, all) => {
|
|
12
|
-
for (var name in all)
|
|
13
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
-
};
|
|
15
|
-
var __copyProps = (to, from, except, desc) => {
|
|
16
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
-
for (let key of __getOwnPropNames(from))
|
|
18
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
-
}
|
|
21
|
-
return to;
|
|
22
|
-
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
31
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
-
|
|
33
|
-
// node_modules/base-x/src/esm/index.js
|
|
34
|
-
function base(ALPHABET2) {
|
|
35
|
-
if (ALPHABET2.length >= 255) {
|
|
36
|
-
throw new TypeError("Alphabet too long");
|
|
37
|
-
}
|
|
38
|
-
const BASE_MAP = new Uint8Array(256);
|
|
39
|
-
for (let j = 0; j < BASE_MAP.length; j++) {
|
|
40
|
-
BASE_MAP[j] = 255;
|
|
41
|
-
}
|
|
42
|
-
for (let i = 0; i < ALPHABET2.length; i++) {
|
|
43
|
-
const x = ALPHABET2.charAt(i);
|
|
44
|
-
const xc = x.charCodeAt(0);
|
|
45
|
-
if (BASE_MAP[xc] !== 255) {
|
|
46
|
-
throw new TypeError(x + " is ambiguous");
|
|
47
|
-
}
|
|
48
|
-
BASE_MAP[xc] = i;
|
|
49
|
-
}
|
|
50
|
-
const BASE = ALPHABET2.length;
|
|
51
|
-
const LEADER = ALPHABET2.charAt(0);
|
|
52
|
-
const FACTOR = Math.log(BASE) / Math.log(256);
|
|
53
|
-
const iFACTOR = Math.log(256) / Math.log(BASE);
|
|
54
|
-
function encode(source) {
|
|
55
|
-
if (source instanceof Uint8Array) {
|
|
56
|
-
} else if (ArrayBuffer.isView(source)) {
|
|
57
|
-
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
58
|
-
} else if (Array.isArray(source)) {
|
|
59
|
-
source = Uint8Array.from(source);
|
|
60
|
-
}
|
|
61
|
-
if (!(source instanceof Uint8Array)) {
|
|
62
|
-
throw new TypeError("Expected Uint8Array");
|
|
63
|
-
}
|
|
64
|
-
if (source.length === 0) {
|
|
65
|
-
return "";
|
|
66
|
-
}
|
|
67
|
-
let zeroes = 0;
|
|
68
|
-
let length = 0;
|
|
69
|
-
let pbegin = 0;
|
|
70
|
-
const pend = source.length;
|
|
71
|
-
while (pbegin !== pend && source[pbegin] === 0) {
|
|
72
|
-
pbegin++;
|
|
73
|
-
zeroes++;
|
|
74
|
-
}
|
|
75
|
-
const size = (pend - pbegin) * iFACTOR + 1 >>> 0;
|
|
76
|
-
const b58 = new Uint8Array(size);
|
|
77
|
-
while (pbegin !== pend) {
|
|
78
|
-
let carry = source[pbegin];
|
|
79
|
-
let i = 0;
|
|
80
|
-
for (let it1 = size - 1; (carry !== 0 || i < length) && it1 !== -1; it1--, i++) {
|
|
81
|
-
carry += 256 * b58[it1] >>> 0;
|
|
82
|
-
b58[it1] = carry % BASE >>> 0;
|
|
83
|
-
carry = carry / BASE >>> 0;
|
|
84
|
-
}
|
|
85
|
-
if (carry !== 0) {
|
|
86
|
-
throw new Error("Non-zero carry");
|
|
87
|
-
}
|
|
88
|
-
length = i;
|
|
89
|
-
pbegin++;
|
|
90
|
-
}
|
|
91
|
-
let it2 = size - length;
|
|
92
|
-
while (it2 !== size && b58[it2] === 0) {
|
|
93
|
-
it2++;
|
|
94
|
-
}
|
|
95
|
-
let str = LEADER.repeat(zeroes);
|
|
96
|
-
for (; it2 < size; ++it2) {
|
|
97
|
-
str += ALPHABET2.charAt(b58[it2]);
|
|
98
|
-
}
|
|
99
|
-
return str;
|
|
100
|
-
}
|
|
101
|
-
function decodeUnsafe(source) {
|
|
102
|
-
if (typeof source !== "string") {
|
|
103
|
-
throw new TypeError("Expected String");
|
|
104
|
-
}
|
|
105
|
-
if (source.length === 0) {
|
|
106
|
-
return new Uint8Array();
|
|
107
|
-
}
|
|
108
|
-
let psz = 0;
|
|
109
|
-
let zeroes = 0;
|
|
110
|
-
let length = 0;
|
|
111
|
-
while (source[psz] === LEADER) {
|
|
112
|
-
zeroes++;
|
|
113
|
-
psz++;
|
|
114
|
-
}
|
|
115
|
-
const size = (source.length - psz) * FACTOR + 1 >>> 0;
|
|
116
|
-
const b256 = new Uint8Array(size);
|
|
117
|
-
while (psz < source.length) {
|
|
118
|
-
const charCode = source.charCodeAt(psz);
|
|
119
|
-
if (charCode > 255) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
let carry = BASE_MAP[charCode];
|
|
123
|
-
if (carry === 255) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
let i = 0;
|
|
127
|
-
for (let it3 = size - 1; (carry !== 0 || i < length) && it3 !== -1; it3--, i++) {
|
|
128
|
-
carry += BASE * b256[it3] >>> 0;
|
|
129
|
-
b256[it3] = carry % 256 >>> 0;
|
|
130
|
-
carry = carry / 256 >>> 0;
|
|
131
|
-
}
|
|
132
|
-
if (carry !== 0) {
|
|
133
|
-
throw new Error("Non-zero carry");
|
|
134
|
-
}
|
|
135
|
-
length = i;
|
|
136
|
-
psz++;
|
|
137
|
-
}
|
|
138
|
-
let it4 = size - length;
|
|
139
|
-
while (it4 !== size && b256[it4] === 0) {
|
|
140
|
-
it4++;
|
|
141
|
-
}
|
|
142
|
-
const vch = new Uint8Array(zeroes + (size - it4));
|
|
143
|
-
let j = zeroes;
|
|
144
|
-
while (it4 !== size) {
|
|
145
|
-
vch[j++] = b256[it4++];
|
|
146
|
-
}
|
|
147
|
-
return vch;
|
|
148
|
-
}
|
|
149
|
-
function decode(string) {
|
|
150
|
-
const buffer = decodeUnsafe(string);
|
|
151
|
-
if (buffer) {
|
|
152
|
-
return buffer;
|
|
153
|
-
}
|
|
154
|
-
throw new Error("Non-base" + BASE + " character");
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
encode,
|
|
158
|
-
decodeUnsafe,
|
|
159
|
-
decode
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
var esm_default;
|
|
163
|
-
var init_esm = __esm({
|
|
164
|
-
"node_modules/base-x/src/esm/index.js"() {
|
|
165
|
-
"use strict";
|
|
166
|
-
esm_default = base;
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// node_modules/bs58/src/esm/index.js
|
|
171
|
-
var esm_exports = {};
|
|
172
|
-
__export(esm_exports, {
|
|
173
|
-
default: () => esm_default2
|
|
174
|
-
});
|
|
175
|
-
var ALPHABET, esm_default2;
|
|
176
|
-
var init_esm2 = __esm({
|
|
177
|
-
"node_modules/bs58/src/esm/index.js"() {
|
|
178
|
-
"use strict";
|
|
179
|
-
init_esm();
|
|
180
|
-
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
181
|
-
esm_default2 = esm_default(ALPHABET);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// src/client/index.ts
|
|
186
|
-
var client_exports = {};
|
|
187
|
-
__export(client_exports, {
|
|
188
|
-
BASE_MAINNET: () => BASE_MAINNET,
|
|
189
|
-
DEXTER_FACILITATOR_URL: () => DEXTER_FACILITATOR_URL,
|
|
190
|
-
KEYPAIR_SYMBOL: () => KEYPAIR_SYMBOL,
|
|
191
|
-
SOLANA_MAINNET: () => SOLANA_MAINNET,
|
|
192
|
-
USDC_MINT: () => USDC_MINT,
|
|
193
|
-
X402Error: () => X402Error,
|
|
194
|
-
capabilitySearch: () => capabilitySearch,
|
|
195
|
-
createBudgetAccount: () => createBudgetAccount,
|
|
196
|
-
createEvmAdapter: () => createEvmAdapter,
|
|
197
|
-
createEvmKeypairWallet: () => createEvmKeypairWallet,
|
|
198
|
-
createKeypairWallet: () => createKeypairWallet,
|
|
199
|
-
createSolanaAdapter: () => createSolanaAdapter,
|
|
200
|
-
createX402Client: () => createX402Client,
|
|
201
|
-
fireImpressionBeacon: () => fireImpressionBeacon,
|
|
202
|
-
getPaymentReceipt: () => getPaymentReceipt,
|
|
203
|
-
getSponsoredAccessInfo: () => getSponsoredAccessInfo,
|
|
204
|
-
getSponsoredRecommendations: () => getSponsoredRecommendations,
|
|
205
|
-
isEvmKeypairWallet: () => isEvmKeypairWallet,
|
|
206
|
-
isKeypairWallet: () => isKeypairWallet,
|
|
207
|
-
wrapFetch: () => wrapFetch
|
|
208
|
-
});
|
|
209
|
-
module.exports = __toCommonJS(client_exports);
|
|
210
|
-
|
|
211
|
-
// src/types.ts
|
|
212
|
-
var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
213
|
-
var DEXTER_FACILITATOR_URL = "https://x402.dexter.cash";
|
|
214
|
-
var X402Error = class _X402Error extends Error {
|
|
215
|
-
/** Error code for programmatic handling */
|
|
216
|
-
code;
|
|
217
|
-
/** Additional error details */
|
|
218
|
-
details;
|
|
219
|
-
constructor(code, message, details) {
|
|
220
|
-
super(message);
|
|
221
|
-
this.name = "X402Error";
|
|
222
|
-
this.code = code;
|
|
223
|
-
this.details = details;
|
|
224
|
-
Object.setPrototypeOf(this, _X402Error.prototype);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// src/adapters/solana.ts
|
|
229
|
-
var import_web3 = require("@solana/web3.js");
|
|
230
|
-
var import_spl_token = require("@solana/spl-token");
|
|
231
|
-
var SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
232
|
-
var SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
233
|
-
var SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
|
|
234
|
-
var DEFAULT_RPC_URLS = {
|
|
235
|
-
[SOLANA_MAINNET]: "https://api.dexter.cash/api/solana/rpc",
|
|
236
|
-
[SOLANA_DEVNET]: "https://api.devnet.solana.com",
|
|
237
|
-
[SOLANA_TESTNET]: "https://api.testnet.solana.com"
|
|
238
|
-
};
|
|
239
|
-
var DEFAULT_COMPUTE_UNIT_LIMIT = 12e3;
|
|
240
|
-
var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
241
|
-
function isSolanaWallet(wallet) {
|
|
242
|
-
if (!wallet || typeof wallet !== "object") return false;
|
|
243
|
-
const w = wallet;
|
|
244
|
-
return "publicKey" in w && "signTransaction" in w && typeof w.signTransaction === "function";
|
|
245
|
-
}
|
|
246
|
-
var SolanaAdapter = class {
|
|
247
|
-
name = "Solana";
|
|
248
|
-
networks = [SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET];
|
|
249
|
-
config;
|
|
250
|
-
log;
|
|
251
|
-
constructor(config = {}) {
|
|
252
|
-
this.config = config;
|
|
253
|
-
this.log = config.verbose ? console.log.bind(console, "[x402:solana]") : () => {
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
canHandle(network) {
|
|
257
|
-
if (this.networks.includes(network)) return true;
|
|
258
|
-
if (network === "solana") return true;
|
|
259
|
-
if (network === "solana-devnet") return true;
|
|
260
|
-
if (network === "solana-testnet") return true;
|
|
261
|
-
if (network.startsWith("solana:")) return true;
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
getDefaultRpcUrl(network) {
|
|
265
|
-
if (this.config.rpcUrls?.[network]) {
|
|
266
|
-
return this.config.rpcUrls[network];
|
|
267
|
-
}
|
|
268
|
-
if (DEFAULT_RPC_URLS[network]) {
|
|
269
|
-
return DEFAULT_RPC_URLS[network];
|
|
270
|
-
}
|
|
271
|
-
if (network === "solana") return DEFAULT_RPC_URLS[SOLANA_MAINNET];
|
|
272
|
-
if (network === "solana-devnet") return DEFAULT_RPC_URLS[SOLANA_DEVNET];
|
|
273
|
-
if (network === "solana-testnet") return DEFAULT_RPC_URLS[SOLANA_TESTNET];
|
|
274
|
-
return DEFAULT_RPC_URLS[SOLANA_MAINNET];
|
|
275
|
-
}
|
|
276
|
-
getAddress(wallet) {
|
|
277
|
-
if (!isSolanaWallet(wallet)) return null;
|
|
278
|
-
return wallet.publicKey?.toBase58() ?? null;
|
|
279
|
-
}
|
|
280
|
-
isConnected(wallet) {
|
|
281
|
-
if (!isSolanaWallet(wallet)) return false;
|
|
282
|
-
return wallet.publicKey !== null;
|
|
283
|
-
}
|
|
284
|
-
async getBalance(accept, wallet, rpcUrl) {
|
|
285
|
-
if (!isSolanaWallet(wallet) || !wallet.publicKey) {
|
|
286
|
-
return 0;
|
|
287
|
-
}
|
|
288
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
289
|
-
const connection = new import_web3.Connection(url, "confirmed");
|
|
290
|
-
const userPubkey = new import_web3.PublicKey(wallet.publicKey.toBase58());
|
|
291
|
-
const mintPubkey = new import_web3.PublicKey(accept.asset);
|
|
292
|
-
try {
|
|
293
|
-
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
294
|
-
const programId = mintInfo?.owner.toBase58() === import_spl_token.TOKEN_2022_PROGRAM_ID.toBase58() ? import_spl_token.TOKEN_2022_PROGRAM_ID : import_spl_token.TOKEN_PROGRAM_ID;
|
|
295
|
-
const ata = await (0, import_spl_token.getAssociatedTokenAddress)(
|
|
296
|
-
mintPubkey,
|
|
297
|
-
userPubkey,
|
|
298
|
-
false,
|
|
299
|
-
programId
|
|
300
|
-
);
|
|
301
|
-
const account = await (0, import_spl_token.getAccount)(connection, ata, void 0, programId);
|
|
302
|
-
const decimals = accept.extra?.decimals ?? 6;
|
|
303
|
-
return Number(account.amount) / Math.pow(10, decimals);
|
|
304
|
-
} catch (err) {
|
|
305
|
-
if (err && typeof err === "object" && "name" in err && (err.name === "TokenAccountNotFoundError" || err.name === "TokenInvalidAccountOwnerError")) {
|
|
306
|
-
return 0;
|
|
307
|
-
}
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
async buildTransaction(accept, wallet, rpcUrl) {
|
|
312
|
-
if (!isSolanaWallet(wallet)) {
|
|
313
|
-
throw new Error("Invalid Solana wallet");
|
|
314
|
-
}
|
|
315
|
-
if (!wallet.publicKey) {
|
|
316
|
-
throw new Error("Wallet not connected");
|
|
317
|
-
}
|
|
318
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
319
|
-
const connection = new import_web3.Connection(url, "confirmed");
|
|
320
|
-
const userPubkey = new import_web3.PublicKey(wallet.publicKey.toBase58());
|
|
321
|
-
const { payTo, asset, extra } = accept;
|
|
322
|
-
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
323
|
-
if (!amount) {
|
|
324
|
-
throw new Error("Missing amount in payment requirements");
|
|
325
|
-
}
|
|
326
|
-
if (!extra?.feePayer) {
|
|
327
|
-
throw new Error("Missing feePayer in payment requirements");
|
|
328
|
-
}
|
|
329
|
-
const feePayerPubkey = new import_web3.PublicKey(extra.feePayer);
|
|
330
|
-
const mintPubkey = new import_web3.PublicKey(asset);
|
|
331
|
-
const destinationPubkey = new import_web3.PublicKey(payTo);
|
|
332
|
-
this.log("Building transaction:", {
|
|
333
|
-
from: userPubkey.toBase58(),
|
|
334
|
-
to: payTo,
|
|
335
|
-
amount,
|
|
336
|
-
asset,
|
|
337
|
-
feePayer: extra.feePayer
|
|
338
|
-
});
|
|
339
|
-
const instructions = [];
|
|
340
|
-
instructions.push(
|
|
341
|
-
import_web3.ComputeBudgetProgram.setComputeUnitLimit({
|
|
342
|
-
units: DEFAULT_COMPUTE_UNIT_LIMIT
|
|
343
|
-
})
|
|
344
|
-
);
|
|
345
|
-
instructions.push(
|
|
346
|
-
import_web3.ComputeBudgetProgram.setComputeUnitPrice({
|
|
347
|
-
microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
|
|
348
|
-
})
|
|
349
|
-
);
|
|
350
|
-
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
351
|
-
if (!mintInfo) {
|
|
352
|
-
throw new Error(`Token mint ${asset} not found`);
|
|
353
|
-
}
|
|
354
|
-
const programId = mintInfo.owner.toBase58() === import_spl_token.TOKEN_2022_PROGRAM_ID.toBase58() ? import_spl_token.TOKEN_2022_PROGRAM_ID : import_spl_token.TOKEN_PROGRAM_ID;
|
|
355
|
-
const mint = await (0, import_spl_token.getMint)(connection, mintPubkey, void 0, programId);
|
|
356
|
-
if (typeof extra?.decimals === "number" && mint.decimals !== extra.decimals) {
|
|
357
|
-
this.log(
|
|
358
|
-
`Decimals mismatch: requirements say ${extra.decimals}, mint says ${mint.decimals}`
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(
|
|
362
|
-
mintPubkey,
|
|
363
|
-
userPubkey,
|
|
364
|
-
false,
|
|
365
|
-
programId
|
|
366
|
-
);
|
|
367
|
-
const destinationAta = await (0, import_spl_token.getAssociatedTokenAddress)(
|
|
368
|
-
mintPubkey,
|
|
369
|
-
destinationPubkey,
|
|
370
|
-
false,
|
|
371
|
-
programId
|
|
372
|
-
);
|
|
373
|
-
const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
|
|
374
|
-
if (!sourceAtaInfo) {
|
|
375
|
-
throw new Error(
|
|
376
|
-
`No token account found for ${asset}. Please ensure you have USDC in your wallet.`
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
|
|
380
|
-
if (!destAtaInfo) {
|
|
381
|
-
throw new Error(
|
|
382
|
-
`Seller token account not found. The seller (${payTo}) must have a USDC account.`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
const amountBigInt = BigInt(amount);
|
|
386
|
-
instructions.push(
|
|
387
|
-
(0, import_spl_token.createTransferCheckedInstruction)(
|
|
388
|
-
sourceAta,
|
|
389
|
-
mintPubkey,
|
|
390
|
-
destinationAta,
|
|
391
|
-
userPubkey,
|
|
392
|
-
amountBigInt,
|
|
393
|
-
mint.decimals,
|
|
394
|
-
[],
|
|
395
|
-
programId
|
|
396
|
-
)
|
|
397
|
-
);
|
|
398
|
-
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
399
|
-
const message = new import_web3.TransactionMessage({
|
|
400
|
-
payerKey: feePayerPubkey,
|
|
401
|
-
recentBlockhash: blockhash,
|
|
402
|
-
instructions
|
|
403
|
-
}).compileToV0Message();
|
|
404
|
-
const transaction = new import_web3.VersionedTransaction(message);
|
|
405
|
-
const signedTx = await wallet.signTransaction(transaction);
|
|
406
|
-
this.log("Transaction signed successfully");
|
|
407
|
-
return {
|
|
408
|
-
serialized: Buffer.from(signedTx.serialize()).toString("base64")
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
function createSolanaAdapter(config) {
|
|
413
|
-
return new SolanaAdapter(config);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/adapters/evm.ts
|
|
417
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
418
|
-
var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
419
|
-
var PERMIT2_WITNESS_TYPES = {
|
|
420
|
-
PermitWitnessTransferFrom: [
|
|
421
|
-
{ name: "permitted", type: "TokenPermissions" },
|
|
422
|
-
{ name: "spender", type: "address" },
|
|
423
|
-
{ name: "nonce", type: "uint256" },
|
|
424
|
-
{ name: "deadline", type: "uint256" },
|
|
425
|
-
{ name: "witness", type: "Witness" }
|
|
426
|
-
],
|
|
427
|
-
TokenPermissions: [
|
|
428
|
-
{ name: "token", type: "address" },
|
|
429
|
-
{ name: "amount", type: "uint256" }
|
|
430
|
-
],
|
|
431
|
-
Witness: [
|
|
432
|
-
{ name: "to", type: "address" },
|
|
433
|
-
{ name: "validAfter", type: "uint256" }
|
|
434
|
-
]
|
|
435
|
-
};
|
|
436
|
-
var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
437
|
-
var BASE_MAINNET = "eip155:8453";
|
|
438
|
-
var BASE_SEPOLIA = "eip155:84532";
|
|
439
|
-
var ARBITRUM_ONE = "eip155:42161";
|
|
440
|
-
var POLYGON = "eip155:137";
|
|
441
|
-
var OPTIMISM = "eip155:10";
|
|
442
|
-
var AVALANCHE = "eip155:43114";
|
|
443
|
-
var BSC_MAINNET = "eip155:56";
|
|
444
|
-
var SKALE_BASE = "eip155:1187947933";
|
|
445
|
-
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
446
|
-
var ETHEREUM_MAINNET = "eip155:1";
|
|
447
|
-
var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
|
|
448
|
-
var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
449
|
-
var CHAIN_IDS = {
|
|
450
|
-
[BSC_MAINNET]: 56,
|
|
451
|
-
[BASE_MAINNET]: 8453,
|
|
452
|
-
[BASE_SEPOLIA]: 84532,
|
|
453
|
-
[ARBITRUM_ONE]: 42161,
|
|
454
|
-
[POLYGON]: 137,
|
|
455
|
-
[OPTIMISM]: 10,
|
|
456
|
-
[AVALANCHE]: 43114,
|
|
457
|
-
[SKALE_BASE]: 1187947933,
|
|
458
|
-
[SKALE_BASE_SEPOLIA]: 324705682,
|
|
459
|
-
[ETHEREUM_MAINNET]: 1
|
|
460
|
-
};
|
|
461
|
-
var DEFAULT_RPC_URLS2 = {
|
|
462
|
-
[BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
|
|
463
|
-
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
464
|
-
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
465
|
-
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
466
|
-
[POLYGON]: "https://polygon-rpc.com",
|
|
467
|
-
[OPTIMISM]: "https://mainnet.optimism.io",
|
|
468
|
-
[AVALANCHE]: "https://api.avax.network/ext/bc/C/rpc",
|
|
469
|
-
[SKALE_BASE]: "https://skale-base.skalenodes.com/v1/base",
|
|
470
|
-
[SKALE_BASE_SEPOLIA]: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
471
|
-
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
472
|
-
};
|
|
473
|
-
var USDC_ADDRESSES = {
|
|
474
|
-
[BSC_MAINNET]: BSC_USDC,
|
|
475
|
-
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
476
|
-
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
477
|
-
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
478
|
-
[POLYGON]: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
479
|
-
[OPTIMISM]: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
480
|
-
[AVALANCHE]: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
481
|
-
[SKALE_BASE]: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
482
|
-
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
483
|
-
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
484
|
-
};
|
|
485
|
-
var BSC_STABLECOIN_ADDRESSES = {
|
|
486
|
-
[BSC_USDT]: { symbol: "USDT", decimals: 18 },
|
|
487
|
-
[BSC_USDC]: { symbol: "USDC", decimals: 18 }
|
|
488
|
-
};
|
|
489
|
-
function isEvmWallet(wallet) {
|
|
490
|
-
if (!wallet || typeof wallet !== "object") return false;
|
|
491
|
-
const w = wallet;
|
|
492
|
-
return "address" in w && typeof w.address === "string" && w.address.startsWith("0x");
|
|
493
|
-
}
|
|
494
|
-
var EvmAdapter = class {
|
|
495
|
-
name = "EVM";
|
|
496
|
-
networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
497
|
-
config;
|
|
498
|
-
log;
|
|
499
|
-
constructor(config = {}) {
|
|
500
|
-
this.config = config;
|
|
501
|
-
this.log = config.verbose ? console.log.bind(console, "[x402:evm]") : () => {
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
canHandle(network) {
|
|
505
|
-
if (this.networks.includes(network)) return true;
|
|
506
|
-
if (network === "base") return true;
|
|
507
|
-
if (network === "bsc") return true;
|
|
508
|
-
if (network === "ethereum") return true;
|
|
509
|
-
if (network === "arbitrum") return true;
|
|
510
|
-
if (network.startsWith("eip155:")) return true;
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
getDefaultRpcUrl(network) {
|
|
514
|
-
if (this.config.rpcUrls?.[network]) {
|
|
515
|
-
return this.config.rpcUrls[network];
|
|
516
|
-
}
|
|
517
|
-
if (DEFAULT_RPC_URLS2[network]) {
|
|
518
|
-
return DEFAULT_RPC_URLS2[network];
|
|
519
|
-
}
|
|
520
|
-
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
521
|
-
if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
|
|
522
|
-
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
523
|
-
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
524
|
-
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
525
|
-
}
|
|
526
|
-
getAddress(wallet) {
|
|
527
|
-
if (!isEvmWallet(wallet)) return null;
|
|
528
|
-
return wallet.address;
|
|
529
|
-
}
|
|
530
|
-
isConnected(wallet) {
|
|
531
|
-
if (!isEvmWallet(wallet)) return false;
|
|
532
|
-
return !!wallet.address;
|
|
533
|
-
}
|
|
534
|
-
getChainId(network) {
|
|
535
|
-
if (CHAIN_IDS[network]) return CHAIN_IDS[network];
|
|
536
|
-
if (network.startsWith("eip155:")) {
|
|
537
|
-
const chainIdStr = network.split(":")[1];
|
|
538
|
-
return parseInt(chainIdStr, 10);
|
|
539
|
-
}
|
|
540
|
-
if (network === "base") return 8453;
|
|
541
|
-
if (network === "bsc") return 56;
|
|
542
|
-
if (network === "ethereum") return 1;
|
|
543
|
-
if (network === "arbitrum") return 42161;
|
|
544
|
-
return 8453;
|
|
545
|
-
}
|
|
546
|
-
async getBalance(accept, wallet, rpcUrl) {
|
|
547
|
-
if (!isEvmWallet(wallet) || !wallet.address) {
|
|
548
|
-
return 0;
|
|
549
|
-
}
|
|
550
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
551
|
-
try {
|
|
552
|
-
const data = this.encodeBalanceOf(wallet.address);
|
|
553
|
-
const response = await fetch(url, {
|
|
554
|
-
method: "POST",
|
|
555
|
-
headers: { "Content-Type": "application/json" },
|
|
556
|
-
body: JSON.stringify({
|
|
557
|
-
jsonrpc: "2.0",
|
|
558
|
-
id: 1,
|
|
559
|
-
method: "eth_call",
|
|
560
|
-
params: [
|
|
561
|
-
{
|
|
562
|
-
to: accept.asset,
|
|
563
|
-
data
|
|
564
|
-
},
|
|
565
|
-
"latest"
|
|
566
|
-
]
|
|
567
|
-
})
|
|
568
|
-
});
|
|
569
|
-
if (!response.ok) {
|
|
570
|
-
throw new Error(`RPC request failed: ${response.status}`);
|
|
571
|
-
}
|
|
572
|
-
const result = await response.json();
|
|
573
|
-
if (result.error) {
|
|
574
|
-
throw new Error(`RPC error: ${JSON.stringify(result.error)}`);
|
|
575
|
-
}
|
|
576
|
-
if (!result.result || result.result === "0x") {
|
|
577
|
-
return 0;
|
|
578
|
-
}
|
|
579
|
-
const balance = BigInt(result.result);
|
|
580
|
-
const decimals = accept.extra?.decimals ?? 6;
|
|
581
|
-
return Number(balance) / Math.pow(10, decimals);
|
|
582
|
-
} catch (err) {
|
|
583
|
-
throw err;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
encodeBalanceOf(address) {
|
|
587
|
-
const selector = "0x70a08231";
|
|
588
|
-
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
589
|
-
return selector + paddedAddress;
|
|
590
|
-
}
|
|
591
|
-
async buildTransaction(accept, wallet, rpcUrl) {
|
|
592
|
-
if (!isEvmWallet(wallet)) {
|
|
593
|
-
throw new Error("Invalid EVM wallet");
|
|
594
|
-
}
|
|
595
|
-
if (!wallet.address) {
|
|
596
|
-
throw new Error("Wallet not connected");
|
|
597
|
-
}
|
|
598
|
-
if (accept.scheme === "exact-approval") {
|
|
599
|
-
return this.buildApprovalTransaction(accept, wallet, rpcUrl);
|
|
600
|
-
}
|
|
601
|
-
if (accept.extra?.assetTransferMethod === "permit2") {
|
|
602
|
-
return this.buildPermit2Transaction(accept, wallet, rpcUrl);
|
|
603
|
-
}
|
|
604
|
-
const { payTo, asset, extra } = accept;
|
|
605
|
-
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
606
|
-
if (!amount) {
|
|
607
|
-
throw new Error("Missing amount in payment requirements");
|
|
608
|
-
}
|
|
609
|
-
this.log("Building EVM transaction:", {
|
|
610
|
-
from: wallet.address,
|
|
611
|
-
to: payTo,
|
|
612
|
-
amount,
|
|
613
|
-
asset,
|
|
614
|
-
network: accept.network
|
|
615
|
-
});
|
|
616
|
-
const chainId = this.getChainId(accept.network);
|
|
617
|
-
const domain = {
|
|
618
|
-
name: extra?.name ?? "USD Coin",
|
|
619
|
-
version: extra?.version ?? "2",
|
|
620
|
-
chainId: BigInt(chainId),
|
|
621
|
-
verifyingContract: asset
|
|
622
|
-
};
|
|
623
|
-
const types = {
|
|
624
|
-
TransferWithAuthorization: [
|
|
625
|
-
{ name: "from", type: "address" },
|
|
626
|
-
{ name: "to", type: "address" },
|
|
627
|
-
{ name: "value", type: "uint256" },
|
|
628
|
-
{ name: "validAfter", type: "uint256" },
|
|
629
|
-
{ name: "validBefore", type: "uint256" },
|
|
630
|
-
{ name: "nonce", type: "bytes32" }
|
|
631
|
-
]
|
|
632
|
-
};
|
|
633
|
-
const nonceBytes = new Uint8Array(32);
|
|
634
|
-
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
635
|
-
const nonce = "0x" + [...nonceBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
636
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
637
|
-
const authorization = {
|
|
638
|
-
from: wallet.address,
|
|
639
|
-
to: payTo,
|
|
640
|
-
value: amount,
|
|
641
|
-
// string
|
|
642
|
-
validAfter: String(now - 600),
|
|
643
|
-
// 10 minutes before (matching upstream)
|
|
644
|
-
validBefore: String(now + (accept.maxTimeoutSeconds || 60)),
|
|
645
|
-
nonce
|
|
646
|
-
};
|
|
647
|
-
const message = {
|
|
648
|
-
from: wallet.address,
|
|
649
|
-
to: payTo,
|
|
650
|
-
value: BigInt(amount),
|
|
651
|
-
validAfter: BigInt(now - 600),
|
|
652
|
-
validBefore: BigInt(now + (accept.maxTimeoutSeconds || 60)),
|
|
653
|
-
nonce
|
|
654
|
-
};
|
|
655
|
-
if (!wallet.signTypedData) {
|
|
656
|
-
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
657
|
-
}
|
|
658
|
-
const signature = await wallet.signTypedData({
|
|
659
|
-
domain,
|
|
660
|
-
types,
|
|
661
|
-
primaryType: "TransferWithAuthorization",
|
|
662
|
-
message
|
|
663
|
-
});
|
|
664
|
-
this.log("EIP-712 signature obtained");
|
|
665
|
-
const payload = {
|
|
666
|
-
authorization,
|
|
667
|
-
signature
|
|
668
|
-
};
|
|
669
|
-
return {
|
|
670
|
-
serialized: JSON.stringify(payload),
|
|
671
|
-
signature
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
// ===========================================================================
|
|
675
|
-
// exact-approval: BSC and other chains without EIP-3009
|
|
676
|
-
// ===========================================================================
|
|
677
|
-
/**
|
|
678
|
-
* Build a payment transaction for chains that use the approval-based scheme.
|
|
679
|
-
* The facilitator's /supported response provides the EIP-712 domain and types
|
|
680
|
-
* in accept.extra, so the client doesn't hardcode any contract addresses.
|
|
681
|
-
*/
|
|
682
|
-
async buildApprovalTransaction(accept, wallet, rpcUrl) {
|
|
683
|
-
const { payTo, asset, extra } = accept;
|
|
684
|
-
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
685
|
-
if (!amount) {
|
|
686
|
-
throw new Error("Missing amount in payment requirements");
|
|
687
|
-
}
|
|
688
|
-
const facilitatorContract = extra?.facilitatorContract;
|
|
689
|
-
if (!facilitatorContract) {
|
|
690
|
-
throw new Error(
|
|
691
|
-
"exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
if (!wallet.signTypedData) {
|
|
695
|
-
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
696
|
-
}
|
|
697
|
-
this.log("Building approval-based transaction:", {
|
|
698
|
-
from: wallet.address,
|
|
699
|
-
to: payTo,
|
|
700
|
-
amount,
|
|
701
|
-
asset,
|
|
702
|
-
network: accept.network,
|
|
703
|
-
facilitatorContract
|
|
704
|
-
});
|
|
705
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
706
|
-
const fee = extra?.fee ?? "0";
|
|
707
|
-
const totalNeeded = BigInt(amount) + BigInt(fee);
|
|
708
|
-
const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
|
|
709
|
-
if (currentAllowance < totalNeeded) {
|
|
710
|
-
if (!wallet.sendTransaction) {
|
|
711
|
-
throw new Error(
|
|
712
|
-
"BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
|
|
716
|
-
this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
|
|
717
|
-
const approveTxHash = await wallet.sendTransaction({
|
|
718
|
-
to: asset,
|
|
719
|
-
data: this.encodeApprove(facilitatorContract, approvalAmount),
|
|
720
|
-
value: 0n
|
|
721
|
-
});
|
|
722
|
-
this.log(`Approval tx sent: ${approveTxHash}`);
|
|
723
|
-
await this.waitForReceipt(url, approveTxHash);
|
|
724
|
-
this.log("Approval confirmed");
|
|
725
|
-
} else {
|
|
726
|
-
this.log("Sufficient allowance, skipping approval");
|
|
727
|
-
}
|
|
728
|
-
const nonceBytes = new Uint8Array(16);
|
|
729
|
-
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
730
|
-
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
|
|
731
|
-
const paymentIdBytes = new Uint8Array(32);
|
|
732
|
-
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
|
|
733
|
-
const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
734
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
735
|
-
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
736
|
-
const eip712Domain = extra?.eip712Domain;
|
|
737
|
-
const domain = eip712Domain ? {
|
|
738
|
-
name: eip712Domain.name,
|
|
739
|
-
version: eip712Domain.version,
|
|
740
|
-
chainId: BigInt(eip712Domain.chainId),
|
|
741
|
-
verifyingContract: eip712Domain.verifyingContract
|
|
742
|
-
} : {
|
|
743
|
-
name: "DexterBSCFacilitator",
|
|
744
|
-
version: "1",
|
|
745
|
-
chainId: BigInt(this.getChainId(accept.network)),
|
|
746
|
-
verifyingContract: facilitatorContract
|
|
747
|
-
};
|
|
748
|
-
const types = extra?.eip712Types ?? {
|
|
749
|
-
Payment: [
|
|
750
|
-
{ name: "from", type: "address" },
|
|
751
|
-
{ name: "to", type: "address" },
|
|
752
|
-
{ name: "token", type: "address" },
|
|
753
|
-
{ name: "amount", type: "uint256" },
|
|
754
|
-
{ name: "fee", type: "uint256" },
|
|
755
|
-
{ name: "nonce", type: "uint256" },
|
|
756
|
-
{ name: "deadline", type: "uint256" },
|
|
757
|
-
{ name: "paymentId", type: "bytes32" }
|
|
758
|
-
]
|
|
759
|
-
};
|
|
760
|
-
const message = {
|
|
761
|
-
from: wallet.address,
|
|
762
|
-
to: payTo,
|
|
763
|
-
token: asset,
|
|
764
|
-
amount: BigInt(amount),
|
|
765
|
-
fee: BigInt(fee),
|
|
766
|
-
nonce: BigInt(nonce),
|
|
767
|
-
deadline: BigInt(deadline),
|
|
768
|
-
paymentId
|
|
769
|
-
};
|
|
770
|
-
const signature = await wallet.signTypedData({
|
|
771
|
-
domain,
|
|
772
|
-
types,
|
|
773
|
-
primaryType: "Payment",
|
|
774
|
-
message
|
|
775
|
-
});
|
|
776
|
-
this.log("EIP-712 Payment signature obtained");
|
|
777
|
-
const payload = {
|
|
778
|
-
from: wallet.address,
|
|
779
|
-
to: payTo,
|
|
780
|
-
token: asset,
|
|
781
|
-
amount,
|
|
782
|
-
fee,
|
|
783
|
-
nonce,
|
|
784
|
-
deadline,
|
|
785
|
-
paymentId,
|
|
786
|
-
signature
|
|
787
|
-
};
|
|
788
|
-
return {
|
|
789
|
-
serialized: JSON.stringify(payload),
|
|
790
|
-
signature
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
// ===========================================================================
|
|
794
|
-
// Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
|
|
795
|
-
// ===========================================================================
|
|
796
|
-
/**
|
|
797
|
-
* Build a Permit2 payment transaction. Used when the facilitator signals
|
|
798
|
-
* assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
|
|
799
|
-
*
|
|
800
|
-
* Flow:
|
|
801
|
-
* 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
|
|
802
|
-
* 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
|
|
803
|
-
* 3. Return { permit2Authorization, signature } payload for the facilitator.
|
|
804
|
-
*/
|
|
805
|
-
async buildPermit2Transaction(accept, wallet, rpcUrl) {
|
|
806
|
-
const { payTo, asset } = accept;
|
|
807
|
-
const amount = accept.amount ?? accept.maxAmountRequired;
|
|
808
|
-
if (!amount) {
|
|
809
|
-
throw new Error("Missing amount in payment requirements");
|
|
810
|
-
}
|
|
811
|
-
if (!wallet.signTypedData) {
|
|
812
|
-
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
813
|
-
}
|
|
814
|
-
this.log("Building Permit2 transaction:", {
|
|
815
|
-
from: wallet.address,
|
|
816
|
-
to: payTo,
|
|
817
|
-
amount,
|
|
818
|
-
asset,
|
|
819
|
-
network: accept.network
|
|
820
|
-
});
|
|
821
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
822
|
-
const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
|
|
823
|
-
let approvalExtension;
|
|
824
|
-
if (currentAllowance < BigInt(amount)) {
|
|
825
|
-
const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
|
|
826
|
-
if (wallet.signTransaction) {
|
|
827
|
-
this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
|
|
828
|
-
const chainId2 = this.getChainId(accept.network);
|
|
829
|
-
const gasPrice = await this.readGasPrice(url);
|
|
830
|
-
const nonce2 = await this.readNonce(url, wallet.address);
|
|
831
|
-
const signedTx = await wallet.signTransaction({
|
|
832
|
-
to: asset,
|
|
833
|
-
data: approveData,
|
|
834
|
-
chainId: chainId2,
|
|
835
|
-
gas: 50000n,
|
|
836
|
-
// standard ERC-20 approve
|
|
837
|
-
gasPrice,
|
|
838
|
-
nonce: nonce2
|
|
839
|
-
});
|
|
840
|
-
approvalExtension = {
|
|
841
|
-
erc20ApprovalGasSponsoring: {
|
|
842
|
-
info: {
|
|
843
|
-
from: wallet.address,
|
|
844
|
-
asset,
|
|
845
|
-
spender: PERMIT2_ADDRESS,
|
|
846
|
-
amount: MAX_UINT256.toString(),
|
|
847
|
-
signedTransaction: signedTx,
|
|
848
|
-
version: "1"
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
this.log("Permit2 approval signed for facilitator relay");
|
|
853
|
-
} else if (wallet.sendTransaction) {
|
|
854
|
-
this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
|
|
855
|
-
const approveTxHash = await wallet.sendTransaction({
|
|
856
|
-
to: asset,
|
|
857
|
-
data: approveData,
|
|
858
|
-
value: 0n
|
|
859
|
-
});
|
|
860
|
-
this.log(`Permit2 approval tx sent: ${approveTxHash}`);
|
|
861
|
-
await this.waitForReceipt(url, approveTxHash);
|
|
862
|
-
this.log("Permit2 approval confirmed");
|
|
863
|
-
} else {
|
|
864
|
-
throw new Error(
|
|
865
|
-
"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."
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
} else {
|
|
869
|
-
this.log("Sufficient Permit2 allowance, skipping approval");
|
|
870
|
-
}
|
|
871
|
-
const nonceBytes = new Uint8Array(32);
|
|
872
|
-
(globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
|
|
873
|
-
const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
|
|
874
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
875
|
-
const validAfter = now - 600;
|
|
876
|
-
const deadline = now + (accept.maxTimeoutSeconds || 300);
|
|
877
|
-
const chainId = this.getChainId(accept.network);
|
|
878
|
-
const domain = {
|
|
879
|
-
name: "Permit2",
|
|
880
|
-
chainId: BigInt(chainId),
|
|
881
|
-
verifyingContract: PERMIT2_ADDRESS
|
|
882
|
-
};
|
|
883
|
-
const message = {
|
|
884
|
-
permitted: {
|
|
885
|
-
token: asset,
|
|
886
|
-
amount: BigInt(amount)
|
|
887
|
-
},
|
|
888
|
-
spender: X402_EXACT_PERMIT2_PROXY,
|
|
889
|
-
nonce,
|
|
890
|
-
deadline: BigInt(deadline),
|
|
891
|
-
witness: {
|
|
892
|
-
to: payTo,
|
|
893
|
-
validAfter: BigInt(validAfter)
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
const signature = await wallet.signTypedData({
|
|
897
|
-
domain,
|
|
898
|
-
types: PERMIT2_WITNESS_TYPES,
|
|
899
|
-
primaryType: "PermitWitnessTransferFrom",
|
|
900
|
-
message
|
|
901
|
-
});
|
|
902
|
-
this.log("Permit2 PermitWitnessTransferFrom signature obtained");
|
|
903
|
-
const payload = {
|
|
904
|
-
signature,
|
|
905
|
-
permit2Authorization: {
|
|
906
|
-
from: wallet.address,
|
|
907
|
-
permitted: {
|
|
908
|
-
token: asset,
|
|
909
|
-
amount
|
|
910
|
-
},
|
|
911
|
-
spender: X402_EXACT_PERMIT2_PROXY,
|
|
912
|
-
nonce: nonce.toString(),
|
|
913
|
-
deadline: String(deadline),
|
|
914
|
-
witness: {
|
|
915
|
-
to: payTo,
|
|
916
|
-
validAfter: String(validAfter)
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
return {
|
|
921
|
-
serialized: JSON.stringify(payload),
|
|
922
|
-
signature,
|
|
923
|
-
extensions: approvalExtension
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Read ERC-20 allowance via raw eth_call (no viem dependency needed).
|
|
928
|
-
*/
|
|
929
|
-
async readAllowance(rpcUrl, token, owner, spender) {
|
|
930
|
-
const selector = "0xdd62ed3e";
|
|
931
|
-
const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
932
|
-
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
933
|
-
const data = selector + paddedOwner + paddedSpender;
|
|
934
|
-
try {
|
|
935
|
-
const response = await fetch(rpcUrl, {
|
|
936
|
-
method: "POST",
|
|
937
|
-
headers: { "Content-Type": "application/json" },
|
|
938
|
-
body: JSON.stringify({
|
|
939
|
-
jsonrpc: "2.0",
|
|
940
|
-
id: 1,
|
|
941
|
-
method: "eth_call",
|
|
942
|
-
params: [{ to: token, data }, "latest"]
|
|
943
|
-
})
|
|
944
|
-
});
|
|
945
|
-
const result = await response.json();
|
|
946
|
-
if (result.error || !result.result || result.result === "0x") return 0n;
|
|
947
|
-
return BigInt(result.result);
|
|
948
|
-
} catch {
|
|
949
|
-
return 0n;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Encode ERC-20 approve(address,uint256) calldata.
|
|
954
|
-
*/
|
|
955
|
-
encodeApprove(spender, amount) {
|
|
956
|
-
const selector = "0x095ea7b3";
|
|
957
|
-
const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
|
|
958
|
-
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
959
|
-
return selector + paddedSpender + paddedAmount;
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* Wait for a transaction receipt by polling eth_getTransactionReceipt.
|
|
963
|
-
*/
|
|
964
|
-
async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
965
|
-
const start = Date.now();
|
|
966
|
-
while (Date.now() - start < timeoutMs) {
|
|
967
|
-
try {
|
|
968
|
-
const response = await fetch(rpcUrl, {
|
|
969
|
-
method: "POST",
|
|
970
|
-
headers: { "Content-Type": "application/json" },
|
|
971
|
-
body: JSON.stringify({
|
|
972
|
-
jsonrpc: "2.0",
|
|
973
|
-
id: 1,
|
|
974
|
-
method: "eth_getTransactionReceipt",
|
|
975
|
-
params: [txHash]
|
|
976
|
-
})
|
|
977
|
-
});
|
|
978
|
-
const result = await response.json();
|
|
979
|
-
if (result.result) {
|
|
980
|
-
if (result.result.status === "0x0") {
|
|
981
|
-
throw new Error(`Approval transaction reverted: ${txHash}`);
|
|
982
|
-
}
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
} catch (err) {
|
|
986
|
-
if (err instanceof Error && err.message.includes("reverted")) throw err;
|
|
987
|
-
}
|
|
988
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
989
|
-
}
|
|
990
|
-
throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Read gas price via eth_gasPrice RPC call.
|
|
994
|
-
*/
|
|
995
|
-
async readGasPrice(rpcUrl) {
|
|
996
|
-
try {
|
|
997
|
-
const response = await fetch(rpcUrl, {
|
|
998
|
-
method: "POST",
|
|
999
|
-
headers: { "Content-Type": "application/json" },
|
|
1000
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
|
|
1001
|
-
});
|
|
1002
|
-
const result = await response.json();
|
|
1003
|
-
return result.result ? BigInt(result.result) : 50000000n;
|
|
1004
|
-
} catch {
|
|
1005
|
-
return 50000000n;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Read transaction count (nonce) via eth_getTransactionCount RPC call.
|
|
1010
|
-
*/
|
|
1011
|
-
async readNonce(rpcUrl, address) {
|
|
1012
|
-
try {
|
|
1013
|
-
const response = await fetch(rpcUrl, {
|
|
1014
|
-
method: "POST",
|
|
1015
|
-
headers: { "Content-Type": "application/json" },
|
|
1016
|
-
body: JSON.stringify({
|
|
1017
|
-
jsonrpc: "2.0",
|
|
1018
|
-
id: 1,
|
|
1019
|
-
method: "eth_getTransactionCount",
|
|
1020
|
-
params: [address, "latest"]
|
|
1021
|
-
})
|
|
1022
|
-
});
|
|
1023
|
-
const result = await response.json();
|
|
1024
|
-
return result.result ? parseInt(result.result, 16) : 0;
|
|
1025
|
-
} catch {
|
|
1026
|
-
return 0;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Calculate how much to approve based on the facilitator's approval strategy.
|
|
1031
|
-
* Buffered approvals reduce the number of on-chain approval txs for micropayments.
|
|
1032
|
-
*/
|
|
1033
|
-
calculateApprovalAmount(paymentAmount, fee, strategy) {
|
|
1034
|
-
const total = BigInt(paymentAmount) + BigInt(fee);
|
|
1035
|
-
if (!strategy || strategy.mode === "exact") {
|
|
1036
|
-
return total;
|
|
1037
|
-
}
|
|
1038
|
-
const multiple = BigInt(strategy.defaultMultiple ?? 10);
|
|
1039
|
-
const buffered = total * multiple;
|
|
1040
|
-
if (strategy.maxCapUsd) {
|
|
1041
|
-
const decimals = this.inferDecimals(paymentAmount);
|
|
1042
|
-
const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
|
|
1043
|
-
if (buffered > maxCap) return maxCap;
|
|
1044
|
-
}
|
|
1045
|
-
if (strategy.exactAboveUsd) {
|
|
1046
|
-
const decimals = this.inferDecimals(paymentAmount);
|
|
1047
|
-
const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
|
|
1048
|
-
if (BigInt(paymentAmount) > threshold) return total;
|
|
1049
|
-
}
|
|
1050
|
-
return buffered;
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* Infer token decimals from payment amount magnitude.
|
|
1054
|
-
* BSC stablecoins use 18 decimals, all others use 6.
|
|
1055
|
-
* A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
|
|
1056
|
-
* If the amount has > 12 digits, it's almost certainly 18 decimals.
|
|
1057
|
-
*/
|
|
1058
|
-
inferDecimals(amount) {
|
|
1059
|
-
return amount.length > 12 ? 18 : 6;
|
|
1060
|
-
}
|
|
1061
|
-
};
|
|
1062
|
-
function createEvmAdapter(config) {
|
|
1063
|
-
return new EvmAdapter(config);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// src/adapters/index.ts
|
|
1067
|
-
function isKnownUSDC(asset) {
|
|
1068
|
-
if (asset === "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") return true;
|
|
1069
|
-
if (asset === "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU") return true;
|
|
1070
|
-
const lc = asset.toLowerCase();
|
|
1071
|
-
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
1072
|
-
if (addr.toLowerCase() === lc) return true;
|
|
1073
|
-
}
|
|
1074
|
-
for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
|
|
1075
|
-
if (addr.toLowerCase() === lc) return true;
|
|
1076
|
-
}
|
|
1077
|
-
return false;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// src/client/x402-client.ts
|
|
1081
|
-
var receiptStore = /* @__PURE__ */ new WeakMap();
|
|
1082
|
-
function getPaymentReceipt(response) {
|
|
1083
|
-
return receiptStore.get(response);
|
|
1084
|
-
}
|
|
1085
|
-
function createX402Client(config) {
|
|
1086
|
-
const {
|
|
1087
|
-
adapters = [createSolanaAdapter({ verbose: config.verbose }), createEvmAdapter({ verbose: config.verbose })],
|
|
1088
|
-
wallets: walletSet,
|
|
1089
|
-
wallet: legacyWallet,
|
|
1090
|
-
preferredNetwork,
|
|
1091
|
-
rpcUrls = {},
|
|
1092
|
-
maxAmountAtomic,
|
|
1093
|
-
fetch: customFetch = globalThis.fetch,
|
|
1094
|
-
verbose = false,
|
|
1095
|
-
accessPass: accessPassConfig,
|
|
1096
|
-
onPaymentRequired,
|
|
1097
|
-
maxRetries = 0,
|
|
1098
|
-
retryDelayMs = 500
|
|
1099
|
-
} = config;
|
|
1100
|
-
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
1101
|
-
};
|
|
1102
|
-
async function fetchWithRetry(input, init) {
|
|
1103
|
-
let lastError;
|
|
1104
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1105
|
-
try {
|
|
1106
|
-
const response = await customFetch(input, init);
|
|
1107
|
-
if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
|
|
1108
|
-
log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
|
|
1109
|
-
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
return response;
|
|
1113
|
-
} catch (err) {
|
|
1114
|
-
lastError = err;
|
|
1115
|
-
if (attempt < maxRetries) {
|
|
1116
|
-
log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
|
|
1117
|
-
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
throw lastError;
|
|
1122
|
-
}
|
|
1123
|
-
const passCache = /* @__PURE__ */ new Map();
|
|
1124
|
-
function getCachedPass(url) {
|
|
1125
|
-
try {
|
|
1126
|
-
const host = new URL(url).host;
|
|
1127
|
-
const cached = passCache.get(host);
|
|
1128
|
-
if (cached && cached.expiresAt > Date.now() / 1e3 + 10) {
|
|
1129
|
-
return cached.jwt;
|
|
1130
|
-
}
|
|
1131
|
-
if (cached) {
|
|
1132
|
-
passCache.delete(host);
|
|
1133
|
-
}
|
|
1134
|
-
} catch {
|
|
1135
|
-
}
|
|
1136
|
-
return null;
|
|
1137
|
-
}
|
|
1138
|
-
function cachePass(url, jwt) {
|
|
1139
|
-
try {
|
|
1140
|
-
const host = new URL(url).host;
|
|
1141
|
-
const parts = jwt.split(".");
|
|
1142
|
-
if (parts.length === 3) {
|
|
1143
|
-
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
1144
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1145
|
-
const maxExp = now + 86400;
|
|
1146
|
-
const expiresAt = Math.min(typeof payload.exp === "number" ? payload.exp : now, maxExp);
|
|
1147
|
-
passCache.set(host, { jwt, expiresAt });
|
|
1148
|
-
log("Access pass cached for", host, "| expires:", new Date(expiresAt * 1e3).toISOString());
|
|
1149
|
-
}
|
|
1150
|
-
} catch {
|
|
1151
|
-
log("Failed to cache access pass");
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
const wallets = walletSet || {};
|
|
1155
|
-
if (legacyWallet && !wallets.solana && isSolanaWallet(legacyWallet)) {
|
|
1156
|
-
wallets.solana = legacyWallet;
|
|
1157
|
-
}
|
|
1158
|
-
if (legacyWallet && !wallets.evm && isEvmWallet(legacyWallet)) {
|
|
1159
|
-
wallets.evm = legacyWallet;
|
|
1160
|
-
}
|
|
1161
|
-
function findPaymentOption(accepts) {
|
|
1162
|
-
const candidates = [];
|
|
1163
|
-
for (const accept of accepts) {
|
|
1164
|
-
const adapter = adapters.find((a) => a.canHandle(accept.network));
|
|
1165
|
-
if (!adapter) continue;
|
|
1166
|
-
let wallet;
|
|
1167
|
-
if (adapter.name === "Solana") {
|
|
1168
|
-
wallet = wallets.solana;
|
|
1169
|
-
} else if (adapter.name === "EVM") {
|
|
1170
|
-
wallet = wallets.evm;
|
|
1171
|
-
}
|
|
1172
|
-
if (wallet && adapter.isConnected(wallet)) {
|
|
1173
|
-
candidates.push({ accept, adapter, wallet });
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
if (candidates.length === 0) {
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
if (preferredNetwork) {
|
|
1180
|
-
const preferred = candidates.find((c) => c.accept.network === preferredNetwork);
|
|
1181
|
-
if (preferred) return preferred;
|
|
1182
|
-
}
|
|
1183
|
-
return candidates[0];
|
|
1184
|
-
}
|
|
1185
|
-
function getChainDisplayName(network, adapterName) {
|
|
1186
|
-
const names = {
|
|
1187
|
-
"eip155:56": "BSC",
|
|
1188
|
-
"eip155:8453": "Base",
|
|
1189
|
-
"eip155:84532": "Base Sepolia",
|
|
1190
|
-
"eip155:42161": "Arbitrum",
|
|
1191
|
-
"eip155:137": "Polygon",
|
|
1192
|
-
"eip155:10": "Optimism",
|
|
1193
|
-
"eip155:43114": "Avalanche",
|
|
1194
|
-
"eip155:1": "Ethereum"
|
|
1195
|
-
};
|
|
1196
|
-
return names[network] || adapterName;
|
|
1197
|
-
}
|
|
1198
|
-
function getRpcUrl(network, adapter) {
|
|
1199
|
-
return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
|
|
1200
|
-
}
|
|
1201
|
-
async function purchaseAccessPass(input, init, originalResponse, passInfo, url) {
|
|
1202
|
-
let tierQuery = "";
|
|
1203
|
-
if (accessPassConfig?.preferTier && passInfo.tiers) {
|
|
1204
|
-
const match2 = passInfo.tiers.find((t) => t.id === accessPassConfig.preferTier);
|
|
1205
|
-
if (match2) {
|
|
1206
|
-
if (accessPassConfig.maxSpend && parseFloat(match2.price) > parseFloat(accessPassConfig.maxSpend)) {
|
|
1207
|
-
throw new X402Error(
|
|
1208
|
-
"access_pass_exceeds_max_spend",
|
|
1209
|
-
`Access pass tier "${match2.id}" costs $${match2.price}, exceeds max spend $${accessPassConfig.maxSpend}`
|
|
1210
|
-
);
|
|
1211
|
-
}
|
|
1212
|
-
tierQuery = `tier=${match2.id}`;
|
|
1213
|
-
}
|
|
1214
|
-
} else if (accessPassConfig?.preferDuration && passInfo.ratePerHour) {
|
|
1215
|
-
tierQuery = `duration=${accessPassConfig.preferDuration}`;
|
|
1216
|
-
} else if (passInfo.tiers && passInfo.tiers.length > 0) {
|
|
1217
|
-
const cheapest = passInfo.tiers[0];
|
|
1218
|
-
if (accessPassConfig?.maxSpend && parseFloat(cheapest.price) > parseFloat(accessPassConfig.maxSpend)) {
|
|
1219
|
-
throw new X402Error(
|
|
1220
|
-
"access_pass_exceeds_max_spend",
|
|
1221
|
-
`Cheapest access pass costs $${cheapest.price}, exceeds max spend $${accessPassConfig?.maxSpend}`
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
tierQuery = `tier=${cheapest.id}`;
|
|
1225
|
-
}
|
|
1226
|
-
const passUrl = tierQuery ? url.includes("?") ? `${url}&${tierQuery}` : `${url}?${tierQuery}` : url;
|
|
1227
|
-
log("Purchasing access pass:", tierQuery || "default tier");
|
|
1228
|
-
const paymentRequiredHeader = originalResponse.headers.get("PAYMENT-REQUIRED");
|
|
1229
|
-
if (!paymentRequiredHeader) return null;
|
|
1230
|
-
let requirements;
|
|
1231
|
-
try {
|
|
1232
|
-
requirements = JSON.parse(atob(paymentRequiredHeader));
|
|
1233
|
-
} catch {
|
|
1234
|
-
return null;
|
|
1235
|
-
}
|
|
1236
|
-
const match = findPaymentOption(requirements.accepts);
|
|
1237
|
-
if (!match) return null;
|
|
1238
|
-
const { accept, adapter, wallet } = match;
|
|
1239
|
-
if (adapter.name === "Solana" && !accept.extra?.feePayer) return null;
|
|
1240
|
-
const decimals = accept.extra?.decimals ?? (isKnownUSDC(accept.asset) ? 6 : void 0);
|
|
1241
|
-
if (typeof decimals !== "number") return null;
|
|
1242
|
-
const paymentAmount = accept.amount ?? accept.maxAmountRequired;
|
|
1243
|
-
if (!paymentAmount) return null;
|
|
1244
|
-
const rpcUrl = getRpcUrl(accept.network, adapter);
|
|
1245
|
-
try {
|
|
1246
|
-
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
1247
|
-
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
1248
|
-
if (balance < requiredAmount) {
|
|
1249
|
-
throw new X402Error(
|
|
1250
|
-
"insufficient_balance",
|
|
1251
|
-
`Insufficient balance for access pass. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
1252
|
-
);
|
|
1253
|
-
}
|
|
1254
|
-
} catch (err) {
|
|
1255
|
-
if (err instanceof X402Error) throw err;
|
|
1256
|
-
}
|
|
1257
|
-
const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
|
|
1258
|
-
let payload;
|
|
1259
|
-
if (adapter.name === "EVM") {
|
|
1260
|
-
payload = JSON.parse(signedTx.serialized);
|
|
1261
|
-
} else {
|
|
1262
|
-
payload = { transaction: signedTx.serialized };
|
|
1263
|
-
}
|
|
1264
|
-
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1265
|
-
let resolvedResource = requirements.resource;
|
|
1266
|
-
if (typeof requirements.resource === "string") {
|
|
1267
|
-
try {
|
|
1268
|
-
const resolved = new URL(requirements.resource, originalUrl);
|
|
1269
|
-
if (["http:", "https:"].includes(resolved.protocol)) {
|
|
1270
|
-
resolvedResource = resolved.toString();
|
|
1271
|
-
}
|
|
1272
|
-
} catch {
|
|
1273
|
-
}
|
|
1274
|
-
} else if (requirements.resource && typeof requirements.resource === "object" && "url" in requirements.resource) {
|
|
1275
|
-
const rObj = requirements.resource;
|
|
1276
|
-
try {
|
|
1277
|
-
const resolved = new URL(rObj.url, originalUrl);
|
|
1278
|
-
if (["http:", "https:"].includes(resolved.protocol)) {
|
|
1279
|
-
resolvedResource = { ...rObj, url: resolved.toString() };
|
|
1280
|
-
}
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
const paymentSignature = {
|
|
1285
|
-
x402Version: accept.x402Version ?? 2,
|
|
1286
|
-
resource: resolvedResource,
|
|
1287
|
-
accepted: accept,
|
|
1288
|
-
payload
|
|
1289
|
-
};
|
|
1290
|
-
if (signedTx.extensions) {
|
|
1291
|
-
paymentSignature.extensions = signedTx.extensions;
|
|
1292
|
-
}
|
|
1293
|
-
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
1294
|
-
const passResponse = await customFetch(passUrl, {
|
|
1295
|
-
...init,
|
|
1296
|
-
method: "POST",
|
|
1297
|
-
headers: {
|
|
1298
|
-
...init?.headers || {},
|
|
1299
|
-
"Content-Type": "application/json",
|
|
1300
|
-
"PAYMENT-SIGNATURE": paymentSignatureHeader
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
if (!passResponse.ok) {
|
|
1304
|
-
log("Pass purchase failed:", passResponse.status);
|
|
1305
|
-
return null;
|
|
1306
|
-
}
|
|
1307
|
-
const accessPassJwt = passResponse.headers.get("ACCESS-PASS");
|
|
1308
|
-
if (accessPassJwt) {
|
|
1309
|
-
cachePass(url, accessPassJwt);
|
|
1310
|
-
log("Access pass purchased and cached");
|
|
1311
|
-
}
|
|
1312
|
-
return passResponse;
|
|
1313
|
-
}
|
|
1314
|
-
async function x402Fetch(input, init) {
|
|
1315
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1316
|
-
log("Making request:", url);
|
|
1317
|
-
if (accessPassConfig) {
|
|
1318
|
-
const cachedJwt = getCachedPass(url);
|
|
1319
|
-
if (cachedJwt) {
|
|
1320
|
-
log("Using cached access pass");
|
|
1321
|
-
const passResponse = await customFetch(input, {
|
|
1322
|
-
...init,
|
|
1323
|
-
headers: {
|
|
1324
|
-
...init?.headers || {},
|
|
1325
|
-
"Authorization": `Bearer ${cachedJwt}`
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
if (passResponse.status !== 401 && passResponse.status !== 402) {
|
|
1329
|
-
return passResponse;
|
|
1330
|
-
}
|
|
1331
|
-
log("Cached pass rejected (status", passResponse.status, "), purchasing new pass");
|
|
1332
|
-
try {
|
|
1333
|
-
passCache.delete(new URL(url).host);
|
|
1334
|
-
} catch {
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
const response = await fetchWithRetry(input, init);
|
|
1339
|
-
if (response.status !== 402) {
|
|
1340
|
-
return response;
|
|
1341
|
-
}
|
|
1342
|
-
log("Received 402 Payment Required");
|
|
1343
|
-
const passTiersHeader = response.headers.get("X-ACCESS-PASS-TIERS");
|
|
1344
|
-
if (accessPassConfig && passTiersHeader) {
|
|
1345
|
-
log("Server offers access passes, purchasing...");
|
|
1346
|
-
try {
|
|
1347
|
-
const passInfo = JSON.parse(atob(passTiersHeader));
|
|
1348
|
-
const passResponse = await purchaseAccessPass(input, init, response, passInfo, url);
|
|
1349
|
-
if (passResponse) return passResponse;
|
|
1350
|
-
} catch (e) {
|
|
1351
|
-
log("Access pass purchase failed, falling back to per-request payment:", e);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
|
|
1355
|
-
if (!paymentRequiredHeader) {
|
|
1356
|
-
throw new X402Error(
|
|
1357
|
-
"missing_payment_required_header",
|
|
1358
|
-
"Server returned 402 but no PAYMENT-REQUIRED header"
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1361
|
-
let requirements;
|
|
1362
|
-
try {
|
|
1363
|
-
const decoded = atob(paymentRequiredHeader);
|
|
1364
|
-
requirements = JSON.parse(decoded);
|
|
1365
|
-
} catch {
|
|
1366
|
-
throw new X402Error(
|
|
1367
|
-
"invalid_payment_required",
|
|
1368
|
-
"Failed to decode PAYMENT-REQUIRED header"
|
|
1369
|
-
);
|
|
1370
|
-
}
|
|
1371
|
-
log("Payment requirements:", requirements);
|
|
1372
|
-
const quoteHash = response.headers.get("X-Quote-Hash");
|
|
1373
|
-
if (quoteHash) {
|
|
1374
|
-
log("Quote hash received:", quoteHash);
|
|
1375
|
-
}
|
|
1376
|
-
const match = findPaymentOption(requirements.accepts);
|
|
1377
|
-
if (!match) {
|
|
1378
|
-
const availableNetworks = requirements.accepts.map((a) => a.network).join(", ");
|
|
1379
|
-
throw new X402Error(
|
|
1380
|
-
"no_matching_payment_option",
|
|
1381
|
-
`No connected wallet for any available network: ${availableNetworks}`
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
const { accept, adapter, wallet } = match;
|
|
1385
|
-
log(`Using ${adapter.name} for ${accept.network}`);
|
|
1386
|
-
if (adapter.name === "Solana" && !accept.extra?.feePayer) {
|
|
1387
|
-
throw new X402Error(
|
|
1388
|
-
"missing_fee_payer",
|
|
1389
|
-
"Solana payment option missing feePayer in extra"
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
const decimals = accept.extra?.decimals ?? (isKnownUSDC(accept.asset) ? 6 : void 0);
|
|
1393
|
-
if (typeof decimals !== "number") {
|
|
1394
|
-
throw new X402Error(
|
|
1395
|
-
"missing_decimals",
|
|
1396
|
-
"Payment option missing decimals - provide in extra or use a known stablecoin"
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
const paymentAmount = accept.amount ?? accept.maxAmountRequired;
|
|
1400
|
-
if (!paymentAmount) {
|
|
1401
|
-
throw new X402Error("missing_amount", "Payment option missing amount");
|
|
1402
|
-
}
|
|
1403
|
-
if (maxAmountAtomic && BigInt(paymentAmount) > BigInt(maxAmountAtomic)) {
|
|
1404
|
-
throw new X402Error(
|
|
1405
|
-
"amount_exceeds_max",
|
|
1406
|
-
`Payment amount ${paymentAmount} exceeds maximum ${maxAmountAtomic}`
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
const rpcUrl = getRpcUrl(accept.network, adapter);
|
|
1410
|
-
log("Checking balance...");
|
|
1411
|
-
try {
|
|
1412
|
-
const balance = await adapter.getBalance(accept, wallet, rpcUrl);
|
|
1413
|
-
const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
|
|
1414
|
-
if (balance < requiredAmount) {
|
|
1415
|
-
const chainName = getChainDisplayName(accept.network, adapter.name);
|
|
1416
|
-
throw new X402Error(
|
|
1417
|
-
"insufficient_balance",
|
|
1418
|
-
`Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
|
|
1419
|
-
);
|
|
1420
|
-
}
|
|
1421
|
-
log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
|
|
1422
|
-
} catch (err) {
|
|
1423
|
-
if (err instanceof X402Error) throw err;
|
|
1424
|
-
log("Balance check failed (RPC error), proceeding with transaction attempt");
|
|
1425
|
-
}
|
|
1426
|
-
if (onPaymentRequired) {
|
|
1427
|
-
const approved = await onPaymentRequired(accept);
|
|
1428
|
-
if (!approved) {
|
|
1429
|
-
throw new X402Error("payment_rejected", "Payment rejected by onPaymentRequired callback");
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
log("Building transaction...");
|
|
1433
|
-
const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
|
|
1434
|
-
log("Transaction signed");
|
|
1435
|
-
let payload;
|
|
1436
|
-
if (adapter.name === "EVM") {
|
|
1437
|
-
payload = JSON.parse(signedTx.serialized);
|
|
1438
|
-
} else {
|
|
1439
|
-
payload = { transaction: signedTx.serialized };
|
|
1440
|
-
}
|
|
1441
|
-
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1442
|
-
let resolvedResource = requirements.resource;
|
|
1443
|
-
if (typeof requirements.resource === "string") {
|
|
1444
|
-
try {
|
|
1445
|
-
const resolvedUrl = new URL(requirements.resource, originalUrl).toString();
|
|
1446
|
-
if (resolvedUrl !== requirements.resource) {
|
|
1447
|
-
log("Resolved relative resource URL:", requirements.resource, "\u2192", resolvedUrl);
|
|
1448
|
-
}
|
|
1449
|
-
resolvedResource = resolvedUrl;
|
|
1450
|
-
} catch {
|
|
1451
|
-
resolvedResource = requirements.resource;
|
|
1452
|
-
}
|
|
1453
|
-
} else if (requirements.resource && typeof requirements.resource === "object" && "url" in requirements.resource) {
|
|
1454
|
-
const resourceObj = requirements.resource;
|
|
1455
|
-
try {
|
|
1456
|
-
const resolvedUrl = new URL(resourceObj.url, originalUrl).toString();
|
|
1457
|
-
if (resolvedUrl !== resourceObj.url) {
|
|
1458
|
-
log("Resolved relative resource URL:", resourceObj.url, "\u2192", resolvedUrl);
|
|
1459
|
-
resolvedResource = { ...resourceObj, url: resolvedUrl };
|
|
1460
|
-
}
|
|
1461
|
-
} catch {
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
const paymentSignature = {
|
|
1465
|
-
x402Version: accept.x402Version ?? 2,
|
|
1466
|
-
// Echo version from 402 response, default to 2
|
|
1467
|
-
resource: resolvedResource,
|
|
1468
|
-
accepted: accept,
|
|
1469
|
-
payload
|
|
1470
|
-
};
|
|
1471
|
-
if (signedTx.extensions) {
|
|
1472
|
-
paymentSignature.extensions = signedTx.extensions;
|
|
1473
|
-
}
|
|
1474
|
-
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
1475
|
-
log("Retrying request with payment...");
|
|
1476
|
-
const retryResponse = await fetchWithRetry(input, {
|
|
1477
|
-
...init,
|
|
1478
|
-
headers: {
|
|
1479
|
-
...init?.headers || {},
|
|
1480
|
-
"PAYMENT-SIGNATURE": paymentSignatureHeader,
|
|
1481
|
-
// Forward quote hash for dynamic pricing validation
|
|
1482
|
-
...quoteHash ? { "X-Quote-Hash": quoteHash } : {}
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1485
|
-
log("Retry response status:", retryResponse.status);
|
|
1486
|
-
if (retryResponse.status === 402) {
|
|
1487
|
-
let reason = "unknown";
|
|
1488
|
-
try {
|
|
1489
|
-
const body = await retryResponse.clone().json();
|
|
1490
|
-
reason = String(body.error || body.message || JSON.stringify(body));
|
|
1491
|
-
log("Rejection reason:", reason);
|
|
1492
|
-
} catch {
|
|
1493
|
-
}
|
|
1494
|
-
throw new X402Error(
|
|
1495
|
-
"payment_rejected",
|
|
1496
|
-
`Payment was rejected by the server: ${reason}`
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
const paymentResponseHeader = retryResponse.headers.get("PAYMENT-RESPONSE");
|
|
1500
|
-
if (paymentResponseHeader) {
|
|
1501
|
-
try {
|
|
1502
|
-
const receipt = JSON.parse(atob(paymentResponseHeader));
|
|
1503
|
-
receiptStore.set(retryResponse, receipt);
|
|
1504
|
-
retryResponse["_x402"] = receipt;
|
|
1505
|
-
if (receipt.extensions) {
|
|
1506
|
-
log("Settlement extensions:", Object.keys(receipt.extensions).join(", "));
|
|
1507
|
-
}
|
|
1508
|
-
} catch {
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
return retryResponse;
|
|
1512
|
-
}
|
|
1513
|
-
return {
|
|
1514
|
-
fetch: x402Fetch
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
// src/client/keypair-wallet.ts
|
|
1519
|
-
var import_web32 = require("@solana/web3.js");
|
|
1520
|
-
var KEYPAIR_SYMBOL = /* @__PURE__ */ Symbol.for("x402:keypair");
|
|
1521
|
-
async function createKeypairWallet(privateKey) {
|
|
1522
|
-
let keypair;
|
|
1523
|
-
if (typeof privateKey === "string") {
|
|
1524
|
-
let bs58Decode;
|
|
1525
|
-
try {
|
|
1526
|
-
const mod = await Promise.resolve().then(() => (init_esm2(), esm_exports));
|
|
1527
|
-
const resolve = mod.decode ?? mod.default?.decode;
|
|
1528
|
-
if (!resolve) throw new Error("decode not found");
|
|
1529
|
-
bs58Decode = resolve;
|
|
1530
|
-
} catch (e) {
|
|
1531
|
-
throw new Error(
|
|
1532
|
-
'The "bs58" package is required for base58 private keys. Install it with: npm install bs58'
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
try {
|
|
1536
|
-
const decoded = bs58Decode(privateKey);
|
|
1537
|
-
keypair = import_web32.Keypair.fromSecretKey(decoded);
|
|
1538
|
-
} catch (e) {
|
|
1539
|
-
try {
|
|
1540
|
-
const parsed = JSON.parse(privateKey);
|
|
1541
|
-
if (Array.isArray(parsed)) {
|
|
1542
|
-
keypair = import_web32.Keypair.fromSecretKey(Uint8Array.from(parsed));
|
|
1543
|
-
} else {
|
|
1544
|
-
throw new Error("Invalid private key format");
|
|
1545
|
-
}
|
|
1546
|
-
} catch {
|
|
1547
|
-
throw new Error(
|
|
1548
|
-
"Invalid private key. Expected base58 string or JSON array of bytes."
|
|
1549
|
-
);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
} else if (Array.isArray(privateKey)) {
|
|
1553
|
-
keypair = import_web32.Keypair.fromSecretKey(Uint8Array.from(privateKey));
|
|
1554
|
-
} else if (privateKey instanceof Uint8Array) {
|
|
1555
|
-
keypair = import_web32.Keypair.fromSecretKey(privateKey);
|
|
1556
|
-
} else {
|
|
1557
|
-
throw new Error(
|
|
1558
|
-
"Invalid private key type. Expected string, number[], or Uint8Array."
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
return {
|
|
1562
|
-
publicKey: {
|
|
1563
|
-
toBase58: () => keypair.publicKey.toBase58()
|
|
1564
|
-
},
|
|
1565
|
-
signTransaction: async (tx) => {
|
|
1566
|
-
if (tx instanceof import_web32.VersionedTransaction) {
|
|
1567
|
-
tx.sign([keypair]);
|
|
1568
|
-
return tx;
|
|
1569
|
-
} else if (tx instanceof import_web32.Transaction) {
|
|
1570
|
-
tx.sign(keypair);
|
|
1571
|
-
return tx;
|
|
1572
|
-
}
|
|
1573
|
-
throw new Error("Unknown transaction type");
|
|
1574
|
-
},
|
|
1575
|
-
[KEYPAIR_SYMBOL]: keypair,
|
|
1576
|
-
keypair
|
|
1577
|
-
// deprecated — kept for backwards compat
|
|
1578
|
-
};
|
|
1579
|
-
}
|
|
1580
|
-
function isKeypairWallet(wallet) {
|
|
1581
|
-
if (!wallet || typeof wallet !== "object") return false;
|
|
1582
|
-
const w = wallet;
|
|
1583
|
-
return "keypair" in w && w.keypair instanceof import_web32.Keypair && "publicKey" in w && "signTransaction" in w;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
// src/client/evm-wallet.ts
|
|
1587
|
-
async function createEvmKeypairWallet(privateKey) {
|
|
1588
|
-
let privateKeyToAccount;
|
|
1589
|
-
try {
|
|
1590
|
-
const viemAccountsPath = "viem/accounts";
|
|
1591
|
-
const viemAccounts = await import(viemAccountsPath);
|
|
1592
|
-
privateKeyToAccount = viemAccounts.privateKeyToAccount;
|
|
1593
|
-
} catch {
|
|
1594
|
-
throw new Error(
|
|
1595
|
-
"EVM wallet support requires viem as a peer dependency. Install with: npm install viem"
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
const normalizedKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
1599
|
-
const account = privateKeyToAccount(normalizedKey);
|
|
1600
|
-
return {
|
|
1601
|
-
address: account.address,
|
|
1602
|
-
signTypedData: (params) => account.signTypedData(params),
|
|
1603
|
-
signTransaction: (params) => account.signTransaction({
|
|
1604
|
-
to: params.to,
|
|
1605
|
-
data: params.data,
|
|
1606
|
-
chainId: params.chainId,
|
|
1607
|
-
gas: params.gas,
|
|
1608
|
-
gasPrice: params.gasPrice,
|
|
1609
|
-
nonce: params.nonce,
|
|
1610
|
-
type: "legacy"
|
|
1611
|
-
})
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
function isEvmKeypairWallet(wallet) {
|
|
1615
|
-
if (!wallet || typeof wallet !== "object") return false;
|
|
1616
|
-
const w = wallet;
|
|
1617
|
-
return "address" in w && typeof w.address === "string" && w.address.startsWith("0x") && "signTypedData" in w && typeof w.signTypedData === "function";
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
// src/client/wrap-fetch.ts
|
|
1621
|
-
function wrapFetch(fetchImpl, options) {
|
|
1622
|
-
const {
|
|
1623
|
-
walletPrivateKey,
|
|
1624
|
-
evmPrivateKey,
|
|
1625
|
-
preferredNetwork,
|
|
1626
|
-
rpcUrls,
|
|
1627
|
-
maxAmountAtomic,
|
|
1628
|
-
verbose,
|
|
1629
|
-
accessPass,
|
|
1630
|
-
onPaymentRequired
|
|
1631
|
-
} = options;
|
|
1632
|
-
if (!walletPrivateKey && !evmPrivateKey) {
|
|
1633
|
-
throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
|
|
1634
|
-
}
|
|
1635
|
-
const wallets = {};
|
|
1636
|
-
const walletInits = [];
|
|
1637
|
-
if (walletPrivateKey) {
|
|
1638
|
-
walletInits.push(
|
|
1639
|
-
createKeypairWallet(walletPrivateKey).then((w) => {
|
|
1640
|
-
wallets.solana = w;
|
|
1641
|
-
}).catch((e) => {
|
|
1642
|
-
console.warn(`[x402] Solana wallet init failed: ${e.message}`);
|
|
1643
|
-
})
|
|
1644
|
-
);
|
|
1645
|
-
}
|
|
1646
|
-
if (evmPrivateKey) {
|
|
1647
|
-
walletInits.push(
|
|
1648
|
-
createEvmKeypairWallet(evmPrivateKey).then((w) => {
|
|
1649
|
-
wallets.evm = w;
|
|
1650
|
-
}).catch((e) => {
|
|
1651
|
-
console.warn(`[x402] EVM wallet init failed: ${e.message}`);
|
|
1652
|
-
})
|
|
1653
|
-
);
|
|
1654
|
-
}
|
|
1655
|
-
const walletsReady = walletInits.length > 0 ? Promise.all(walletInits) : null;
|
|
1656
|
-
const clientConfig = {
|
|
1657
|
-
wallets,
|
|
1658
|
-
preferredNetwork,
|
|
1659
|
-
rpcUrls,
|
|
1660
|
-
maxAmountAtomic,
|
|
1661
|
-
fetch: fetchImpl,
|
|
1662
|
-
verbose,
|
|
1663
|
-
accessPass,
|
|
1664
|
-
onPaymentRequired
|
|
1665
|
-
};
|
|
1666
|
-
const client = createX402Client(clientConfig);
|
|
1667
|
-
const clientFetch = client.fetch.bind(client);
|
|
1668
|
-
if (walletsReady) {
|
|
1669
|
-
return (async (input, init) => {
|
|
1670
|
-
await walletsReady;
|
|
1671
|
-
return clientFetch(input, init);
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
return clientFetch;
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// src/client/discovery.ts
|
|
1678
|
-
var DEFAULT_CAPABILITY_ENDPOINT = "https://x402.dexter.cash/api/x402gle/capability";
|
|
1679
|
-
function formatPriceLabel(priceUsdc) {
|
|
1680
|
-
if (priceUsdc == null) return "price on request";
|
|
1681
|
-
if (priceUsdc === 0) return "free";
|
|
1682
|
-
if (priceUsdc < 0.01) return `$${priceUsdc.toFixed(4)}`;
|
|
1683
|
-
return `$${priceUsdc.toFixed(2)}`;
|
|
1684
|
-
}
|
|
1685
|
-
function mapResult(r) {
|
|
1686
|
-
return {
|
|
1687
|
-
resourceId: r.resourceId,
|
|
1688
|
-
name: r.displayName ?? r.resourceUrl,
|
|
1689
|
-
url: r.resourceUrl,
|
|
1690
|
-
method: r.method || "GET",
|
|
1691
|
-
price: formatPriceLabel(r.pricing.usdc),
|
|
1692
|
-
priceUsdc: r.pricing.usdc,
|
|
1693
|
-
network: r.pricing.network,
|
|
1694
|
-
description: r.description ?? "",
|
|
1695
|
-
category: r.category ?? "uncategorized",
|
|
1696
|
-
qualityScore: r.verification.qualityScore,
|
|
1697
|
-
verified: r.verification.status === "pass",
|
|
1698
|
-
verificationStatus: r.verification.status,
|
|
1699
|
-
totalCalls: r.usage.totalSettlements,
|
|
1700
|
-
totalVolumeUsdc: r.usage.totalVolumeUsdc,
|
|
1701
|
-
iconUrl: r.icon,
|
|
1702
|
-
host: r.host,
|
|
1703
|
-
gamingFlags: r.gaming.flags,
|
|
1704
|
-
gamingSuspicious: r.gaming.suspicious,
|
|
1705
|
-
tier: r.tier,
|
|
1706
|
-
similarity: Math.round(r.similarity * 1e3) / 1e3,
|
|
1707
|
-
why: r.why,
|
|
1708
|
-
score: r.score
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
async function capabilitySearch(options) {
|
|
1712
|
-
if (!options?.query || !options.query.trim()) {
|
|
1713
|
-
throw new Error("capabilitySearch: query is required");
|
|
1714
|
-
}
|
|
1715
|
-
const {
|
|
1716
|
-
query,
|
|
1717
|
-
limit = 20,
|
|
1718
|
-
unverified,
|
|
1719
|
-
testnets,
|
|
1720
|
-
rerank,
|
|
1721
|
-
endpoint = DEFAULT_CAPABILITY_ENDPOINT
|
|
1722
|
-
} = options;
|
|
1723
|
-
const params = new URLSearchParams();
|
|
1724
|
-
params.set("q", query);
|
|
1725
|
-
params.set("limit", String(Math.min(Math.max(limit, 1), 50)));
|
|
1726
|
-
if (unverified) params.set("unverified", "true");
|
|
1727
|
-
if (testnets) params.set("testnets", "true");
|
|
1728
|
-
if (rerank === false) params.set("rerank", "false");
|
|
1729
|
-
const url = `${endpoint}?${params.toString()}`;
|
|
1730
|
-
const response = await fetch(url, {
|
|
1731
|
-
headers: { Accept: "application/json" },
|
|
1732
|
-
signal: AbortSignal.timeout(2e4)
|
|
1733
|
-
});
|
|
1734
|
-
if (!response.ok) {
|
|
1735
|
-
const body = await response.text().catch(() => "");
|
|
1736
|
-
throw new Error(`Capability search failed: ${response.status} ${body.slice(0, 400)}`);
|
|
1737
|
-
}
|
|
1738
|
-
const data = await response.json();
|
|
1739
|
-
if (!data.ok) {
|
|
1740
|
-
throw new Error(
|
|
1741
|
-
`Capability search error${data.stage ? ` at stage ${data.stage}` : ""}: ${data.error ?? "unknown"}`
|
|
1742
|
-
);
|
|
1743
|
-
}
|
|
1744
|
-
return {
|
|
1745
|
-
query: data.query,
|
|
1746
|
-
strongResults: data.strongResults.map(mapResult),
|
|
1747
|
-
relatedResults: data.relatedResults.map(mapResult),
|
|
1748
|
-
strongCount: data.strongCount,
|
|
1749
|
-
relatedCount: data.relatedCount,
|
|
1750
|
-
topSimilarity: data.topSimilarity,
|
|
1751
|
-
noMatchReason: data.noMatchReason,
|
|
1752
|
-
rerank: data.rerank,
|
|
1753
|
-
intent: data.intent,
|
|
1754
|
-
durationMs: data.durationMs
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
// src/client/budget-account.ts
|
|
1759
|
-
function createBudgetAccount(config) {
|
|
1760
|
-
const { budget, allowedDomains, onPaymentRequired: userOnPayment, ...fetchOptions } = config;
|
|
1761
|
-
const totalBudget = parseFloat(budget.total);
|
|
1762
|
-
const perRequestMax = budget.perRequest ? parseFloat(budget.perRequest) : Infinity;
|
|
1763
|
-
const perHourMax = budget.perHour ? parseFloat(budget.perHour) : Infinity;
|
|
1764
|
-
if (isNaN(totalBudget) || totalBudget <= 0) {
|
|
1765
|
-
throw new Error("budget.total must be a positive number");
|
|
1766
|
-
}
|
|
1767
|
-
let ledger = [];
|
|
1768
|
-
let pendingAmount = 0;
|
|
1769
|
-
function getSpent() {
|
|
1770
|
-
return ledger.reduce((sum, r) => sum + r.amount, 0);
|
|
1771
|
-
}
|
|
1772
|
-
function getHourlySpend() {
|
|
1773
|
-
const cutoff = Date.now() - 36e5;
|
|
1774
|
-
return ledger.filter((r) => r.timestamp >= cutoff).reduce((sum, r) => sum + r.amount, 0);
|
|
1775
|
-
}
|
|
1776
|
-
const innerFetch = wrapFetch(fetch, {
|
|
1777
|
-
...fetchOptions,
|
|
1778
|
-
onPaymentRequired: async (accept) => {
|
|
1779
|
-
const decimals = accept.extra?.decimals ?? 6;
|
|
1780
|
-
const amountUsd = Number(accept.amount) / Math.pow(10, decimals);
|
|
1781
|
-
if (amountUsd > perRequestMax) {
|
|
1782
|
-
throw new X402Error(
|
|
1783
|
-
"amount_exceeds_max",
|
|
1784
|
-
`$${amountUsd.toFixed(4)} exceeds per-request limit of $${perRequestMax.toFixed(2)}`
|
|
1785
|
-
);
|
|
1786
|
-
}
|
|
1787
|
-
const spent = getSpent();
|
|
1788
|
-
if (spent + amountUsd > totalBudget) {
|
|
1789
|
-
throw new X402Error(
|
|
1790
|
-
"amount_exceeds_max",
|
|
1791
|
-
`Budget exceeded. Spent $${spent.toFixed(2)} of $${totalBudget.toFixed(2)}, payment: $${amountUsd.toFixed(4)}`
|
|
1792
|
-
);
|
|
1793
|
-
}
|
|
1794
|
-
const hourly = getHourlySpend();
|
|
1795
|
-
if (hourly + amountUsd > perHourMax) {
|
|
1796
|
-
throw new X402Error(
|
|
1797
|
-
"amount_exceeds_max",
|
|
1798
|
-
`Hourly limit ($${perHourMax.toFixed(2)}) exceeded. Spent $${hourly.toFixed(2)} this hour`
|
|
1799
|
-
);
|
|
1800
|
-
}
|
|
1801
|
-
pendingAmount = amountUsd;
|
|
1802
|
-
if (userOnPayment) return userOnPayment(accept);
|
|
1803
|
-
return true;
|
|
1804
|
-
}
|
|
1805
|
-
});
|
|
1806
|
-
const budgetFetch = (async (input, init) => {
|
|
1807
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1808
|
-
let domain = "unknown";
|
|
1809
|
-
try {
|
|
1810
|
-
domain = new URL(url).hostname;
|
|
1811
|
-
} catch {
|
|
1812
|
-
}
|
|
1813
|
-
if (allowedDomains) {
|
|
1814
|
-
if (!allowedDomains.some((d) => domain === d || domain.endsWith(`.${d}`))) {
|
|
1815
|
-
throw new X402Error("payment_rejected", `Domain "${domain}" not in allowed domains`);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
pendingAmount = 0;
|
|
1819
|
-
const response = await innerFetch(input, init);
|
|
1820
|
-
if (pendingAmount > 0) {
|
|
1821
|
-
let network = "unknown";
|
|
1822
|
-
const paymentHeader = response.headers.get("PAYMENT-RESPONSE");
|
|
1823
|
-
if (paymentHeader) {
|
|
1824
|
-
try {
|
|
1825
|
-
const decoded = JSON.parse(atob(paymentHeader));
|
|
1826
|
-
network = decoded.network || network;
|
|
1827
|
-
} catch {
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
ledger.push({ amount: pendingAmount, domain, network, timestamp: Date.now() });
|
|
1831
|
-
pendingAmount = 0;
|
|
1832
|
-
}
|
|
1833
|
-
return response;
|
|
1834
|
-
});
|
|
1835
|
-
return {
|
|
1836
|
-
fetch: budgetFetch,
|
|
1837
|
-
get spent() {
|
|
1838
|
-
return `$${getSpent().toFixed(2)}`;
|
|
1839
|
-
},
|
|
1840
|
-
get remaining() {
|
|
1841
|
-
return `$${(totalBudget - getSpent()).toFixed(2)}`;
|
|
1842
|
-
},
|
|
1843
|
-
get payments() {
|
|
1844
|
-
return ledger.length;
|
|
1845
|
-
},
|
|
1846
|
-
get spentAmount() {
|
|
1847
|
-
return getSpent();
|
|
1848
|
-
},
|
|
1849
|
-
get remainingAmount() {
|
|
1850
|
-
return totalBudget - getSpent();
|
|
1851
|
-
},
|
|
1852
|
-
get ledger() {
|
|
1853
|
-
return ledger;
|
|
1854
|
-
},
|
|
1855
|
-
get hourlySpend() {
|
|
1856
|
-
return getHourlySpend();
|
|
1857
|
-
},
|
|
1858
|
-
reset() {
|
|
1859
|
-
ledger = [];
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
// src/client/sponsored-access.ts
|
|
1865
|
-
function getSponsoredAccessInfo(response) {
|
|
1866
|
-
const receipt = getPaymentReceipt(response);
|
|
1867
|
-
if (!receipt?.extensions?.["sponsored-access"]) return void 0;
|
|
1868
|
-
return receipt.extensions["sponsored-access"];
|
|
1869
|
-
}
|
|
1870
|
-
function getSponsoredRecommendations(response) {
|
|
1871
|
-
const info = getSponsoredAccessInfo(response);
|
|
1872
|
-
if (!info?.recommendations?.length) return void 0;
|
|
1873
|
-
return info.recommendations;
|
|
1874
|
-
}
|
|
1875
|
-
async function fireImpressionBeacon(response) {
|
|
1876
|
-
const info = getSponsoredAccessInfo(response);
|
|
1877
|
-
const beaconUrl = info?.tracking?.impressionBeacon;
|
|
1878
|
-
if (!beaconUrl) return false;
|
|
1879
|
-
try {
|
|
1880
|
-
await fetch(beaconUrl, { method: "GET" });
|
|
1881
|
-
} catch {
|
|
1882
|
-
}
|
|
1883
|
-
return true;
|
|
1884
|
-
}
|
|
1885
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1886
|
-
0 && (module.exports = {
|
|
1887
|
-
BASE_MAINNET,
|
|
1888
|
-
DEXTER_FACILITATOR_URL,
|
|
1889
|
-
KEYPAIR_SYMBOL,
|
|
1890
|
-
SOLANA_MAINNET,
|
|
1891
|
-
USDC_MINT,
|
|
1892
|
-
X402Error,
|
|
1893
|
-
capabilitySearch,
|
|
1894
|
-
createBudgetAccount,
|
|
1895
|
-
createEvmAdapter,
|
|
1896
|
-
createEvmKeypairWallet,
|
|
1897
|
-
createKeypairWallet,
|
|
1898
|
-
createSolanaAdapter,
|
|
1899
|
-
createX402Client,
|
|
1900
|
-
fireImpressionBeacon,
|
|
1901
|
-
getPaymentReceipt,
|
|
1902
|
-
getSponsoredAccessInfo,
|
|
1903
|
-
getSponsoredRecommendations,
|
|
1904
|
-
isEvmKeypairWallet,
|
|
1905
|
-
isKeypairWallet,
|
|
1906
|
-
wrapFetch
|
|
1907
|
-
});
|
|
1
|
+
"use strict";var tt=Object.create;var fe=Object.defineProperty;var nt=Object.getOwnPropertyDescriptor;var rt=Object.getOwnPropertyNames;var st=Object.getPrototypeOf,ot=Object.prototype.hasOwnProperty;var Oe=(n,e)=>()=>(n&&(e=n(n=0)),e);var $e=(n,e)=>{for(var t in e)fe(n,t,{get:e[t],enumerable:!0})},qe=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of rt(e))!ot.call(n,o)&&o!==t&&fe(n,o,{get:()=>e[o],enumerable:!(s=nt(e,o))||s.enumerable});return n};var me=(n,e,t)=>(t=n!=null?tt(st(n)):{},qe(e||!n||!n.__esModule?fe(t,"default",{value:n,enumerable:!0}):t,n)),at=n=>qe(fe({},"__esModule",{value:!0}),n);function dt(n){if(n.length>=255)throw new TypeError("Alphabet too long");let e=new Uint8Array(256);for(let r=0;r<e.length;r++)e[r]=255;for(let r=0;r<n.length;r++){let c=n.charAt(r),w=c.charCodeAt(0);if(e[w]!==255)throw new TypeError(c+" is ambiguous");e[w]=r}let t=n.length,s=n.charAt(0),o=Math.log(t)/Math.log(256),a=Math.log(256)/Math.log(t);function f(r){if(r instanceof Uint8Array||(ArrayBuffer.isView(r)?r=new Uint8Array(r.buffer,r.byteOffset,r.byteLength):Array.isArray(r)&&(r=Uint8Array.from(r))),!(r instanceof Uint8Array))throw new TypeError("Expected Uint8Array");if(r.length===0)return"";let c=0,w=0,A=0,i=r.length;for(;A!==i&&r[A]===0;)A++,c++;let u=(i-A)*a+1>>>0,g=new Uint8Array(u);for(;A!==i;){let S=r[A],E=0;for(let C=u-1;(S!==0||E<w)&&C!==-1;C--,E++)S+=256*g[C]>>>0,g[C]=S%t>>>0,S=S/t>>>0;if(S!==0)throw new Error("Non-zero carry");w=E,A++}let l=u-w;for(;l!==u&&g[l]===0;)l++;let v=s.repeat(c);for(;l<u;++l)v+=n.charAt(g[l]);return v}function p(r){if(typeof r!="string")throw new TypeError("Expected String");if(r.length===0)return new Uint8Array;let c=0,w=0,A=0;for(;r[c]===s;)w++,c++;let i=(r.length-c)*o+1>>>0,u=new Uint8Array(i);for(;c<r.length;){let S=r.charCodeAt(c);if(S>255)return;let E=e[S];if(E===255)return;let C=0;for(let I=i-1;(E!==0||C<A)&&I!==-1;I--,C++)E+=t*u[I]>>>0,u[I]=E%256>>>0,E=E/256>>>0;if(E!==0)throw new Error("Non-zero carry");A=C,c++}let g=i-A;for(;g!==i&&u[g]===0;)g++;let l=new Uint8Array(w+(i-g)),v=w;for(;g!==i;)l[v++]=u[g++];return l}function d(r){let c=p(r);if(c)return c;throw new Error("Non-base"+t+" character")}return{encode:f,decodeUnsafe:p,decode:d}}var Ve,Je=Oe(()=>{"use strict";Ve=dt});var ze={};$e(ze,{default:()=>ut});var lt,ut,He=Oe(()=>{"use strict";Je();lt="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",ut=Ve(lt)});var ft={};$e(ft,{BASE_MAINNET:()=>F,DEXTER_FACILITATOR_URL:()=>Fe,KEYPAIR_SYMBOL:()=>Ne,SOLANA_MAINNET:()=>G,USDC_MINT:()=>We,X402Error:()=>T,capabilitySearch:()=>Me.capabilitySearch,createBudgetAccount:()=>Qe,createEvmAdapter:()=>ie,createEvmKeypairWallet:()=>ve,createKeypairWallet:()=>Te,createSolanaAdapter:()=>re,createX402Client:()=>Pe,fireImpressionBeacon:()=>et,getPaymentReceipt:()=>Re,getSponsoredAccessInfo:()=>_e,getSponsoredRecommendations:()=>Ze,isEvmKeypairWallet:()=>Ge,isKeypairWallet:()=>Ye,wrapFetch:()=>Ce});module.exports=at(ft);var We="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";var Fe="https://x402.dexter.cash",T=class n extends Error{code;details;constructor(e,t,s){super(t),this.name="X402Error",this.code=e,this.details=s,Object.setPrototypeOf(this,n.prototype)}};var D=require("@solana/web3.js"),B=require("@solana/spl-token"),G="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",ge="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",ye="solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z",ne={[G]:"https://api.dexter.cash/api/solana/rpc",[ge]:"https://api.devnet.solana.com",[ye]:"https://api.testnet.solana.com"},it=12e3,ct=1;function Y(n){if(!n||typeof n!="object")return!1;let e=n;return"publicKey"in e&&"signTransaction"in e&&typeof e.signTransaction=="function"}var he=class{name="Solana";networks=[G,ge,ye];config;log;constructor(e={}){this.config=e,this.log=e.verbose?console.log.bind(console,"[x402:solana]"):()=>{}}canHandle(e){return!!(this.networks.includes(e)||e==="solana"||e==="solana-devnet"||e==="solana-testnet"||e.startsWith("solana:"))}getDefaultRpcUrl(e){return this.config.rpcUrls?.[e]?this.config.rpcUrls[e]:ne[e]?ne[e]:e==="solana"?ne[G]:e==="solana-devnet"?ne[ge]:e==="solana-testnet"?ne[ye]:ne[G]}getAddress(e){return Y(e)?e.publicKey?.toBase58()??null:null}isConnected(e){return Y(e)?e.publicKey!==null:!1}async getBalance(e,t,s){if(!Y(t)||!t.publicKey)return 0;let o=s||this.getDefaultRpcUrl(e.network),a=new D.Connection(o,"confirmed"),f=new D.PublicKey(t.publicKey.toBase58()),p=new D.PublicKey(e.asset);try{let r=(await a.getAccountInfo(p,"confirmed"))?.owner.toBase58()===B.TOKEN_2022_PROGRAM_ID.toBase58()?B.TOKEN_2022_PROGRAM_ID:B.TOKEN_PROGRAM_ID,c=await(0,B.getAssociatedTokenAddress)(p,f,!1,r),w=await(0,B.getAccount)(a,c,void 0,r),A=e.extra?.decimals??6;return Number(w.amount)/Math.pow(10,A)}catch(d){if(d&&typeof d=="object"&&"name"in d&&(d.name==="TokenAccountNotFoundError"||d.name==="TokenInvalidAccountOwnerError"))return 0;throw d}}async buildTransaction(e,t,s){if(!Y(t))throw new Error("Invalid Solana wallet");if(!t.publicKey)throw new Error("Wallet not connected");let o=s||this.getDefaultRpcUrl(e.network),a=new D.Connection(o,"confirmed"),f=new D.PublicKey(t.publicKey.toBase58()),{payTo:p,asset:d,extra:r}=e,c=e.amount??e.maxAmountRequired;if(!c)throw new Error("Missing amount in payment requirements");if(!r?.feePayer)throw new Error("Missing feePayer in payment requirements");let w=new D.PublicKey(r.feePayer),A=new D.PublicKey(d),i=new D.PublicKey(p);this.log("Building transaction:",{from:f.toBase58(),to:p,amount:c,asset:d,feePayer:r.feePayer});let u=[];u.push(D.ComputeBudgetProgram.setComputeUnitLimit({units:it})),u.push(D.ComputeBudgetProgram.setComputeUnitPrice({microLamports:ct}));let g=await a.getAccountInfo(A,"confirmed");if(!g)throw new Error(`Token mint ${d} not found`);let l=g.owner.toBase58()===B.TOKEN_2022_PROGRAM_ID.toBase58()?B.TOKEN_2022_PROGRAM_ID:B.TOKEN_PROGRAM_ID,v=await(0,B.getMint)(a,A,void 0,l);typeof r?.decimals=="number"&&v.decimals!==r.decimals&&this.log(`Decimals mismatch: requirements say ${r.decimals}, mint says ${v.decimals}`);let S=await(0,B.getAssociatedTokenAddress)(A,f,!1,l),E=await(0,B.getAssociatedTokenAddress)(A,i,!1,l);if(!await a.getAccountInfo(S,"confirmed"))throw new Error(`No token account found for ${d}. Please ensure you have USDC in your wallet.`);if(!await a.getAccountInfo(E,"confirmed"))throw new Error(`Seller token account not found. The seller (${p}) must have a USDC account.`);let X=BigInt(c);u.push((0,B.createTransferCheckedInstruction)(S,A,E,f,X,v.decimals,[],l));let{blockhash:L}=await a.getLatestBlockhash("confirmed"),y=new D.TransactionMessage({payerKey:w,recentBlockhash:L,instructions:u}).compileToV0Message(),x=new D.VersionedTransaction(y),R=await t.signTransaction(x);return this.log("Transaction signed successfully"),{serialized:Buffer.from(R.serialize()).toString("base64")}}};function re(n){return new he(n)}var de="0x000000000022D473030F116dDEE9F6B43aC78BA3",Ie="0x402085c248EeA27D92E8b30b2C58ed07f9E20001",pt={PermitWitnessTransferFrom:[{name:"permitted",type:"TokenPermissions"},{name:"spender",type:"address"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"},{name:"witness",type:"Witness"}],TokenPermissions:[{name:"token",type:"address"},{name:"amount",type:"uint256"}],Witness:[{name:"to",type:"address"},{name:"validAfter",type:"uint256"}]},Le=BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),F="eip155:8453",le="eip155:84532",se="eip155:42161",Ae="eip155:137",xe="eip155:10",Se="eip155:43114",oe="eip155:56",be="eip155:1187947933",Ee="eip155:324705682",ae="eip155:1",je="0x55d398326f99059fF775485246999027B3197955",ke="0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",Ke={[oe]:56,[F]:8453,[le]:84532,[se]:42161,[Ae]:137,[xe]:10,[Se]:43114,[be]:1187947933,[Ee]:324705682,[ae]:1},Q={[oe]:"https://bsc-dataseed1.binance.org",[F]:"https://api.dexter.cash/api/base/rpc",[le]:"https://sepolia.base.org",[se]:"https://arb1.arbitrum.io/rpc",[Ae]:"https://polygon-rpc.com",[xe]:"https://mainnet.optimism.io",[Se]:"https://api.avax.network/ext/bc/C/rpc",[be]:"https://skale-base.skalenodes.com/v1/base",[Ee]:"https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",[ae]:"https://eth.llamarpc.com"},Ue={[oe]:ke,[F]:"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",[le]:"0x036CbD53842c5426634e7929541eC2318f3dCF7e",[se]:"0xaf88d065e77c8cC2239327C5EDb3A432268e5831",[Ae]:"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",[xe]:"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",[Se]:"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",[be]:"0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",[Ee]:"0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",[ae]:"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},Be={[je]:{symbol:"USDT",decimals:18},[ke]:{symbol:"USDC",decimals:18}};function Z(n){if(!n||typeof n!="object")return!1;let e=n;return"address"in e&&typeof e.address=="string"&&e.address.startsWith("0x")}var we=class{name="EVM";networks=[oe,F,le,ae,se];config;log;constructor(e={}){this.config=e,this.log=e.verbose?console.log.bind(console,"[x402:evm]"):()=>{}}canHandle(e){return!!(this.networks.includes(e)||e==="base"||e==="bsc"||e==="ethereum"||e==="arbitrum"||e.startsWith("eip155:"))}getDefaultRpcUrl(e){return this.config.rpcUrls?.[e]?this.config.rpcUrls[e]:Q[e]?Q[e]:e==="base"?Q[F]:e==="bsc"?Q[oe]:e==="ethereum"?Q[ae]:e==="arbitrum"?Q[se]:Q[F]}getAddress(e){return Z(e)?e.address:null}isConnected(e){return Z(e)?!!e.address:!1}getChainId(e){if(Ke[e])return Ke[e];if(e.startsWith("eip155:")){let t=e.split(":")[1];return parseInt(t,10)}return e==="base"?8453:e==="bsc"?56:e==="ethereum"?1:e==="arbitrum"?42161:8453}async getBalance(e,t,s){if(!Z(t)||!t.address)return 0;let o=s||this.getDefaultRpcUrl(e.network);try{let a=this.encodeBalanceOf(t.address),f=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:e.asset,data:a},"latest"]})});if(!f.ok)throw new Error(`RPC request failed: ${f.status}`);let p=await f.json();if(p.error)throw new Error(`RPC error: ${JSON.stringify(p.error)}`);if(!p.result||p.result==="0x")return 0;let d=BigInt(p.result),r=e.extra?.decimals??6;return Number(d)/Math.pow(10,r)}catch(a){throw a}}encodeBalanceOf(e){let t="0x70a08231",s=e.slice(2).toLowerCase().padStart(64,"0");return t+s}async buildTransaction(e,t,s){if(!Z(t))throw new Error("Invalid EVM wallet");if(!t.address)throw new Error("Wallet not connected");if(e.scheme==="exact-approval")return this.buildApprovalTransaction(e,t,s);if(e.extra?.assetTransferMethod==="permit2")return this.buildPermit2Transaction(e,t,s);let{payTo:o,asset:a,extra:f}=e,p=e.amount??e.maxAmountRequired;if(!p)throw new Error("Missing amount in payment requirements");this.log("Building EVM transaction:",{from:t.address,to:o,amount:p,asset:a,network:e.network});let d=this.getChainId(e.network),r={name:f?.name??"USD Coin",version:f?.version??"2",chainId:BigInt(d),verifyingContract:a},c={TransferWithAuthorization:[{name:"from",type:"address"},{name:"to",type:"address"},{name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},{name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"}]},w=new Uint8Array(32);(globalThis.crypto??(await import("crypto")).webcrypto).getRandomValues(w);let A="0x"+[...w].map(S=>S.toString(16).padStart(2,"0")).join(""),i=Math.floor(Date.now()/1e3),u={from:t.address,to:o,value:p,validAfter:String(i-600),validBefore:String(i+(e.maxTimeoutSeconds||60)),nonce:A},g={from:t.address,to:o,value:BigInt(p),validAfter:BigInt(i-600),validBefore:BigInt(i+(e.maxTimeoutSeconds||60)),nonce:A};if(!t.signTypedData)throw new Error("Wallet does not support signTypedData (EIP-712)");let l=await t.signTypedData({domain:r,types:c,primaryType:"TransferWithAuthorization",message:g});return this.log("EIP-712 signature obtained"),{serialized:JSON.stringify({authorization:u,signature:l}),signature:l}}async buildApprovalTransaction(e,t,s){let{payTo:o,asset:a,extra:f}=e,p=e.amount??e.maxAmountRequired;if(!p)throw new Error("Missing amount in payment requirements");let d=f?.facilitatorContract;if(!d)throw new Error("exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this.");if(!t.signTypedData)throw new Error("Wallet does not support signTypedData (EIP-712)");this.log("Building approval-based transaction:",{from:t.address,to:o,amount:p,asset:a,network:e.network,facilitatorContract:d});let r=s||this.getDefaultRpcUrl(e.network),c=f?.fee??"0",w=BigInt(p)+BigInt(c),A=await this.readAllowance(r,a,t.address,d);if(A<w){if(!t.sendTransaction)throw new Error("BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support.");let x=this.calculateApprovalAmount(p,c,f?.approvalStrategy);this.log(`Approving ${x} for ${d} (current allowance: ${A})`);let R=await t.sendTransaction({to:a,data:this.encodeApprove(d,x),value:0n});this.log(`Approval tx sent: ${R}`),await this.waitForReceipt(r,R),this.log("Approval confirmed")}else this.log("Sufficient allowance, skipping approval");let i=new Uint8Array(16);(globalThis.crypto??(await import("crypto")).webcrypto).getRandomValues(i);let u=[...i].reduce((x,R)=>x*256n+BigInt(R),0n).toString(),g=new Uint8Array(32);(globalThis.crypto??(await import("crypto")).webcrypto).getRandomValues(g);let l="0x"+[...g].map(x=>x.toString(16).padStart(2,"0")).join(""),S=Math.floor(Date.now()/1e3)+(e.maxTimeoutSeconds||300),E=f?.eip712Domain,C=E?{name:E.name,version:E.version,chainId:BigInt(E.chainId),verifyingContract:E.verifyingContract}:{name:"DexterBSCFacilitator",version:"1",chainId:BigInt(this.getChainId(e.network)),verifyingContract:d},I=f?.eip712Types??{Payment:[{name:"from",type:"address"},{name:"to",type:"address"},{name:"token",type:"address"},{name:"amount",type:"uint256"},{name:"fee",type:"uint256"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"},{name:"paymentId",type:"bytes32"}]},X={from:t.address,to:o,token:a,amount:BigInt(p),fee:BigInt(c),nonce:BigInt(u),deadline:BigInt(S),paymentId:l},L=await t.signTypedData({domain:C,types:I,primaryType:"Payment",message:X});this.log("EIP-712 Payment signature obtained");let y={from:t.address,to:o,token:a,amount:p,fee:c,nonce:u,deadline:S,paymentId:l,signature:L};return{serialized:JSON.stringify(y),signature:L}}async buildPermit2Transaction(e,t,s){let{payTo:o,asset:a}=e,f=e.amount??e.maxAmountRequired;if(!f)throw new Error("Missing amount in payment requirements");if(!t.signTypedData)throw new Error("Wallet does not support signTypedData (EIP-712)");this.log("Building Permit2 transaction:",{from:t.address,to:o,amount:f,asset:a,network:e.network});let p=s||this.getDefaultRpcUrl(e.network),d=await this.readAllowance(p,a,t.address,de),r;if(d<BigInt(f)){let C=this.encodeApprove(de,Le);if(t.signTransaction){this.log(`Signing Permit2 approval for relay (current allowance: ${d})`);let I=this.getChainId(e.network),X=await this.readGasPrice(p),L=await this.readNonce(p,t.address),y=await t.signTransaction({to:a,data:C,chainId:I,gas:50000n,gasPrice:X,nonce:L});r={erc20ApprovalGasSponsoring:{info:{from:t.address,asset:a,spender:de,amount:Le.toString(),signedTransaction:y,version:"1"}}},this.log("Permit2 approval signed for facilitator relay")}else if(t.sendTransaction){this.log(`Approving Permit2 directly (current allowance: ${d})`);let I=await t.sendTransaction({to:a,data:C,value:0n});this.log(`Permit2 approval tx sent: ${I}`),await this.waitForReceipt(p,I),this.log("Permit2 approval confirmed")}else throw new Error("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.")}else this.log("Sufficient Permit2 allowance, skipping approval");let c=new Uint8Array(32);(globalThis.crypto??(await import("crypto")).webcrypto).getRandomValues(c);let w=[...c].reduce((C,I)=>C*256n+BigInt(I),0n),A=Math.floor(Date.now()/1e3),i=A-600,u=A+(e.maxTimeoutSeconds||300),g=this.getChainId(e.network),l={name:"Permit2",chainId:BigInt(g),verifyingContract:de},v={permitted:{token:a,amount:BigInt(f)},spender:Ie,nonce:w,deadline:BigInt(u),witness:{to:o,validAfter:BigInt(i)}},S=await t.signTypedData({domain:l,types:pt,primaryType:"PermitWitnessTransferFrom",message:v});this.log("Permit2 PermitWitnessTransferFrom signature obtained");let E={signature:S,permit2Authorization:{from:t.address,permitted:{token:a,amount:f},spender:Ie,nonce:w.toString(),deadline:String(u),witness:{to:o,validAfter:String(i)}}};return{serialized:JSON.stringify(E),signature:S,extensions:r}}async readAllowance(e,t,s,o){let a="0xdd62ed3e",f=s.slice(2).toLowerCase().padStart(64,"0"),p=o.slice(2).toLowerCase().padStart(64,"0"),d=a+f+p;try{let c=await(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:t,data:d},"latest"]})})).json();return c.error||!c.result||c.result==="0x"?0n:BigInt(c.result)}catch{return 0n}}encodeApprove(e,t){let s="0x095ea7b3",o=e.slice(2).toLowerCase().padStart(64,"0"),a=t.toString(16).padStart(64,"0");return s+o+a}async waitForReceipt(e,t,s=3e4){let o=Date.now();for(;Date.now()-o<s;){try{let f=await(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_getTransactionReceipt",params:[t]})})).json();if(f.result){if(f.result.status==="0x0")throw new Error(`Approval transaction reverted: ${t}`);return}}catch(a){if(a instanceof Error&&a.message.includes("reverted"))throw a}await new Promise(a=>setTimeout(a,2e3))}throw new Error(`Approval transaction receipt timeout after ${s}ms: ${t}`)}async readGasPrice(e){try{let s=await(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_gasPrice",params:[]})})).json();return s.result?BigInt(s.result):50000000n}catch{return 50000000n}}async readNonce(e,t){try{let o=await(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_getTransactionCount",params:[t,"latest"]})})).json();return o.result?parseInt(o.result,16):0}catch{return 0}}calculateApprovalAmount(e,t,s){let o=BigInt(e)+BigInt(t);if(!s||s.mode==="exact")return o;let a=BigInt(s.defaultMultiple??10),f=o*a;if(s.maxCapUsd){let p=this.inferDecimals(e),d=BigInt(Math.floor(s.maxCapUsd*Math.pow(10,p)));if(f>d)return d}if(s.exactAboveUsd){let p=this.inferDecimals(e),d=BigInt(Math.floor(s.exactAboveUsd*Math.pow(10,p)));if(BigInt(e)>d)return o}return f}inferDecimals(e){return e.length>12?18:6}};function ie(n){return new we(n)}function De(n){if(n==="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"||n==="4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")return!0;let e=n.toLowerCase();for(let t of Object.values(Ue))if(t.toLowerCase()===e)return!0;for(let t of Object.keys(Be))if(t.toLowerCase()===e)return!0;return!1}var Xe=new WeakMap;function Re(n){return Xe.get(n)}function Pe(n){let{adapters:e=[re({verbose:n.verbose}),ie({verbose:n.verbose})],wallets:t,wallet:s,preferredNetwork:o,rpcUrls:a={},maxAmountAtomic:f,fetch:p=globalThis.fetch,verbose:d=!1,accessPass:r,onPaymentRequired:c,maxRetries:w=0,retryDelayMs:A=500}=n,i=d?console.log.bind(console,"[x402]"):()=>{};async function u(y,x){let R;for(let b=0;b<=w;b++)try{let P=await p(y,x);if(P.status>=502&&P.status<=504&&b<w){i(`Retry ${b+1}/${w}: server returned ${P.status}`),await new Promise(_=>setTimeout(_,A*Math.pow(2,b)));continue}return P}catch(P){R=P,b<w&&(i(`Retry ${b+1}/${w}: ${P instanceof Error?P.message:"network error"}`),await new Promise(_=>setTimeout(_,A*Math.pow(2,b))))}throw R}let g=new Map;function l(y){try{let x=new URL(y).host,R=g.get(x);if(R&&R.expiresAt>Date.now()/1e3+10)return R.jwt;R&&g.delete(x)}catch{}return null}function v(y,x){try{let R=new URL(y).host,b=x.split(".");if(b.length===3){let P=JSON.parse(atob(b[1].replace(/-/g,"+").replace(/_/g,"/"))),_=Math.floor(Date.now()/1e3),k=_+86400,O=Math.min(typeof P.exp=="number"?P.exp:_,k);g.set(R,{jwt:x,expiresAt:O}),i("Access pass cached for",R,"| expires:",new Date(O*1e3).toISOString())}}catch{i("Failed to cache access pass")}}let S=t||{};s&&!S.solana&&Y(s)&&(S.solana=s),s&&!S.evm&&Z(s)&&(S.evm=s);function E(y){let x=[];for(let R of y){let b=e.find(_=>_.canHandle(R.network));if(!b)continue;let P;b.name==="Solana"?P=S.solana:b.name==="EVM"&&(P=S.evm),P&&b.isConnected(P)&&x.push({accept:R,adapter:b,wallet:P})}if(x.length===0)return null;if(o){let R=x.find(b=>b.accept.network===o);if(R)return R}return x[0]}function C(y,x){return{"eip155:56":"BSC","eip155:8453":"Base","eip155:84532":"Base Sepolia","eip155:42161":"Arbitrum","eip155:137":"Polygon","eip155:10":"Optimism","eip155:43114":"Avalanche","eip155:1":"Ethereum"}[y]||x}function I(y,x){return a[y]||x.getDefaultRpcUrl(y)}async function X(y,x,R,b,P){let _="";if(r?.preferTier&&b.tiers){let m=b.tiers.find(W=>W.id===r.preferTier);if(m){if(r.maxSpend&&parseFloat(m.price)>parseFloat(r.maxSpend))throw new T("access_pass_exceeds_max_spend",`Access pass tier "${m.id}" costs $${m.price}, exceeds max spend $${r.maxSpend}`);_=`tier=${m.id}`}}else if(r?.preferDuration&&b.ratePerHour)_=`duration=${r.preferDuration}`;else if(b.tiers&&b.tiers.length>0){let m=b.tiers[0];if(r?.maxSpend&&parseFloat(m.price)>parseFloat(r.maxSpend))throw new T("access_pass_exceeds_max_spend",`Cheapest access pass costs $${m.price}, exceeds max spend $${r?.maxSpend}`);_=`tier=${m.id}`}let k=_?P.includes("?")?`${P}&${_}`:`${P}?${_}`:P;i("Purchasing access pass:",_||"default tier");let O=R.headers.get("PAYMENT-REQUIRED");if(!O)return null;let M;try{M=JSON.parse(atob(O))}catch{return null}let N=E(M.accepts);if(!N)return null;let{accept:U,adapter:K,wallet:ce}=N;if(K.name==="Solana"&&!U.extra?.feePayer)return null;let V=U.extra?.decimals??(De(U.asset)?6:void 0);if(typeof V!="number")return null;let pe=U.amount??U.maxAmountRequired;if(!pe)return null;let J=I(U.network,K);try{let m=await K.getBalance(U,ce,J),W=Number(pe)/Math.pow(10,V);if(m<W)throw new T("insufficient_balance",`Insufficient balance for access pass. Have $${m.toFixed(4)}, need $${W.toFixed(4)}`)}catch(m){if(m instanceof T)throw m}let j=await K.buildTransaction(U,ce,J),ee;K.name==="EVM"?ee=JSON.parse(j.serialized):ee={transaction:j.serialized};let z=typeof y=="string"?y:y instanceof URL?y.href:y.url,te=M.resource;if(typeof M.resource=="string")try{let m=new URL(M.resource,z);["http:","https:"].includes(m.protocol)&&(te=m.toString())}catch{}else if(M.resource&&typeof M.resource=="object"&&"url"in M.resource){let m=M.resource;try{let W=new URL(m.url,z);["http:","https:"].includes(W.protocol)&&(te={...m,url:W.toString()})}catch{}}let ue={x402Version:U.x402Version??2,resource:te,accepted:U,payload:ee};j.extensions&&(ue.extensions=j.extensions);let q=btoa(JSON.stringify(ue)),H=await p(k,{...x,method:"POST",headers:{...x?.headers||{},"Content-Type":"application/json","PAYMENT-SIGNATURE":q}});if(!H.ok)return i("Pass purchase failed:",H.status),null;let h=H.headers.get("ACCESS-PASS");return h&&(v(P,h),i("Access pass purchased and cached")),H}async function L(y,x){let R=typeof y=="string"?y:y instanceof URL?y.href:y.url;if(i("Making request:",R),r){let h=l(R);if(h){i("Using cached access pass");let m=await p(y,{...x,headers:{...x?.headers||{},Authorization:`Bearer ${h}`}});if(m.status!==401&&m.status!==402)return m;i("Cached pass rejected (status",m.status,"), purchasing new pass");try{g.delete(new URL(R).host)}catch{}}}let b=await u(y,x);if(b.status!==402)return b;i("Received 402 Payment Required");let P=b.headers.get("X-ACCESS-PASS-TIERS");if(r&&P){i("Server offers access passes, purchasing...");try{let h=JSON.parse(atob(P)),m=await X(y,x,b,h,R);if(m)return m}catch(h){i("Access pass purchase failed, falling back to per-request payment:",h)}}let _=b.headers.get("PAYMENT-REQUIRED");if(!_)throw new T("missing_payment_required_header","Server returned 402 but no PAYMENT-REQUIRED header");let k;try{let h=atob(_);k=JSON.parse(h)}catch{throw new T("invalid_payment_required","Failed to decode PAYMENT-REQUIRED header")}i("Payment requirements:",k);let O=b.headers.get("X-Quote-Hash");O&&i("Quote hash received:",O);let M=E(k.accepts);if(!M){let h=k.accepts.map(m=>m.network).join(", ");throw new T("no_matching_payment_option",`No connected wallet for any available network: ${h}`)}let{accept:N,adapter:U,wallet:K}=M;if(i(`Using ${U.name} for ${N.network}`),U.name==="Solana"&&!N.extra?.feePayer)throw new T("missing_fee_payer","Solana payment option missing feePayer in extra");let ce=N.extra?.decimals??(De(N.asset)?6:void 0);if(typeof ce!="number")throw new T("missing_decimals","Payment option missing decimals - provide in extra or use a known stablecoin");let V=N.amount??N.maxAmountRequired;if(!V)throw new T("missing_amount","Payment option missing amount");if(f&&BigInt(V)>BigInt(f))throw new T("amount_exceeds_max",`Payment amount ${V} exceeds maximum ${f}`);let pe=I(N.network,U);i("Checking balance...");try{let h=await U.getBalance(N,K,pe),m=Number(V)/Math.pow(10,ce);if(h<m){let W=C(N.network,U.name);throw new T("insufficient_balance",`Insufficient balance on ${W}. Have $${h.toFixed(4)}, need $${m.toFixed(4)}`)}i(`Balance OK: $${h.toFixed(4)} >= $${m.toFixed(4)}`)}catch(h){if(h instanceof T)throw h;i("Balance check failed (RPC error), proceeding with transaction attempt")}if(c&&!await c(N))throw new T("payment_rejected","Payment rejected by onPaymentRequired callback");i("Building transaction...");let J=await U.buildTransaction(N,K,pe);i("Transaction signed");let j;U.name==="EVM"?j=JSON.parse(J.serialized):j={transaction:J.serialized};let ee=typeof y=="string"?y:y instanceof URL?y.href:y.url,z=k.resource;if(typeof k.resource=="string")try{let h=new URL(k.resource,ee).toString();h!==k.resource&&i("Resolved relative resource URL:",k.resource,"\u2192",h),z=h}catch{z=k.resource}else if(k.resource&&typeof k.resource=="object"&&"url"in k.resource){let h=k.resource;try{let m=new URL(h.url,ee).toString();m!==h.url&&(i("Resolved relative resource URL:",h.url,"\u2192",m),z={...h,url:m})}catch{}}let te={x402Version:N.x402Version??2,resource:z,accepted:N,payload:j};J.extensions&&(te.extensions=J.extensions);let ue=btoa(JSON.stringify(te));i("Retrying request with payment...");let q=await u(y,{...x,headers:{...x?.headers||{},"PAYMENT-SIGNATURE":ue,...O?{"X-Quote-Hash":O}:{}}});if(i("Retry response status:",q.status),q.status===402){let h="unknown";try{let m=await q.clone().json();h=String(m.error||m.message||JSON.stringify(m)),i("Rejection reason:",h)}catch{}throw new T("payment_rejected",`Payment was rejected by the server: ${h}`)}let H=q.headers.get("PAYMENT-RESPONSE");if(H)try{let h=JSON.parse(atob(H));Xe.set(q,h),q._x402=h,h.extensions&&i("Settlement extensions:",Object.keys(h.extensions).join(", "))}catch{}return q}return{fetch:L}}var $=require("@solana/web3.js"),Ne=Symbol.for("x402:keypair");async function Te(n){let e;if(typeof n=="string"){let t;try{let s=await Promise.resolve().then(()=>(He(),ze)),o=s.decode??s.default?.decode;if(!o)throw new Error("decode not found");t=o}catch{throw new Error('The "bs58" package is required for base58 private keys. Install it with: npm install bs58')}try{let s=t(n);e=$.Keypair.fromSecretKey(s)}catch{try{let o=JSON.parse(n);if(Array.isArray(o))e=$.Keypair.fromSecretKey(Uint8Array.from(o));else throw new Error("Invalid private key format")}catch{throw new Error("Invalid private key. Expected base58 string or JSON array of bytes.")}}}else if(Array.isArray(n))e=$.Keypair.fromSecretKey(Uint8Array.from(n));else if(n instanceof Uint8Array)e=$.Keypair.fromSecretKey(n);else throw new Error("Invalid private key type. Expected string, number[], or Uint8Array.");return{publicKey:{toBase58:()=>e.publicKey.toBase58()},signTransaction:async t=>{if(t instanceof $.VersionedTransaction)return t.sign([e]),t;if(t instanceof $.Transaction)return t.sign(e),t;throw new Error("Unknown transaction type")},[Ne]:e,keypair:e}}function Ye(n){if(!n||typeof n!="object")return!1;let e=n;return"keypair"in e&&e.keypair instanceof $.Keypair&&"publicKey"in e&&"signTransaction"in e}async function ve(n){let e;try{e=(await import("viem/accounts")).privateKeyToAccount}catch{throw new Error("EVM wallet support requires viem as a peer dependency. Install with: npm install viem")}let t=n.startsWith("0x")?n:`0x${n}`,s=e(t);return{address:s.address,signTypedData:o=>s.signTypedData(o),signTransaction:o=>s.signTransaction({to:o.to,data:o.data,chainId:o.chainId,gas:o.gas,gasPrice:o.gasPrice,nonce:o.nonce,type:"legacy"})}}function Ge(n){if(!n||typeof n!="object")return!1;let e=n;return"address"in e&&typeof e.address=="string"&&e.address.startsWith("0x")&&"signTypedData"in e&&typeof e.signTypedData=="function"}function Ce(n,e){let{walletPrivateKey:t,evmPrivateKey:s,preferredNetwork:o,rpcUrls:a,maxAmountAtomic:f,verbose:p,accessPass:d,onPaymentRequired:r}=e;if(!t&&!s)throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");let c={},w=[];t&&w.push(Te(t).then(l=>{c.solana=l}).catch(l=>{console.warn(`[x402] Solana wallet init failed: ${l.message}`)})),s&&w.push(ve(s).then(l=>{c.evm=l}).catch(l=>{console.warn(`[x402] EVM wallet init failed: ${l.message}`)}));let A=w.length>0?Promise.all(w):null,u=Pe({wallets:c,preferredNetwork:o,rpcUrls:a,maxAmountAtomic:f,fetch:n,verbose:p,accessPass:d,onPaymentRequired:r}),g=u.fetch.bind(u);return A?(async(l,v)=>(await A,g(l,v))):g}var Me=require("@dexterai/x402-core");function Qe(n){let{budget:e,allowedDomains:t,onPaymentRequired:s,...o}=n,a=parseFloat(e.total),f=e.perRequest?parseFloat(e.perRequest):1/0,p=e.perHour?parseFloat(e.perHour):1/0;if(isNaN(a)||a<=0)throw new Error("budget.total must be a positive number");let d=[],r=0;function c(){return d.reduce((u,g)=>u+g.amount,0)}function w(){let u=Date.now()-36e5;return d.filter(g=>g.timestamp>=u).reduce((g,l)=>g+l.amount,0)}let A=Ce(fetch,{...o,onPaymentRequired:async u=>{let g=u.extra?.decimals??6,l=Number(u.amount)/Math.pow(10,g);if(l>f)throw new T("amount_exceeds_max",`$${l.toFixed(4)} exceeds per-request limit of $${f.toFixed(2)}`);let v=c();if(v+l>a)throw new T("amount_exceeds_max",`Budget exceeded. Spent $${v.toFixed(2)} of $${a.toFixed(2)}, payment: $${l.toFixed(4)}`);let S=w();if(S+l>p)throw new T("amount_exceeds_max",`Hourly limit ($${p.toFixed(2)}) exceeded. Spent $${S.toFixed(2)} this hour`);return r=l,s?s(u):!0}});return{fetch:(async(u,g)=>{let l=typeof u=="string"?u:u instanceof URL?u.href:u.url,v="unknown";try{v=new URL(l).hostname}catch{}if(t&&!t.some(E=>v===E||v.endsWith(`.${E}`)))throw new T("payment_rejected",`Domain "${v}" not in allowed domains`);r=0;let S=await A(u,g);if(r>0){let E="unknown",C=S.headers.get("PAYMENT-RESPONSE");if(C)try{E=JSON.parse(atob(C)).network||E}catch{}d.push({amount:r,domain:v,network:E,timestamp:Date.now()}),r=0}return S}),get spent(){return`$${c().toFixed(2)}`},get remaining(){return`$${(a-c()).toFixed(2)}`},get payments(){return d.length},get spentAmount(){return c()},get remainingAmount(){return a-c()},get ledger(){return d},get hourlySpend(){return w()},reset(){d=[]}}}function _e(n){let e=Re(n);if(e?.extensions?.["sponsored-access"])return e.extensions["sponsored-access"]}function Ze(n){let e=_e(n);if(e?.recommendations?.length)return e.recommendations}async function et(n){let t=_e(n)?.tracking?.impressionBeacon;if(!t)return!1;try{await fetch(t,{method:"GET"})}catch{}return!0}0&&(module.exports={BASE_MAINNET,DEXTER_FACILITATOR_URL,KEYPAIR_SYMBOL,SOLANA_MAINNET,USDC_MINT,X402Error,capabilitySearch,createBudgetAccount,createEvmAdapter,createEvmKeypairWallet,createKeypairWallet,createSolanaAdapter,createX402Client,fireImpressionBeacon,getPaymentReceipt,getSponsoredAccessInfo,getSponsoredRecommendations,isEvmKeypairWallet,isKeypairWallet,wrapFetch});
|