@bankofai/x402-gateway 1.0.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/README.md CHANGED
@@ -3,12 +3,13 @@
3
3
  TypeScript reverse proxy for paid HTTP APIs. This version uses the npm
4
4
  TypeScript x402 SDK packages only:
5
5
 
6
- - `@bankofai/x402-core@1.0.0`
7
- - `@bankofai/x402-evm@1.0.0`
8
- - `@bankofai/x402-tron@1.0.0`
6
+ - `@bankofai/x402-core@1.0.1-beta.2`
7
+ - `@bankofai/x402-evm@1.0.1-beta.2`
8
+ - `@bankofai/x402-tron@1.0.1-beta.2`
9
9
 
10
- Payment requirements are emitted as `scheme=exact`; supported stablecoins add
11
- `extra.assetTransferMethod=permit2`.
10
+ Payment requirements support `scheme=exact` and TRON `scheme=exact_gasfree`.
11
+ Exact requirements add `extra.assetTransferMethod=permit2`; GasFree requirements
12
+ receive fee terms from the configured facilitator.
12
13
 
13
14
  ## Install
14
15
 
@@ -148,10 +149,11 @@ endpoints:
148
149
  - price_usd: 0.002
149
150
  ```
150
151
 
151
- `@bankofai/x402-*` 1.0.0 uses `scheme: exact` with
152
- `extra.assetTransferMethod: permit2` in the payment requirement. Older provider
153
- configs that say `exact_permit` are normalized at load time, but new provider
154
- configs should use `protocol: exact` and `asset_transfer_method: permit2`.
152
+ `@bankofai/x402-*` 1.0.1-beta.2 uses `scheme: exact` with
153
+ `extra.assetTransferMethod: permit2`, or TRON `scheme: exact_gasfree`. Older
154
+ provider configs that say `exact_permit` are normalized to `exact`. For GasFree,
155
+ set both `scheme` and `protocol` to `exact_gasfree`; the facilitator must support
156
+ GasFree for the selected TRON network and token.
155
157
 
156
158
  Network aliases accepted:
157
159
 
package/dist/config.js CHANGED
@@ -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
  }
package/dist/server.js CHANGED
@@ -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);
package/dist/tokens.js CHANGED
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-gateway",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-beta.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -22,9 +22,9 @@
22
22
  "node": ">=20"
23
23
  },
24
24
  "dependencies": {
25
- "@bankofai/x402-core": "1.0.0",
26
- "@bankofai/x402-evm": "1.0.0",
27
- "@bankofai/x402-tron": "1.0.0",
25
+ "@bankofai/x402-core": "1.0.1-beta.4",
26
+ "@bankofai/x402-evm": "1.0.1-beta.4",
27
+ "@bankofai/x402-tron": "1.0.1-beta.4",
28
28
  "yaml": "^2.8.2"
29
29
  },
30
30
  "devDependencies": {