@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 +8 -2
- package/dist/gateway/config.js +31 -12
- package/dist/gateway/server.js +40 -28
- package/dist/gateway/tokens.js +11 -5
- package/dist/tokens.js +12 -6
- package/package.json +5 -5
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:
|
|
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
|
-
|
|
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",
|
package/dist/gateway/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/gateway/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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
310
|
+
const accepts = requirements;
|
|
299
311
|
const challenge = {
|
|
300
312
|
x402Version: 2,
|
|
301
313
|
error: "Payment required",
|
|
302
|
-
resource: { url: url
|
|
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/gateway/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/dist/tokens.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const TOKENS = {
|
|
2
|
-
"tron:
|
|
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:
|
|
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:
|
|
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:
|
|
79
|
-
"tron
|
|
80
|
-
"
|
|
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.
|
|
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.
|
|
24
|
-
"@bankofai/x402-evm": "1.0.1-beta.
|
|
25
|
-
"@bankofai/x402-gateway": "
|
|
26
|
-
"@bankofai/x402-tron": "1.0.1-beta.
|
|
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"
|