@bankofai/x402-gateway 1.0.0-beta.1 → 1.0.0-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 +11 -1
- package/dist/cli.js +71 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,11 @@ x402-gateway check --providers providers
|
|
|
45
45
|
x402-gateway check --provider examples/provider.yml --json
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
`check` validates provider YAML syntax, required fields, environment expansion,
|
|
49
|
+
network aliases, endpoint shape, and duplicate provider names. It does not call
|
|
50
|
+
upstream APIs, facilitator services, RPC endpoints, recipient addresses, or the
|
|
51
|
+
full payment path.
|
|
52
|
+
|
|
48
53
|
Run from source during development:
|
|
49
54
|
|
|
50
55
|
```bash
|
|
@@ -81,10 +86,11 @@ Options:
|
|
|
81
86
|
|
|
82
87
|
- `--provider <file>`: load one provider YAML file.
|
|
83
88
|
- `--providers <dir>`: recursively load `provider.yml` / `provider.yaml`.
|
|
89
|
+
`--provider` and `--providers` are mutually exclusive.
|
|
84
90
|
- `--host <host>`: bind host. The CLI default is `127.0.0.1`; Docker passes `0.0.0.0`.
|
|
85
91
|
- `--port <port>`: bind port, default `8080`.
|
|
86
92
|
- `--json`: machine-readable startup/check/error output.
|
|
87
|
-
- `--quiet`: suppress startup and
|
|
93
|
+
- `--quiet`: suppress startup, shutdown, and successful `check` output.
|
|
88
94
|
- `--debug`: include stack traces for startup errors.
|
|
89
95
|
- `--help`, `--version`: inspect usage and installed version.
|
|
90
96
|
|
|
@@ -97,6 +103,10 @@ health: http://127.0.0.1:4020/__402/health
|
|
|
97
103
|
ready: http://127.0.0.1:4020/__402/ready
|
|
98
104
|
```
|
|
99
105
|
|
|
106
|
+
With `--json`, startup output includes `source`, `host`, `port`, `count`,
|
|
107
|
+
`providers`, `health`, and `ready` for deployment scripts. CLI errors also
|
|
108
|
+
honor `--json`, including argument parsing errors such as unknown options.
|
|
109
|
+
|
|
100
110
|
Admin endpoints such as `/__402/providers`, `/__402/endpoints`, and `/metrics`
|
|
101
111
|
are protected by default. Set `X402_GATEWAY_ADMIN_TOKEN` in deployed
|
|
102
112
|
environments and call them with `Authorization: Bearer <token>`. For a
|
package/dist/cli.js
CHANGED
|
@@ -65,17 +65,21 @@ function opt(options, key, fallback) {
|
|
|
65
65
|
function flag(options, key) {
|
|
66
66
|
return options[key] === true;
|
|
67
67
|
}
|
|
68
|
-
function
|
|
68
|
+
function rawHasFlag(argv, name, short) {
|
|
69
|
+
return argv.some(item => item === `--${name}` || item.startsWith(`--${name}=`) || (short ? item === short : false));
|
|
70
|
+
}
|
|
71
|
+
function readVersion() {
|
|
69
72
|
try {
|
|
70
73
|
const url = new URL("../package.json", import.meta.url);
|
|
71
|
-
|
|
74
|
+
const version = JSON.parse(fs.readFileSync(url, "utf8")).version;
|
|
75
|
+
return typeof version === "string" && version ? version : undefined;
|
|
72
76
|
}
|
|
73
77
|
catch {
|
|
74
|
-
return
|
|
78
|
+
return undefined;
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
function help() {
|
|
78
|
-
return `x402-gateway ${
|
|
82
|
+
return `x402-gateway ${readVersion() ?? "unknown"}
|
|
79
83
|
|
|
80
84
|
Usage:
|
|
81
85
|
x402-gateway start --providers <dir> [options]
|
|
@@ -105,6 +109,9 @@ Examples:
|
|
|
105
109
|
`;
|
|
106
110
|
}
|
|
107
111
|
function providerPath(options) {
|
|
112
|
+
if (opt(options, "provider") && opt(options, "providers")) {
|
|
113
|
+
throw new CliError("Options --provider and --providers are mutually exclusive.");
|
|
114
|
+
}
|
|
108
115
|
const source = opt(options, "providers", opt(options, "provider", process.env.X402_GATEWAY_PROVIDERS_DIR));
|
|
109
116
|
if (!source)
|
|
110
117
|
throw new CliError("No provider source configured. Use --provider <file> or --providers <dir>.");
|
|
@@ -120,25 +127,56 @@ function parsePort(value) {
|
|
|
120
127
|
}
|
|
121
128
|
return port;
|
|
122
129
|
}
|
|
130
|
+
function parseHost(value) {
|
|
131
|
+
const host = value ?? process.env.X402_GATEWAY_HOST ?? "127.0.0.1";
|
|
132
|
+
if (!host.trim())
|
|
133
|
+
throw new CliError("Invalid --host; expected a non-empty host.");
|
|
134
|
+
return host;
|
|
135
|
+
}
|
|
136
|
+
function listenErrorMessage(error, host, port) {
|
|
137
|
+
const err = error;
|
|
138
|
+
if (err?.code === "EADDRINUSE")
|
|
139
|
+
return `Port ${port} is already in use on ${host}. Choose another --port or stop the existing process.`;
|
|
140
|
+
if (err?.code === "EADDRNOTAVAIL")
|
|
141
|
+
return `Host ${host} is not available on this machine. Use --host 127.0.0.1 for local runs or --host 0.0.0.0 in containers.`;
|
|
142
|
+
return error instanceof Error ? error.message : String(error);
|
|
143
|
+
}
|
|
123
144
|
function publicHost(host) {
|
|
124
145
|
return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
125
146
|
}
|
|
126
|
-
function
|
|
147
|
+
function localUrls(host, port) {
|
|
148
|
+
const base = `http://${publicHost(host)}:${port}`;
|
|
149
|
+
return {
|
|
150
|
+
base,
|
|
151
|
+
health: `${base}/__402/health`,
|
|
152
|
+
ready: `${base}/__402/ready`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function printStartup(options, source, host, port, providers) {
|
|
127
156
|
if (flag(options, "quiet"))
|
|
128
157
|
return;
|
|
158
|
+
const urls = localUrls(host, port);
|
|
129
159
|
if (flag(options, "json")) {
|
|
130
|
-
process.stdout.write(JSON.stringify({ ok: true, host, port, providers }, null, 2) + "\n");
|
|
160
|
+
process.stdout.write(JSON.stringify({ ok: true, source, host, port, count: providers.length, providers, health: urls.health, ready: urls.ready }, null, 2) + "\n");
|
|
131
161
|
return;
|
|
132
162
|
}
|
|
133
|
-
|
|
134
|
-
process.stdout.write(`x402-gateway listening on ${base}\n`);
|
|
163
|
+
process.stdout.write(`x402-gateway listening on ${urls.base}\n`);
|
|
135
164
|
process.stdout.write(`providers: ${providers.length} loaded\n`);
|
|
136
|
-
process.stdout.write(`health: ${
|
|
137
|
-
process.stdout.write(`ready: ${
|
|
165
|
+
process.stdout.write(`health: ${urls.health}\n`);
|
|
166
|
+
process.stdout.write(`ready: ${urls.ready}\n`);
|
|
167
|
+
}
|
|
168
|
+
function loadProvidersForCli(action, source) {
|
|
169
|
+
try {
|
|
170
|
+
return loadProviders(source);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
174
|
+
throw new Error(`failed to load providers from ${source} for ${action}: ${message}`);
|
|
175
|
+
}
|
|
138
176
|
}
|
|
139
177
|
function check(options) {
|
|
140
178
|
const source = providerPath(options);
|
|
141
|
-
const providers =
|
|
179
|
+
const providers = loadProvidersForCli("check", source);
|
|
142
180
|
const summary = [...providers.values()].map(entry => ({
|
|
143
181
|
name: entry.config.name,
|
|
144
182
|
network: entry.config.operator.network,
|
|
@@ -148,6 +186,8 @@ function check(options) {
|
|
|
148
186
|
process.stdout.write(JSON.stringify({ ok: true, source, count: summary.length, providers: summary }, null, 2) + "\n");
|
|
149
187
|
return;
|
|
150
188
|
}
|
|
189
|
+
if (flag(options, "quiet"))
|
|
190
|
+
return;
|
|
151
191
|
process.stdout.write(`ok: ${summary.length} provider${summary.length === 1 ? "" : "s"} loaded from ${source}\n`);
|
|
152
192
|
for (const item of summary) {
|
|
153
193
|
process.stdout.write(` ${item.name} (${item.network}) endpoints=${item.endpoints}\n`);
|
|
@@ -155,22 +195,29 @@ function check(options) {
|
|
|
155
195
|
}
|
|
156
196
|
async function start(options) {
|
|
157
197
|
const source = providerPath(options);
|
|
158
|
-
const host = opt(options, "host"
|
|
198
|
+
const host = parseHost(opt(options, "host"));
|
|
159
199
|
const port = parsePort(opt(options, "port"));
|
|
160
|
-
const providers =
|
|
200
|
+
const providers = loadProvidersForCli("start", source);
|
|
161
201
|
const server = createGatewayServer(providers);
|
|
162
202
|
await new Promise((resolve, reject) => {
|
|
163
|
-
server.once("error", reject);
|
|
203
|
+
server.once("error", error => reject(new Error(listenErrorMessage(error, host, port))));
|
|
164
204
|
server.listen(port, host, () => {
|
|
165
205
|
server.off("error", reject);
|
|
166
|
-
printStartup(options, host, port, [...providers.keys()]);
|
|
206
|
+
printStartup(options, source, host, port, [...providers.keys()]);
|
|
167
207
|
resolve();
|
|
168
208
|
});
|
|
169
209
|
});
|
|
170
210
|
const shutdown = (signal) => {
|
|
171
211
|
if (!flag(options, "quiet") && !flag(options, "json"))
|
|
172
212
|
process.stderr.write(`received ${signal}, shutting down...\n`);
|
|
213
|
+
const timeout = setTimeout(() => {
|
|
214
|
+
if (!flag(options, "quiet") && !flag(options, "json"))
|
|
215
|
+
process.stderr.write("server close timed out; exiting\n");
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}, 10_000);
|
|
218
|
+
timeout.unref();
|
|
173
219
|
server.close(() => {
|
|
220
|
+
clearTimeout(timeout);
|
|
174
221
|
if (!flag(options, "quiet") && !flag(options, "json"))
|
|
175
222
|
process.stderr.write("server closed\n");
|
|
176
223
|
process.exit(0);
|
|
@@ -186,7 +233,10 @@ async function main() {
|
|
|
186
233
|
return;
|
|
187
234
|
}
|
|
188
235
|
if (flag(parsed.options, "version")) {
|
|
189
|
-
|
|
236
|
+
const version = readVersion();
|
|
237
|
+
if (!version)
|
|
238
|
+
throw new Error("Unable to read package version.");
|
|
239
|
+
process.stdout.write(`${version}\n`);
|
|
190
240
|
return;
|
|
191
241
|
}
|
|
192
242
|
if (parsed.command === "check")
|
|
@@ -195,22 +245,17 @@ async function main() {
|
|
|
195
245
|
await start(parsed.options);
|
|
196
246
|
}
|
|
197
247
|
main().catch(error => {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
return { options: {} };
|
|
204
|
-
}
|
|
205
|
-
})();
|
|
248
|
+
const argv = process.argv.slice(2);
|
|
249
|
+
const json = rawHasFlag(argv, "json");
|
|
250
|
+
const debug = rawHasFlag(argv, "debug");
|
|
206
251
|
const message = error instanceof Error ? error.message : String(error);
|
|
207
|
-
if (
|
|
252
|
+
if (json) {
|
|
208
253
|
process.stdout.write(JSON.stringify({ ok: false, error: { message } }, null, 2) + "\n");
|
|
209
254
|
}
|
|
210
255
|
else {
|
|
211
256
|
process.stderr.write(`x402-gateway: ${message}\n`);
|
|
212
257
|
process.stderr.write("Run `x402-gateway --help` for usage.\n");
|
|
213
|
-
if (
|
|
258
|
+
if (debug && error instanceof Error && error.stack) {
|
|
214
259
|
process.stderr.write(`${error.stack}\n`);
|
|
215
260
|
}
|
|
216
261
|
}
|