@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 +12 -10
- package/dist/config.js +31 -12
- package/dist/server.js +24 -25
- package/dist/tokens.js +11 -5
- package/package.json +4 -4
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.
|
|
7
|
-
- `@bankofai/x402-evm@1.0.
|
|
8
|
-
- `@bankofai/x402-tron@1.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
|
|
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.
|
|
152
|
-
`extra.assetTransferMethod: permit2
|
|
153
|
-
configs that say `exact_permit` are normalized
|
|
154
|
-
|
|
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
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
21
|
-
"tron
|
|
22
|
-
"
|
|
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.
|
|
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.
|
|
26
|
-
"@bankofai/x402-evm": "1.0.
|
|
27
|
-
"@bankofai/x402-tron": "1.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": {
|