@bankofai/x402-cli 1.0.1-beta.0 → 1.0.1-beta.2

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/cli.js CHANGED
@@ -1202,6 +1202,11 @@ async function pay(url, options) {
1202
1202
  body: ["GET", "HEAD"].includes(method.toUpperCase()) ? undefined : opt(options, "body"),
1203
1203
  }, timeoutMs(options), `fetch ${url}`);
1204
1204
  if (probe.status !== 402) {
1205
+ const body = await responsePayload(probe);
1206
+ if (!probe.ok) {
1207
+ const retryAfter = probe.headers.get("retry-after");
1208
+ throw new Error(`HTTP ${probe.status} from ${url}${retryAfter ? ` (retry after ${retryAfter}s)` : ""}: ${typeof body === "string" ? body.slice(0, 500) : JSON.stringify(body).slice(0, 500)}`);
1209
+ }
1205
1210
  emit({
1206
1211
  command: "client",
1207
1212
  mode: outputMode(options),
@@ -1209,7 +1214,7 @@ async function pay(url, options) {
1209
1214
  url,
1210
1215
  status: probe.status,
1211
1216
  message: "Not a payment-required endpoint",
1212
- response: await responsePayload(probe),
1217
+ response: body,
1213
1218
  },
1214
1219
  });
1215
1220
  return;
@@ -1260,7 +1265,8 @@ async function pay(url, options) {
1260
1265
  ...(paymentResponse ? { paymentResponse: decodeResponse(paymentResponse) } : {}),
1261
1266
  };
1262
1267
  if (!paid.ok) {
1263
- throw new Error(`HTTP ${paid.status} from ${url}: ${typeof body === "string" ? body.slice(0, 500) : JSON.stringify(body).slice(0, 500)}`);
1268
+ const retryAfter = paid.headers.get("retry-after");
1269
+ throw new Error(`HTTP ${paid.status} from ${url}${retryAfter ? ` (retry after ${retryAfter}s)` : ""}: ${typeof body === "string" ? body.slice(0, 500) : JSON.stringify(body).slice(0, 500)}`);
1264
1270
  }
1265
1271
  emit({
1266
1272
  command: "client",
@@ -119,15 +119,31 @@ export function loadProvider(file) {
119
119
  };
120
120
  }
121
121
  function normalizePaymentProtocol(config, file) {
122
- const raw = String(config.operator.protocol || config.operator.scheme || "exact").toLowerCase();
123
- const normalized = raw.replace(/[-:\s]/g, "_");
124
- if (!["exact", "exact_permit", "permit2", "exact_permit2"].includes(normalized)) {
125
- throw new Error(`${file}: unsupported x402 protocol ${raw}; use exact + permit2`);
122
+ const rawSchemes = config.operator.schemes?.length
123
+ ? config.operator.schemes
124
+ : [config.operator.protocol || config.operator.scheme || "exact"];
125
+ const schemes = [...new Set(rawSchemes.map(value => {
126
+ const raw = String(value).toLowerCase();
127
+ const normalized = raw.replace(/[-:\s]/g, "_");
128
+ if (!["exact", "exact_gasfree", "exact_permit", "permit2", "exact_permit2"].includes(normalized)) {
129
+ throw new Error(`${file}: unsupported x402 protocol ${raw}; use exact or exact_gasfree`);
130
+ }
131
+ return normalized === "exact_gasfree" ? "exact_gasfree" : "exact";
132
+ }))];
133
+ if (schemes.includes("exact_gasfree") && !config.operator.network.startsWith("tron:")) {
134
+ throw new Error(`${file}: exact_gasfree is supported only on TRON networks`);
135
+ }
136
+ config.operator.schemes = schemes;
137
+ config.operator.scheme = schemes[0];
138
+ config.operator.protocol = schemes[0];
139
+ if (schemes.includes("exact")) {
140
+ config.operator.asset_transfer_method = "permit2";
141
+ config.operator.assetTransferMethod = "permit2";
142
+ }
143
+ else {
144
+ delete config.operator.asset_transfer_method;
145
+ delete config.operator.assetTransferMethod;
126
146
  }
127
- config.operator.scheme = "exact";
128
- config.operator.protocol = "exact";
129
- config.operator.asset_transfer_method = "permit2";
130
- config.operator.assetTransferMethod = "permit2";
131
147
  }
132
148
  export function loadProviders(providerPath) {
133
149
  const stat = fs.statSync(providerPath);
@@ -178,20 +194,23 @@ export function paymentRequirements(provider, price) {
178
194
  const network = normalizeNetwork(provider.operator.network);
179
195
  const symbols = provider.operator.currencies?.usd ?? ["USDT"];
180
196
  const payTo = provider.recipients?.[provider.operator.recipient]?.account ?? provider.operator.recipient;
181
- return symbols.map(symbol => {
197
+ const schemes = provider.operator.schemes?.length
198
+ ? provider.operator.schemes.map(scheme => scheme === "exact_gasfree" ? "exact_gasfree" : "exact")
199
+ : [provider.operator.scheme === "exact_gasfree" ? "exact_gasfree" : "exact"];
200
+ return schemes.flatMap(scheme => symbols.map(symbol => {
182
201
  const token = getToken(network, symbol);
183
202
  const transferMethod = provider.operator.assetTransferMethod || provider.operator.asset_transfer_method || token.assetTransferMethod;
184
203
  const amount = toSmallestUnit(price, token.decimals);
185
204
  if (amount === "0")
186
205
  throw new Error(`positive price produced zero amount for ${symbol} on ${network}`);
187
206
  return {
188
- scheme: "exact",
207
+ scheme,
189
208
  network,
190
209
  amount,
191
210
  asset: token.address,
192
211
  payTo,
193
212
  maxTimeoutSeconds: provider.operator.valid_for_seconds ?? 300,
194
- extra: transferMethod === "permit2" ? { assetTransferMethod: "permit2" } : {},
213
+ extra: scheme === "exact" && transferMethod === "permit2" ? { assetTransferMethod: "permit2" } : {},
195
214
  };
196
- });
215
+ }));
197
216
  }
@@ -5,10 +5,12 @@ import { decodeSignature, encodeRequired, encodeResponse, headers, matchRequirem
5
5
  class HttpError extends Error {
6
6
  status;
7
7
  publicMessage;
8
- constructor(status, publicMessage, message = publicMessage) {
8
+ responseHeaders;
9
+ constructor(status, publicMessage, message = publicMessage, responseHeaders = {}) {
9
10
  super(message);
10
11
  this.status = status;
11
12
  this.publicMessage = publicMessage;
13
+ this.responseHeaders = responseHeaders;
12
14
  }
13
15
  }
14
16
  class RequestTooLargeError extends HttpError {
@@ -52,7 +54,6 @@ const metrics = {
52
54
  paidRequests: 0,
53
55
  verifyFailures: 0,
54
56
  settleFailures: 0,
55
- feeQuoteFailures: 0,
56
57
  upstreamFailures: 0,
57
58
  };
58
59
  async function readBody(request) {
@@ -89,7 +90,7 @@ async function fetchWithTimeout(url, init, timeoutMs, label = "upstream") {
89
90
  async function facilitatorPost(entry, path, body) {
90
91
  const headers = { "content-type": "application/json" };
91
92
  if (entry.facilitatorApiKey) {
92
- headers.authorization = `Bearer ${entry.facilitatorApiKey}`;
93
+ headers["x-api-key"] = entry.facilitatorApiKey;
93
94
  }
94
95
  const response = await fetchWithTimeout(new URL(path, entry.facilitatorUrl), { method: "POST", headers, body: JSON.stringify(body) }, FACILITATOR_TIMEOUT_MS, "facilitator");
95
96
  const text = await response.text();
@@ -98,31 +99,43 @@ async function facilitatorPost(entry, path, body) {
98
99
  data = text ? JSON.parse(text) : {};
99
100
  }
100
101
  catch {
102
+ logFacilitatorFailure(entry, path, response, body, { code: "invalid_json" });
101
103
  throw new HttpError(502, "facilitator returned invalid response");
102
104
  }
103
- if (!response.ok)
104
- throw new HttpError(502, "facilitator request failed", `facilitator ${path} failed: ${response.status} ${text}`);
105
+ if (!response.ok) {
106
+ logFacilitatorFailure(entry, path, response, body, data);
107
+ if (response.status === 429) {
108
+ const retryAfter = response.headers.get("retry-after");
109
+ throw new HttpError(429, "facilitator rate limited", `facilitator ${path} rate limited`, retryAfter ? { "retry-after": retryAfter } : {});
110
+ }
111
+ throw new HttpError(502, "facilitator request failed", `facilitator ${path} failed: ${response.status}`);
112
+ }
105
113
  return data;
106
114
  }
107
- async function attachFeeQuotes(entry, requirements, context) {
108
- try {
109
- const quotes = await facilitatorPost(entry, "/fee_quote", {
110
- paymentRequirements: requirements,
111
- context,
112
- });
113
- const list = Array.isArray(quotes) ? quotes : quotes.quotes ?? quotes.fees ?? [];
114
- return requirements.map(requirement => {
115
- const quote = list.find((item) => item.scheme === requirement.scheme &&
116
- item.network === requirement.network &&
117
- String(item.asset).toLowerCase() === requirement.asset.toLowerCase());
118
- return quote?.fee ? { ...requirement, extra: { ...requirement.extra, fee: quote.fee } } : requirement;
119
- });
120
- }
121
- catch (error) {
122
- metrics.feeQuoteFailures += 1;
123
- console.warn("fee quote failed", error);
124
- return requirements;
125
- }
115
+ function resourceUrl(url) {
116
+ const path = `${url.pathname}${url.search}`;
117
+ const publicBaseUrl = process.env.X402_GATEWAY_PUBLIC_BASE_URL?.trim();
118
+ if (!publicBaseUrl)
119
+ return path;
120
+ return new URL(path, `${publicBaseUrl.replace(/\/+$/, "")}/`).toString();
121
+ }
122
+ function logFacilitatorFailure(entry, path, response, body, data) {
123
+ const requirement = body?.paymentRequirements;
124
+ const nestedError = data?.error && typeof data.error === "object" ? data.error : undefined;
125
+ const message = nestedError?.message ?? data?.message ?? data?.detail ??
126
+ (typeof data?.error === "string" ? data.error : undefined);
127
+ console.error(JSON.stringify({
128
+ event: "facilitator_request_failed",
129
+ provider: entry.config.name,
130
+ endpoint: path,
131
+ status: response.status,
132
+ scheme: requirement?.scheme,
133
+ network: requirement?.network,
134
+ errorCode: nestedError?.code ?? data?.code,
135
+ errorMessage: typeof message === "string" ? message.slice(0, 200) : undefined,
136
+ retryAfter: response.headers.get("retry-after") ?? undefined,
137
+ cfRay: response.headers.get("cf-ray") ?? undefined,
138
+ }));
126
139
  }
127
140
  function isVerifySuccess(verify) {
128
141
  return verify?.valid === true || verify?.isValid === true;
@@ -265,7 +278,6 @@ export function createGatewayServer(providers) {
265
278
  `x402_gateway_paid_requests_total ${metrics.paidRequests}`,
266
279
  `x402_gateway_verify_failures_total ${metrics.verifyFailures}`,
267
280
  `x402_gateway_settle_failures_total ${metrics.settleFailures}`,
268
- `x402_gateway_fee_quote_failures_total ${metrics.feeQuoteFailures}`,
269
281
  `x402_gateway_upstream_failures_total ${metrics.upstreamFailures}`,
270
282
  "",
271
283
  ].join("\n"));
@@ -295,11 +307,11 @@ export function createGatewayServer(providers) {
295
307
  }
296
308
  const paymentHeader = request.headers[headers.signature.toLowerCase()];
297
309
  if (!paymentHeader || Array.isArray(paymentHeader)) {
298
- const accepts = await attachFeeQuotes(entry, requirements);
310
+ const accepts = requirements;
299
311
  const challenge = {
300
312
  x402Version: 2,
301
313
  error: "Payment required",
302
- resource: { url: url.pathname },
314
+ resource: { url: resourceUrl(url) },
303
315
  accepts,
304
316
  };
305
317
  json(response, 402, challenge, { [headers.required]: encodeRequired(challenge) });
@@ -348,7 +360,7 @@ export function createGatewayServer(providers) {
348
360
  }
349
361
  catch (error) {
350
362
  if (error instanceof HttpError) {
351
- json(response, error.status, { error: error.publicMessage });
363
+ json(response, error.status, { error: error.publicMessage }, error.responseHeaders);
352
364
  return;
353
365
  }
354
366
  console.error(error);
@@ -1,9 +1,9 @@
1
1
  export const TOKENS = {
2
- "tron:mainnet": {
2
+ "tron:0x2b6653dc": {
3
3
  USDT: { address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
4
4
  USDD: { address: "TXDk8mbtRbXeYuMNS83CfKPaYYT8XWv9Hz", decimals: 18, name: "Decentralized USD", symbol: "USDD", assetTransferMethod: "permit2" },
5
5
  },
6
- "tron:nile": {
6
+ "tron:0xcd8690dc": {
7
7
  USDT: { address: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", decimals: 6, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
8
8
  USDD: { address: "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK", decimals: 18, name: "Decentralized USD", symbol: "USDD", assetTransferMethod: "permit2" },
9
9
  },
@@ -17,9 +17,15 @@ export const TOKENS = {
17
17
  };
18
18
  export function normalizeNetwork(network) {
19
19
  return ({
20
- "tron-mainnet": "tron:mainnet",
21
- "tron-shasta": "tron:shasta",
22
- "tron-nile": "tron:nile",
20
+ "tron-mainnet": "tron:0x2b6653dc",
21
+ "tron:mainnet": "tron:0x2b6653dc",
22
+ "mainnet": "tron:0x2b6653dc",
23
+ "tron-shasta": "tron:0x94a9059e",
24
+ "tron:shasta": "tron:0x94a9059e",
25
+ "shasta": "tron:0x94a9059e",
26
+ "tron-nile": "tron:0xcd8690dc",
27
+ "tron:nile": "tron:0xcd8690dc",
28
+ "nile": "tron:0xcd8690dc",
23
29
  "bsc-mainnet": "eip155:56",
24
30
  "bsc-testnet": "eip155:97",
25
31
  }[network] ?? network);
package/dist/tokens.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const TOKENS = {
2
- "tron:mainnet": {
2
+ "tron:0x2b6653dc": {
3
3
  USDT: {
4
4
  address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
5
5
  decimals: 6,
@@ -17,7 +17,7 @@ export const TOKENS = {
17
17
  assetTransferMethod: "permit2",
18
18
  },
19
19
  },
20
- "tron:nile": {
20
+ "tron:0xcd8690dc": {
21
21
  USDT: {
22
22
  address: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf",
23
23
  decimals: 6,
@@ -35,7 +35,7 @@ export const TOKENS = {
35
35
  assetTransferMethod: "permit2",
36
36
  },
37
37
  },
38
- "tron:shasta": {
38
+ "tron:0x94a9059e": {
39
39
  USDT: {
40
40
  address: "TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs",
41
41
  decimals: 6,
@@ -75,9 +75,15 @@ export const TOKENS = {
75
75
  };
76
76
  export function normalizeNetwork(network) {
77
77
  return ({
78
- "tron-mainnet": "tron:mainnet",
79
- "tron-shasta": "tron:shasta",
80
- "tron-nile": "tron:nile",
78
+ "tron-mainnet": "tron:0x2b6653dc",
79
+ "tron:mainnet": "tron:0x2b6653dc",
80
+ "mainnet": "tron:0x2b6653dc",
81
+ "tron-shasta": "tron:0x94a9059e",
82
+ "tron:shasta": "tron:0x94a9059e",
83
+ "shasta": "tron:0x94a9059e",
84
+ "tron-nile": "tron:0xcd8690dc",
85
+ "tron:nile": "tron:0xcd8690dc",
86
+ "nile": "tron:0xcd8690dc",
81
87
  "bsc-mainnet": "eip155:56",
82
88
  "bsc-testnet": "eip155:97",
83
89
  }[network] ?? network);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-cli",
3
- "version": "1.0.1-beta.0",
3
+ "version": "1.0.1-beta.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -20,10 +20,10 @@
20
20
  "node": ">=20"
21
21
  },
22
22
  "dependencies": {
23
- "@bankofai/x402-core": "1.0.1-beta.2",
24
- "@bankofai/x402-evm": "1.0.1-beta.2",
25
- "@bankofai/x402-gateway": "^1.0.0",
26
- "@bankofai/x402-tron": "1.0.1-beta.2",
23
+ "@bankofai/x402-core": "1.0.1-beta.4",
24
+ "@bankofai/x402-evm": "1.0.1-beta.4",
25
+ "@bankofai/x402-gateway": "1.0.1-beta.2",
26
+ "@bankofai/x402-tron": "1.0.1-beta.4",
27
27
  "tronweb": "6.4.0",
28
28
  "viem": "^2.55.0",
29
29
  "yaml": "^2.8.2"