@bankofai/x402-gateway 1.0.0-beta.2 → 1.0.1-beta.1

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
 
@@ -187,7 +189,7 @@ docker run --rm -p 4020:8080 \
187
189
  -v "$PWD/providers:/app/providers:ro" \
188
190
  -e X402_GATEWAY_ADMIN_TOKEN=<admin-token> \
189
191
  -e X402_FACILITATOR_API_KEY=<facilitator-api-key> \
190
- bankofai/x402-gateway:<tag>
192
+ bankofai/x402-gateway:v20260709182145
191
193
  ```
192
194
 
193
195
  The Docker command binds `0.0.0.0:8080` explicitly; local CLI runs default to
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
@@ -52,7 +52,6 @@ const metrics = {
52
52
  paidRequests: 0,
53
53
  verifyFailures: 0,
54
54
  settleFailures: 0,
55
- feeQuoteFailures: 0,
56
55
  upstreamFailures: 0,
57
56
  };
58
57
  async function readBody(request) {
@@ -89,7 +88,7 @@ async function fetchWithTimeout(url, init, timeoutMs, label = "upstream") {
89
88
  async function facilitatorPost(entry, path, body) {
90
89
  const headers = { "content-type": "application/json" };
91
90
  if (entry.facilitatorApiKey) {
92
- headers.authorization = `Bearer ${entry.facilitatorApiKey}`;
91
+ headers["x-api-key"] = entry.facilitatorApiKey;
93
92
  }
94
93
  const response = await fetchWithTimeout(new URL(path, entry.facilitatorUrl), { method: "POST", headers, body: JSON.stringify(body) }, FACILITATOR_TIMEOUT_MS, "facilitator");
95
94
  const text = await response.text();
@@ -98,31 +97,32 @@ async function facilitatorPost(entry, path, body) {
98
97
  data = text ? JSON.parse(text) : {};
99
98
  }
100
99
  catch {
100
+ logFacilitatorFailure(entry, path, response, body, { code: "invalid_json" });
101
101
  throw new HttpError(502, "facilitator returned invalid response");
102
102
  }
103
- if (!response.ok)
104
- throw new HttpError(502, "facilitator request failed", `facilitator ${path} failed: ${response.status} ${text}`);
103
+ if (!response.ok) {
104
+ logFacilitatorFailure(entry, path, response, body, data);
105
+ throw new HttpError(502, "facilitator request failed", `facilitator ${path} failed: ${response.status}`);
106
+ }
105
107
  return data;
106
108
  }
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
- }
109
+ function logFacilitatorFailure(entry, path, response, body, data) {
110
+ const requirement = body?.paymentRequirements;
111
+ const nestedError = data?.error && typeof data.error === "object" ? data.error : undefined;
112
+ const message = nestedError?.message ?? data?.message ?? data?.detail ??
113
+ (typeof data?.error === "string" ? data.error : undefined);
114
+ console.error(JSON.stringify({
115
+ event: "facilitator_request_failed",
116
+ provider: entry.config.name,
117
+ endpoint: path,
118
+ status: response.status,
119
+ scheme: requirement?.scheme,
120
+ network: requirement?.network,
121
+ errorCode: nestedError?.code ?? data?.code,
122
+ errorMessage: typeof message === "string" ? message.slice(0, 200) : undefined,
123
+ retryAfter: response.headers.get("retry-after") ?? undefined,
124
+ cfRay: response.headers.get("cf-ray") ?? undefined,
125
+ }));
126
126
  }
127
127
  function isVerifySuccess(verify) {
128
128
  return verify?.valid === true || verify?.isValid === true;
@@ -265,7 +265,6 @@ export function createGatewayServer(providers) {
265
265
  `x402_gateway_paid_requests_total ${metrics.paidRequests}`,
266
266
  `x402_gateway_verify_failures_total ${metrics.verifyFailures}`,
267
267
  `x402_gateway_settle_failures_total ${metrics.settleFailures}`,
268
- `x402_gateway_fee_quote_failures_total ${metrics.feeQuoteFailures}`,
269
268
  `x402_gateway_upstream_failures_total ${metrics.upstreamFailures}`,
270
269
  "",
271
270
  ].join("\n"));
@@ -295,7 +294,7 @@ export function createGatewayServer(providers) {
295
294
  }
296
295
  const paymentHeader = request.headers[headers.signature.toLowerCase()];
297
296
  if (!paymentHeader || Array.isArray(paymentHeader)) {
298
- const accepts = await attachFeeQuotes(entry, requirements);
297
+ const accepts = requirements;
299
298
  const challenge = {
300
299
  x402Version: 2,
301
300
  error: "Payment required",
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-beta.2",
3
+ "version": "1.0.1-beta.1",
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": {