@bankofai/x402-cli 1.0.0-beta.3 → 1.0.0-beta.4

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
@@ -5,11 +5,13 @@ import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { spawn } from "node:child_process";
8
+ import { createRequire } from "node:module";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import YAML from "yaml";
10
11
  import { createPaymentPayload, decodeRequired, decodeResponse, encodeRequired, encodeResponse, encodeSignature, headers } from "./x402.js";
11
12
  import { findTokenByAddress, getToken, normalizeNetwork, toSmallestUnit } from "./tokens.js";
12
13
  const BOOLEAN_FLAGS = new Set(["daemon", "dry-run", "help", "human", "include-blocked", "json", "version"]);
14
+ const require = createRequire(import.meta.url);
13
15
  function parseArgs(argv) {
14
16
  const [command = "help", ...rest] = argv;
15
17
  const positional = [];
@@ -569,7 +571,7 @@ function providerFilename(fqn) {
569
571
  function defaultCatalogSource() {
570
572
  return fs.existsSync(cachedCatalogPath())
571
573
  ? cachedCatalogPath()
572
- : "https://tm-x402-catalog.bankofai.io/api/catalog.json";
574
+ : "https://x402-catalog.bankofai.io/api/catalog.json";
573
575
  }
574
576
  function remoteBaseFromCatalogPayload(payload) {
575
577
  const base = payload.base_url ?? payload.baseUrl;
@@ -696,7 +698,7 @@ function submissionCatalog(detail) {
696
698
  description,
697
699
  useCase,
698
700
  i18n: detail.i18n ?? { "zh-CN": zhCopy(title, subtitle, description, useCase) },
699
- logo: detail.logo ?? "https://tm-x402-catalog.bankofai.io/assets/providers/default.png",
701
+ logo: detail.logo ?? "https://x402-catalog.bankofai.io/assets/providers/default.png",
700
702
  category: detail.category ?? "other",
701
703
  chains: detail.chains ?? [],
702
704
  isFirstParty: Boolean(detail.is_first_party ?? detail.isFirstParty),
@@ -1067,10 +1069,20 @@ function executableInPath(name) {
1067
1069
  }
1068
1070
  return undefined;
1069
1071
  }
1072
+ function resolveGatewayPackageRuntime() {
1073
+ try {
1074
+ return require.resolve("@bankofai/x402-gateway/dist/cli.js");
1075
+ }
1076
+ catch {
1077
+ return undefined;
1078
+ }
1079
+ }
1070
1080
  function gatewayCommand(options) {
1071
1081
  const explicit = opt(options, "gateway-bin");
1082
+ const gatewayPackageRuntime = resolveGatewayPackageRuntime();
1072
1083
  const candidates = [
1073
1084
  explicit ? { file: explicit, source: "--gateway-bin" } : undefined,
1085
+ gatewayPackageRuntime ? { file: gatewayPackageRuntime, source: "@bankofai/x402-gateway dependency" } : undefined,
1074
1086
  { file: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "gateway", "cli.js"), source: "bundled gateway runtime" },
1075
1087
  executableInPath("x402-gateway") ? { file: executableInPath("x402-gateway"), source: "PATH x402-gateway" } : undefined,
1076
1088
  { file: path.resolve(process.cwd(), "../x402-gateway/dist/cli.js"), source: "sibling ../x402-gateway" },
@@ -1087,7 +1099,7 @@ function gatewayCommand(options) {
1087
1099
  // Try the next candidate.
1088
1100
  }
1089
1101
  }
1090
- throw new Error("x402-gateway runtime not found. Install/publish a x402-gateway npm binary, run from a checkout with ../x402-gateway/dist/cli.js, or pass --gateway-bin <path>.");
1102
+ throw new Error("x402-gateway runtime not found. Install @bankofai/x402-gateway, run from a checkout with ../x402-gateway/dist/cli.js, or pass --gateway-bin <path>.");
1091
1103
  }
1092
1104
  async function gatewayStart(args, options) {
1093
1105
  const gateway = gatewayCommand(options);
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { loadProviders } from "./config.js";
3
+ import { createGatewayServer } from "./server.js";
4
+ function parseArgs(argv) {
5
+ const options = {};
6
+ for (let i = 0; i < argv.length; i += 1) {
7
+ const item = argv[i];
8
+ if (!item.startsWith("--"))
9
+ continue;
10
+ const key = item.slice(2);
11
+ const next = argv[i + 1];
12
+ if (!next || next.startsWith("--"))
13
+ options[key] = true;
14
+ else {
15
+ options[key] = next;
16
+ i += 1;
17
+ }
18
+ }
19
+ return options;
20
+ }
21
+ function opt(options, key, fallback) {
22
+ const value = options[key];
23
+ return typeof value === "string" ? value : fallback;
24
+ }
25
+ const options = parseArgs(process.argv.slice(2));
26
+ const providersPath = opt(options, "providers", opt(options, "provider", process.env.X402_GATEWAY_PROVIDERS_DIR || "/app/providers"));
27
+ const host = opt(options, "host", process.env.X402_GATEWAY_HOST || "0.0.0.0");
28
+ const port = Number(opt(options, "port", process.env.PORT || "8080"));
29
+ const providers = loadProviders(providersPath);
30
+ const server = createGatewayServer(providers);
31
+ server.listen(port, host, () => {
32
+ process.stdout.write(JSON.stringify({ ok: true, host, port, providers: [...providers.keys()] }, null, 2) + "\n");
33
+ });
@@ -0,0 +1,136 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { getToken, normalizeNetwork, toSmallestUnit } from "./tokens.js";
5
+ function expandEnv(value) {
6
+ return value.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? "");
7
+ }
8
+ function expandDeep(value) {
9
+ if (typeof value === "string")
10
+ return expandEnv(value);
11
+ if (Array.isArray(value))
12
+ return value.map(expandDeep);
13
+ if (value && typeof value === "object") {
14
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, expandDeep(item)]));
15
+ }
16
+ return value;
17
+ }
18
+ function assertString(value, name) {
19
+ if (typeof value !== "string" || !value.trim())
20
+ throw new Error(`${name} is required`);
21
+ }
22
+ function validateProvider(config, file) {
23
+ assertString(config.name, `${file}: name`);
24
+ assertString(config.forward_url, `${file}: forward_url`);
25
+ assertString(config.operator?.network, `${file}: operator.network`);
26
+ assertString(config.operator?.recipient, `${file}: operator.recipient`);
27
+ if (!config.endpoints?.length)
28
+ throw new Error(`${file}: endpoints must contain at least one endpoint`);
29
+ const seen = new Set();
30
+ for (const [index, endpoint] of config.endpoints.entries()) {
31
+ assertString(endpoint.method, `${file}: endpoints[${index}].method`);
32
+ assertString(endpoint.path, `${file}: endpoints[${index}].path`);
33
+ if (!endpoint.path.startsWith("/"))
34
+ throw new Error(`${file}: endpoint path must start with /`);
35
+ const method = endpoint.method.toUpperCase();
36
+ if (!["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(method)) {
37
+ throw new Error(`${file}: unsupported endpoint method ${endpoint.method}`);
38
+ }
39
+ const key = `${method} ${endpoint.path}`;
40
+ if (seen.has(key))
41
+ throw new Error(`${file}: duplicate endpoint ${key}`);
42
+ seen.add(key);
43
+ for (const [tierIndex, tier] of (endpoint.metering?.dimensions?.[0]?.tiers ?? []).entries()) {
44
+ if (typeof tier.price_usd !== "number" || tier.price_usd < 0) {
45
+ throw new Error(`${file}: endpoints[${index}].metering tier ${tierIndex} price_usd must be >= 0`);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ export function loadProvider(file) {
51
+ const config = expandDeep(YAML.parse(fs.readFileSync(file, "utf8")));
52
+ validateProvider(config, file);
53
+ config.operator.network = normalizeNetwork(config.operator.network);
54
+ normalizePaymentProtocol(config, file);
55
+ return {
56
+ config,
57
+ facilitatorUrl: config.operator.facilitator_url ||
58
+ process.env.X402_FACILITATOR_URL ||
59
+ process.env.FACILITATOR_URL ||
60
+ "https://facilitator.bankofai.io",
61
+ facilitatorApiKey: config.operator.facilitator_api_key ||
62
+ (config.operator.facilitator_api_key_env
63
+ ? process.env[config.operator.facilitator_api_key_env]
64
+ : undefined) ||
65
+ process.env.X402_FACILITATOR_API_KEY ||
66
+ process.env.FACILITATOR_API_KEY,
67
+ };
68
+ }
69
+ function normalizePaymentProtocol(config, file) {
70
+ const raw = String(config.operator.protocol || config.operator.scheme || "exact").toLowerCase();
71
+ const normalized = raw.replace(/[-:\s]/g, "_");
72
+ if (!["exact", "exact_permit", "permit2", "exact_permit2"].includes(normalized)) {
73
+ throw new Error(`${file}: unsupported x402 protocol ${raw}; use exact + permit2`);
74
+ }
75
+ config.operator.scheme = "exact";
76
+ config.operator.protocol = "exact";
77
+ config.operator.asset_transfer_method = "permit2";
78
+ config.operator.assetTransferMethod = "permit2";
79
+ }
80
+ export function loadProviders(providerPath) {
81
+ const stat = fs.statSync(providerPath);
82
+ const files = stat.isDirectory()
83
+ ? fs.readdirSync(providerPath, { recursive: true })
84
+ .map(item => path.join(providerPath, String(item)))
85
+ .filter(item => item.endsWith("provider.yml") || item.endsWith("provider.yaml"))
86
+ : [providerPath];
87
+ const entries = files.map(file => {
88
+ const entry = loadProvider(file);
89
+ return [entry.config.name, entry];
90
+ });
91
+ const names = new Set();
92
+ for (const [name] of entries) {
93
+ if (names.has(name))
94
+ throw new Error(`duplicate provider name: ${name}`);
95
+ names.add(name);
96
+ }
97
+ return new Map(entries);
98
+ }
99
+ export function endpointFor(provider, method, routePath) {
100
+ return provider.endpoints?.find(endpoint => endpoint.method.toUpperCase() === method.toUpperCase() && pathMatches(endpoint.path, routePath));
101
+ }
102
+ function pathMatches(template, routePath) {
103
+ const templateParts = template.split("/").filter(Boolean);
104
+ const routeParts = routePath.split("/").filter(Boolean);
105
+ if (templateParts.length !== routeParts.length)
106
+ return false;
107
+ return templateParts.every((part, index) => {
108
+ if (part.startsWith("{") && part.endsWith("}"))
109
+ return routeParts[index].length > 0;
110
+ return part === routeParts[index];
111
+ });
112
+ }
113
+ export function priceUsd(endpoint, params = {}) {
114
+ const variant = endpoint?.metering?.variants?.find(item => params[item.param] === item.value);
115
+ return (variant?.dimensions ?? endpoint?.metering?.dimensions)?.[0]?.tiers?.[0]?.price_usd ?? 0;
116
+ }
117
+ export function paymentRequirements(provider, price) {
118
+ if (price <= 0)
119
+ return [];
120
+ const network = normalizeNetwork(provider.operator.network);
121
+ const symbols = provider.operator.currencies?.usd ?? ["USDT"];
122
+ const payTo = provider.recipients?.[provider.operator.recipient]?.account ?? provider.operator.recipient;
123
+ return symbols.map(symbol => {
124
+ const token = getToken(network, symbol);
125
+ const transferMethod = provider.operator.assetTransferMethod || provider.operator.asset_transfer_method || token.assetTransferMethod;
126
+ return {
127
+ scheme: "exact",
128
+ network,
129
+ amount: toSmallestUnit(price, token.decimals),
130
+ asset: token.address,
131
+ payTo,
132
+ maxTimeoutSeconds: provider.operator.valid_for_seconds ?? 300,
133
+ extra: transferMethod === "permit2" ? { assetTransferMethod: "permit2" } : {},
134
+ };
135
+ });
136
+ }
@@ -0,0 +1,249 @@
1
+ import http from "node:http";
2
+ import { URL } from "node:url";
3
+ import { endpointFor, paymentRequirements, priceUsd } from "./config.js";
4
+ import { decodeSignature, encodeRequired, encodeResponse, headers, matchRequirement } from "./x402.js";
5
+ const metrics = {
6
+ requests: 0,
7
+ paidRequests: 0,
8
+ verifyFailures: 0,
9
+ settleFailures: 0,
10
+ upstreamFailures: 0,
11
+ };
12
+ async function readBody(request) {
13
+ const chunks = [];
14
+ for await (const chunk of request)
15
+ chunks.push(Buffer.from(chunk));
16
+ return Buffer.concat(chunks);
17
+ }
18
+ function json(response, status, body, extraHeaders = {}) {
19
+ response.writeHead(status, { "content-type": "application/json", ...extraHeaders });
20
+ response.end(JSON.stringify(body));
21
+ }
22
+ async function facilitatorPost(entry, path, body) {
23
+ const headers = { "content-type": "application/json" };
24
+ if (entry.facilitatorApiKey) {
25
+ headers.authorization = `Bearer ${entry.facilitatorApiKey}`;
26
+ }
27
+ const response = await fetch(new URL(path, entry.facilitatorUrl), {
28
+ method: "POST",
29
+ headers,
30
+ body: JSON.stringify(body),
31
+ });
32
+ const text = await response.text();
33
+ const data = text ? JSON.parse(text) : {};
34
+ if (!response.ok)
35
+ throw new Error(`facilitator ${path} failed: ${response.status} ${text}`);
36
+ return data;
37
+ }
38
+ async function attachFeeQuotes(entry, requirements, context) {
39
+ try {
40
+ const quotes = await facilitatorPost(entry, "/fee_quote", {
41
+ paymentRequirements: requirements,
42
+ context,
43
+ });
44
+ const list = Array.isArray(quotes) ? quotes : quotes.quotes ?? quotes.fees ?? [];
45
+ return requirements.map(requirement => {
46
+ const quote = list.find((item) => item.scheme === requirement.scheme &&
47
+ item.network === requirement.network &&
48
+ String(item.asset).toLowerCase() === requirement.asset.toLowerCase());
49
+ return quote?.fee ? { ...requirement, extra: { ...requirement.extra, fee: quote.fee } } : requirement;
50
+ });
51
+ }
52
+ catch {
53
+ return requirements;
54
+ }
55
+ }
56
+ function isAdminAllowed(request) {
57
+ const token = process.env.X402_GATEWAY_ADMIN_TOKEN;
58
+ if (!token)
59
+ return true;
60
+ const auth = request.headers.authorization ?? "";
61
+ return auth === `Bearer ${token}`;
62
+ }
63
+ function requestParams(url, body, request) {
64
+ const params = {};
65
+ url.searchParams.forEach((value, key) => { params[key] = value; });
66
+ const contentType = String(request.headers["content-type"] ?? "").split(";", 1)[0].toLowerCase();
67
+ try {
68
+ if (body.length && contentType === "application/json") {
69
+ const parsed = JSON.parse(body.toString("utf8"));
70
+ if (parsed && typeof parsed === "object") {
71
+ for (const [key, value] of Object.entries(parsed)) {
72
+ if (["string", "number", "boolean"].includes(typeof value))
73
+ params[key] = String(value);
74
+ }
75
+ }
76
+ }
77
+ else if (body.length && contentType === "application/x-www-form-urlencoded") {
78
+ new URLSearchParams(body.toString("utf8")).forEach((value, key) => { params[key] = value; });
79
+ }
80
+ }
81
+ catch {
82
+ // Metering variants are advisory; malformed bodies just do not add params.
83
+ }
84
+ return params;
85
+ }
86
+ function upstreamHeaders(request, entry) {
87
+ const headersOut = new Headers();
88
+ for (const [key, value] of Object.entries(request.headers)) {
89
+ if (!value)
90
+ continue;
91
+ const lower = key.toLowerCase();
92
+ if (["host", "connection", "content-length", "authorization", "payment-signature"].includes(lower))
93
+ continue;
94
+ headersOut.set(key, Array.isArray(value) ? value.join(",") : value);
95
+ }
96
+ const auth = entry.config.routing?.auth;
97
+ if (auth) {
98
+ const value = auth.value ?? (auth.value_from_env ? process.env[auth.value_from_env] : undefined);
99
+ if (value && ["header", "access_token", "oauth2", undefined].includes(auth.method)) {
100
+ headersOut.set(auth.key ?? "Authorization", `${auth.prefix ?? ""}${value}`);
101
+ }
102
+ }
103
+ return headersOut;
104
+ }
105
+ function upstreamUrl(entry, request, routePath) {
106
+ const sourceUrl = new URL(request.url ?? "/", "http://local");
107
+ const upstream = new URL(routePath + (sourceUrl.search || ""), entry.config.forward_url);
108
+ const auth = entry.config.routing?.auth;
109
+ const value = auth?.value ?? (auth?.value_from_env ? process.env[auth.value_from_env] : undefined);
110
+ if (auth?.method === "query_param" && value) {
111
+ upstream.searchParams.set(auth.param ?? auth.key ?? "api_key", value);
112
+ }
113
+ return upstream;
114
+ }
115
+ async function forward(entry, request, response, routePath, body, paymentResponse) {
116
+ const upstream = upstreamUrl(entry, request, routePath);
117
+ const upstreamResponse = await fetch(upstream, {
118
+ method: request.method,
119
+ headers: upstreamHeaders(request, entry),
120
+ body: ["GET", "HEAD"].includes(request.method ?? "GET") ? undefined : new Uint8Array(body),
121
+ });
122
+ const responseHeaders = {};
123
+ upstreamResponse.headers.forEach((value, key) => {
124
+ if (!["connection", "transfer-encoding", "content-encoding", "content-length"].includes(key.toLowerCase())) {
125
+ responseHeaders[key] = value;
126
+ }
127
+ });
128
+ if (paymentResponse)
129
+ responseHeaders[headers.response] = encodeResponse(paymentResponse);
130
+ response.writeHead(upstreamResponse.status, responseHeaders);
131
+ response.end(Buffer.from(await upstreamResponse.arrayBuffer()));
132
+ }
133
+ export function createGatewayServer(providers) {
134
+ return http.createServer(async (request, response) => {
135
+ try {
136
+ metrics.requests += 1;
137
+ const url = new URL(request.url ?? "/", "http://local");
138
+ if (url.pathname === "/__402/health") {
139
+ json(response, 200, { ok: true, providers: providers.size });
140
+ return;
141
+ }
142
+ if (url.pathname === "/__402/ready") {
143
+ json(response, 200, { ok: true, providers: providers.size });
144
+ return;
145
+ }
146
+ if (url.pathname === "/__402/providers") {
147
+ if (!isAdminAllowed(request))
148
+ return json(response, 401, { error: "unauthorized" });
149
+ json(response, 200, { providers: [...providers.values()].map(entry => ({
150
+ name: entry.config.name,
151
+ title: entry.config.title,
152
+ network: entry.config.operator.network,
153
+ facilitatorUrl: entry.facilitatorUrl,
154
+ endpoints: entry.config.endpoints?.length ?? 0,
155
+ })) });
156
+ return;
157
+ }
158
+ if (url.pathname === "/__402/endpoints") {
159
+ if (!isAdminAllowed(request))
160
+ return json(response, 401, { error: "unauthorized" });
161
+ json(response, 200, { endpoints: [...providers.values()].flatMap(entry => (entry.config.endpoints ?? []).map(endpoint => ({
162
+ provider: entry.config.name,
163
+ method: endpoint.method,
164
+ path: endpoint.path,
165
+ priceUsd: priceUsd(endpoint),
166
+ network: entry.config.operator.network,
167
+ }))) });
168
+ return;
169
+ }
170
+ if (url.pathname === "/metrics") {
171
+ response.writeHead(200, { "content-type": "text/plain; version=0.0.4" });
172
+ response.end([
173
+ `x402_gateway_requests_total ${metrics.requests}`,
174
+ `x402_gateway_paid_requests_total ${metrics.paidRequests}`,
175
+ `x402_gateway_verify_failures_total ${metrics.verifyFailures}`,
176
+ `x402_gateway_settle_failures_total ${metrics.settleFailures}`,
177
+ `x402_gateway_upstream_failures_total ${metrics.upstreamFailures}`,
178
+ "",
179
+ ].join("\n"));
180
+ return;
181
+ }
182
+ const match = url.pathname.match(/^\/providers\/([^/]+)(\/.*)$/);
183
+ if (!match) {
184
+ json(response, 404, { error: "not found" });
185
+ return;
186
+ }
187
+ const entry = providers.get(match[1]);
188
+ if (!entry) {
189
+ json(response, 404, { error: "provider not found" });
190
+ return;
191
+ }
192
+ const routePath = match[2];
193
+ const endpoint = endpointFor(entry.config, request.method ?? "GET", routePath);
194
+ if (!endpoint) {
195
+ json(response, 404, { error: "endpoint not found" });
196
+ return;
197
+ }
198
+ const body = await readBody(request);
199
+ const requirements = paymentRequirements(entry.config, priceUsd(endpoint, requestParams(url, body, request)));
200
+ if (!requirements.length) {
201
+ await forward(entry, request, response, routePath, body);
202
+ return;
203
+ }
204
+ const paymentHeader = request.headers[headers.signature.toLowerCase()];
205
+ if (!paymentHeader || Array.isArray(paymentHeader)) {
206
+ const accepts = await attachFeeQuotes(entry, requirements);
207
+ const challenge = {
208
+ x402Version: 2,
209
+ error: "Payment required",
210
+ resource: { url: url.pathname },
211
+ accepts,
212
+ };
213
+ json(response, 402, challenge, { [headers.required]: encodeRequired(challenge) });
214
+ return;
215
+ }
216
+ const payload = decodeSignature(paymentHeader);
217
+ const requirement = requirements.find(item => matchRequirement(payload, item));
218
+ if (!requirement) {
219
+ json(response, 400, { error: "payment does not match any requirement" });
220
+ return;
221
+ }
222
+ const verify = await facilitatorPost(entry, "/verify", {
223
+ paymentPayload: payload,
224
+ paymentRequirements: requirement,
225
+ });
226
+ if (verify?.valid === false || verify?.isValid === false) {
227
+ metrics.verifyFailures += 1;
228
+ json(response, 400, { error: "payment verification failed", verify });
229
+ return;
230
+ }
231
+ let settle;
232
+ try {
233
+ settle = await facilitatorPost(entry, "/settle", {
234
+ paymentPayload: payload,
235
+ paymentRequirements: requirement,
236
+ });
237
+ }
238
+ catch (error) {
239
+ metrics.settleFailures += 1;
240
+ throw error;
241
+ }
242
+ metrics.paidRequests += 1;
243
+ await forward(entry, request, response, routePath, body, settle);
244
+ }
245
+ catch (error) {
246
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) });
247
+ }
248
+ });
249
+ }
@@ -0,0 +1,38 @@
1
+ export const TOKENS = {
2
+ "tron:mainnet": {
3
+ USDT: { address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
4
+ USDD: { address: "TXDk8mbtRbXeYuMNS83CfKPaYYT8XWv9Hz", decimals: 18, name: "Decentralized USD", symbol: "USDD", assetTransferMethod: "permit2" },
5
+ },
6
+ "tron:nile": {
7
+ USDT: { address: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", decimals: 6, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
8
+ USDD: { address: "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK", decimals: 18, name: "Decentralized USD", symbol: "USDD", assetTransferMethod: "permit2" },
9
+ },
10
+ "eip155:56": {
11
+ USDT: { address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
12
+ },
13
+ "eip155:97": {
14
+ USDT: { address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd", decimals: 18, name: "Tether USD", symbol: "USDT", assetTransferMethod: "permit2" },
15
+ USDC: { address: "0x64544969ed7EBf5f083679233325356EbE738930", decimals: 18, name: "USD Coin", symbol: "USDC", assetTransferMethod: "permit2" },
16
+ },
17
+ };
18
+ export function normalizeNetwork(network) {
19
+ return ({
20
+ "tron-mainnet": "tron:mainnet",
21
+ "tron-shasta": "tron:shasta",
22
+ "tron-nile": "tron:nile",
23
+ "bsc-mainnet": "eip155:56",
24
+ "bsc-testnet": "eip155:97",
25
+ }[network] ?? network);
26
+ }
27
+ export function getToken(network, symbol) {
28
+ const normalized = normalizeNetwork(network);
29
+ const token = TOKENS[normalized]?.[symbol.toUpperCase()];
30
+ if (!token)
31
+ throw new Error(`unknown token ${symbol} on ${network}`);
32
+ return token;
33
+ }
34
+ export function toSmallestUnit(amount, decimals) {
35
+ const [whole, fraction = ""] = String(amount).split(".");
36
+ const padded = (fraction + "0".repeat(decimals)).slice(0, decimals);
37
+ return (BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(padded || "0")).toString();
38
+ }
@@ -0,0 +1,25 @@
1
+ import { decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader, } from "@bankofai/x402-core/http";
2
+ export const headers = {
3
+ required: "PAYMENT-REQUIRED",
4
+ signature: "PAYMENT-SIGNATURE",
5
+ response: "PAYMENT-RESPONSE",
6
+ };
7
+ export function encodeRequired(value) {
8
+ return encodePaymentRequiredHeader(value);
9
+ }
10
+ export function encodeResponse(value) {
11
+ return encodePaymentResponseHeader(value);
12
+ }
13
+ export function decodeSignature(value) {
14
+ return decodePaymentSignatureHeader(value);
15
+ }
16
+ export function matchRequirement(payload, requirement) {
17
+ const accepted = payload?.accepted ?? payload?.payment?.accepted ?? payload;
18
+ if (!accepted || typeof accepted !== "object")
19
+ return false;
20
+ return (accepted.scheme === requirement.scheme &&
21
+ accepted.network === requirement.network &&
22
+ String(accepted.amount) === requirement.amount &&
23
+ String(accepted.asset).toLowerCase() === requirement.asset.toLowerCase() &&
24
+ String(accepted.payTo) === requirement.payTo);
25
+ }
package/dist/tokens.js CHANGED
@@ -56,9 +56,9 @@ export const TOKENS = {
56
56
  },
57
57
  "eip155:97": {
58
58
  USDT: {
59
- address: "0x64544969ed7EBf5f083679233325356EbE738930",
59
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
60
60
  decimals: 18,
61
- name: "USD Coin",
61
+ name: "Tether USD",
62
62
  symbol: "USDT",
63
63
  version: "1",
64
64
  assetTransferMethod: "permit2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-cli",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -10,6 +10,8 @@
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p tsconfig.json",
13
+ "bundle:gateway": "node scripts/bundle-gateway.mjs",
14
+ "prepack": "npm run build && npm run bundle:gateway",
13
15
  "test": "npm run build && node --test tests/*.test.mjs",
14
16
  "start": "node dist/cli.js",
15
17
  "dev": "tsx src/cli.ts"