armory-cli 0.2.10 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +111 -4
  2. package/dist/cli.js +543 -58
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -1,22 +1,129 @@
1
1
  # armory-cli
2
2
 
3
- Scaffold Armory payment-enabled apps from templates.
3
+ CLI for the Armory x402 payment protocol. Scaffold apps, query networks/tokens, and verify endpoints.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
8
  bun add -g armory-cli
9
+ # or
10
+ npm install -g armory-cli
9
11
  ```
10
12
 
11
- ## Use
13
+ ## Commands
14
+
15
+ ### `armory create <template> [name]`
16
+
17
+ Scaffold a new x402 payment-enabled project.
18
+
19
+ **Server Templates:**
20
+ - `bun-server` - Bun server with payment middleware
21
+ - `express-server` - Express v5 server
22
+ - `hono-server` - Hono server with extension support
23
+ - `elysia-server` - Elysia/Bun server
24
+ - `next-server` - Next.js middleware
25
+
26
+ **Client Templates:**
27
+ - `viem-client` - Viem x402 client
28
+ - `ethers-client` - Ethers.js v6 client
29
+ - `web3-client` - Web3.js client
30
+
31
+ **Legacy:**
32
+ - `facilitator` - Payment verification server (deprecated)
33
+
34
+ ```bash
35
+ armory create bun-server my-api
36
+ armory create hono-server my-hono-api
37
+ armory create viem-client my-client
38
+ ```
39
+
40
+ ### `armory networks`
41
+
42
+ List supported blockchain networks.
43
+
44
+ ```bash
45
+ armory networks # All networks
46
+ armory networks --mainnet # Mainnets only
47
+ armory networks --testnet # Testnets only
48
+ ```
49
+
50
+ ### `armory tokens [network]`
51
+
52
+ List supported tokens.
53
+
54
+ ```bash
55
+ armory tokens # All tokens
56
+ armory tokens base # Tokens on Base
57
+ armory tokens 8453 # By chain ID
58
+ ```
59
+
60
+ ### `armory validate <type> <value>`
61
+
62
+ Validate a network or token identifier.
63
+
64
+ ```bash
65
+ armory validate network base
66
+ armory validate network 8453
67
+ armory validate token usdc
68
+ armory validate token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
69
+ ```
70
+
71
+ ### `armory extensions`
72
+
73
+ List available x402 protocol extensions.
12
74
 
13
75
  ```bash
14
- armory create my-app
15
- cd my-app
76
+ armory extensions
77
+ armory ext
78
+ ```
79
+
80
+ ### `armory verify <url>`
81
+
82
+ Check x402 payment headers on an endpoint.
83
+
84
+ ```bash
85
+ armory verify https://api.example.com/data
86
+ armory inspect https://api.example.com/data
87
+ ```
88
+
89
+ ## Examples
90
+
91
+ ```bash
92
+ # Create a payment server
93
+ armory create bun-server my-payment-api
94
+ cd my-payment-api
16
95
  bun install
17
96
  bun run dev
97
+
98
+ # In another terminal, create a client
99
+ armory create viem-client my-client
100
+ cd my-client
101
+ bun install
102
+ PRIVATE_KEY=0x... bun run dev
103
+
104
+ # Check available networks and tokens
105
+ armory networks
106
+ armory tokens base
107
+
108
+ # Verify an endpoint
109
+ armory verify http://localhost:3000/api/data
18
110
  ```
19
111
 
112
+ ## Supported Networks
113
+
114
+ | Network | Chain ID |
115
+ |---------|----------|
116
+ | Ethereum | 1 |
117
+ | Base | 8453 |
118
+ | Base Sepolia | 84532 |
119
+ | SKALE Base | 1187947933 |
120
+ | SKALE Base Sepolia | 324705682 |
121
+ | Ethereum Sepolia | 11155111 |
122
+
123
+ ## Supported Tokens
124
+
125
+ USDC, EURC, USDT, WBTC, WETH, SKL across supported networks.
126
+
20
127
  ---
21
128
 
22
129
  MIT License | Sawyer Cutler 2026 | Provided "AS IS" without warranty
package/dist/cli.js CHANGED
@@ -4,6 +4,16 @@
4
4
  import { join } from "path";
5
5
  import { mkdir, writeFile } from "fs/promises";
6
6
  import prompts from "prompts";
7
+ import {
8
+ getMainnets,
9
+ getTestnets,
10
+ getAllTokens,
11
+ getTokensByChain,
12
+ resolveNetwork,
13
+ resolveToken,
14
+ isResolvedNetwork,
15
+ isResolvedToken
16
+ } from "@armory-sh/base";
7
17
  var GITIGNORE = `node_modules
8
18
  dist
9
19
  .env
@@ -11,6 +21,26 @@ dist
11
21
  *.log
12
22
  .DS_Store
13
23
  `;
24
+ var EXTENSIONS_INFO = [
25
+ {
26
+ name: "bazaar",
27
+ description: "Resource discovery - let clients discover your API resources",
28
+ package: "@armory-sh/extensions",
29
+ hooks: ["declareDiscoveryExtension", "extractDiscoveryInfo", "validateDiscoveryExtension"]
30
+ },
31
+ {
32
+ name: "siwx",
33
+ description: "Sign-In-With-X - wallet-based authentication",
34
+ package: "@armory-sh/extensions",
35
+ hooks: ["createSIWxHook", "createSIWxPayload", "verifySIWxSignature"]
36
+ },
37
+ {
38
+ name: "payment-id",
39
+ description: "Payment Identifier - idempotency for payment requests",
40
+ package: "@armory-sh/extensions",
41
+ hooks: ["createPaymentIdHook", "declarePaymentIdentifierExtension"]
42
+ }
43
+ ];
14
44
  function getTemplateFiles(template, projectName) {
15
45
  switch (template) {
16
46
  case "facilitator":
@@ -21,18 +51,62 @@ function getTemplateFiles(template, projectName) {
21
51
  ".gitignore": GITIGNORE
22
52
  };
23
53
  case "server":
54
+ case "bun-server":
55
+ return {
56
+ "package.json": SERVER_PACKAGE(projectName, "bun"),
57
+ "src/index.ts": SERVER_INDEX_BUN,
58
+ "README.md": SERVER_README(projectName, "Bun"),
59
+ ".gitignore": GITIGNORE
60
+ };
61
+ case "express-server":
62
+ return {
63
+ "package.json": SERVER_PACKAGE(projectName, "express"),
64
+ "src/index.ts": SERVER_INDEX_EXPRESS,
65
+ "README.md": SERVER_README(projectName, "Express"),
66
+ ".gitignore": GITIGNORE
67
+ };
68
+ case "hono-server":
24
69
  return {
25
- "package.json": SERVER_PACKAGE(projectName),
26
- "src/index.ts": SERVER_INDEX,
27
- "README.md": SERVER_README(projectName),
70
+ "package.json": SERVER_PACKAGE(projectName, "hono"),
71
+ "src/index.ts": SERVER_INDEX_HONO,
72
+ "README.md": SERVER_README(projectName, "Hono"),
73
+ ".gitignore": GITIGNORE
74
+ };
75
+ case "elysia-server":
76
+ return {
77
+ "package.json": SERVER_PACKAGE(projectName, "elysia"),
78
+ "src/index.ts": SERVER_INDEX_ELYSIA,
79
+ "README.md": SERVER_README(projectName, "Elysia"),
80
+ ".gitignore": GITIGNORE
81
+ };
82
+ case "next-server":
83
+ return {
84
+ "package.json": SERVER_PACKAGE(projectName, "next"),
85
+ "src/middleware.ts": SERVER_INDEX_NEXT,
86
+ "README.md": SERVER_README(projectName, "Next.js"),
28
87
  ".gitignore": GITIGNORE
29
88
  };
30
89
  case "client":
90
+ case "viem-client":
31
91
  return {
32
- "package.json": CLIENT_PACKAGE(projectName),
33
- "src/index.ts": CLIENT_INDEX,
34
- "src/client.ts": CLIENT_IMPL,
35
- "README.md": CLIENT_README(projectName),
92
+ "package.json": CLIENT_PACKAGE(projectName, "viem"),
93
+ "src/index.ts": CLIENT_INDEX_VIEM,
94
+ "src/client.ts": CLIENT_IMPL_VIEM,
95
+ "README.md": CLIENT_README(projectName, "Viem"),
96
+ ".gitignore": GITIGNORE
97
+ };
98
+ case "ethers-client":
99
+ return {
100
+ "package.json": CLIENT_PACKAGE(projectName, "ethers"),
101
+ "src/index.ts": CLIENT_INDEX_ETHERS,
102
+ "README.md": CLIENT_README(projectName, "Ethers"),
103
+ ".gitignore": GITIGNORE
104
+ };
105
+ case "web3-client":
106
+ return {
107
+ "package.json": CLIENT_PACKAGE(projectName, "web3"),
108
+ "src/index.ts": CLIENT_INDEX_WEB3,
109
+ "README.md": CLIENT_README(projectName, "Web3.js"),
36
110
  ".gitignore": GITIGNORE
37
111
  };
38
112
  default:
@@ -89,37 +163,160 @@ bun install
89
163
  bun run dev
90
164
  \`\`\`
91
165
  `;
92
- var SERVER_PACKAGE = (name) => `{
166
+ var SERVER_PACKAGE = (name, framework) => {
167
+ const deps = {
168
+ bun: `"@armory-sh/base": "latest",
169
+ "@armory-sh/middleware-bun": "latest"`,
170
+ express: `"@armory-sh/base": "latest",
171
+ "@armory-sh/middleware-express": "latest",
172
+ "express": "^5.0.0"`,
173
+ hono: `"@armory-sh/base": "latest",
174
+ "@armory-sh/middleware-hono": "latest",
175
+ "hono": "^4.0.0"`,
176
+ elysia: `"@armory-sh/base": "latest",
177
+ "@armory-sh/middleware-elysia": "latest",
178
+ "elysia": "^1.0.0"`,
179
+ next: `"@armory-sh/base": "latest",
180
+ "@armory-sh/middleware-next": "latest",
181
+ "next": "^15.0.0"`
182
+ };
183
+ const scripts = {
184
+ bun: `"dev": "bun run src/index.ts"`,
185
+ express: `"dev": "bun run --bun src/index.ts"`,
186
+ hono: `"dev": "bun run --bun src/index.ts"`,
187
+ elysia: `"dev": "bun run --bun src/index.ts"`,
188
+ next: `"dev": "bun run --bun src/middleware.ts"`
189
+ };
190
+ return `{
93
191
  "name": "${name}",
94
192
  "version": "0.1.0",
95
193
  "type": "module",
96
194
  "scripts": {
97
- "dev": "bun run src/index.ts"
195
+ ${scripts[framework]}
98
196
  },
99
197
  "dependencies": {
100
- "@armory-sh/base": "latest",
101
- "@armory-sh/middleware": "latest"
198
+ ${deps[framework]}
102
199
  }
103
200
  }`;
104
- var SERVER_INDEX = `import { Bun } from "bun";
201
+ };
202
+ var SERVER_INDEX_BUN = `import { Bun } from "bun";
105
203
  import { paymentMiddleware } from "@armory-sh/middleware-bun";
106
204
  import { USDC_BASE } from "@armory-sh/base";
107
205
 
108
206
  const app = Bun.serve({
109
207
  port: 3000,
110
208
  fetch: paymentMiddleware({
111
- payTo: "0x" + "0".repeat(40), // TODO: Replace with your address
209
+ payTo: "0x" + "0".repeat(40),
112
210
  network: "base",
113
211
  token: USDC_BASE,
114
- price: () => "2000000", // 2 USDC
212
+ price: () => "2000000",
115
213
  }),
116
214
  });
117
215
 
118
216
  console.log("Server running on http://localhost:3000");
119
217
  `;
120
- var SERVER_README = (name) => `# ${name}
218
+ var SERVER_INDEX_EXPRESS = `import express from "express";
219
+ import { paymentMiddleware } from "@armory-sh/middleware-express";
220
+ import { USDC_BASE } from "@armory-sh/base";
221
+
222
+ const app = express();
223
+
224
+ app.use(
225
+ "/api",
226
+ paymentMiddleware({
227
+ requirements: {
228
+ scheme: "exact",
229
+ network: "base",
230
+ maxAmountRequired: "2000000",
231
+ resource: "https://example.com/api",
232
+ description: "API access",
233
+ mimeType: "application/json",
234
+ payTo: "0x" + "0".repeat(40),
235
+ assetId: USDC_BASE.contractAddress,
236
+ },
237
+ })
238
+ );
239
+
240
+ app.get("/api/data", (req, res) => {
241
+ res.json({ message: "Hello, paid user!", payment: req.payment });
242
+ });
243
+
244
+ app.listen(3000, () => {
245
+ console.log("Server running on http://localhost:3000");
246
+ });
247
+ `;
248
+ var SERVER_INDEX_HONO = `import { Hono } from "hono";
249
+ import { paymentMiddleware, createPaymentRequirements } from "@armory-sh/middleware-hono";
250
+ import { USDC_BASE } from "@armory-sh/base";
251
+
252
+ const app = new Hono();
253
+
254
+ const requirements = createPaymentRequirements({
255
+ payTo: "0x" + "0".repeat(40),
256
+ network: "base",
257
+ assetId: USDC_BASE.contractAddress,
258
+ amount: "2000000",
259
+ });
260
+
261
+ app.use("/api/*", paymentMiddleware({ requirements }));
262
+
263
+ app.get("/api/data", (c) => {
264
+ const payment = c.get("payment");
265
+ return c.json({ message: "Hello, paid user!", payment });
266
+ });
267
+
268
+ export default app;
269
+
270
+ // Start server
271
+ Bun.serve({
272
+ port: 3000,
273
+ fetch: app.fetch,
274
+ });
275
+
276
+ console.log("Server running on http://localhost:3000");
277
+ `;
278
+ var SERVER_INDEX_ELYSIA = `import { Elysia } from "elysia";
279
+ import { paymentMiddleware } from "@armory-sh/middleware-elysia";
280
+ import { USDC_BASE } from "@armory-sh/base";
121
281
 
122
- x402 Payment Protected Server
282
+ const app = new Elysia()
283
+ .use(
284
+ paymentMiddleware({
285
+ payTo: "0x" + "0".repeat(40),
286
+ network: "base",
287
+ token: USDC_BASE,
288
+ price: () => "2000000",
289
+ })
290
+ )
291
+ .get("/api/data", () => ({ message: "Hello, paid user!" }))
292
+ .listen(3000);
293
+
294
+ console.log("Server running on http://localhost:3000");
295
+ `;
296
+ var SERVER_INDEX_NEXT = `import { createMiddleware } from "@armory-sh/middleware-next";
297
+ import { USDC_BASE } from "@armory-sh/base";
298
+ import { NextResponse } from "next/server";
299
+
300
+ const paymentMiddleware = createMiddleware({
301
+ payTo: process.env.PAY_TO_ADDRESS ?? "0x" + "0".repeat(40),
302
+ network: "base",
303
+ token: USDC_BASE,
304
+ price: () => "2000000",
305
+ });
306
+
307
+ export function middleware(request: NextRequest) {
308
+ const response = paymentMiddleware(request);
309
+ if (response) return response;
310
+ return NextResponse.next();
311
+ }
312
+
313
+ export const config = {
314
+ matcher: "/api/:path*",
315
+ };
316
+ `;
317
+ var SERVER_README = (name, framework) => `# ${name}
318
+
319
+ x402 Payment Protected Server (${framework})
123
320
 
124
321
  ## Development
125
322
 
@@ -134,7 +331,19 @@ bun run dev
134
331
  curl http://localhost:3000/api/data
135
332
  \`\`\`
136
333
  `;
137
- var CLIENT_PACKAGE = (name) => `{
334
+ var CLIENT_PACKAGE = (name, library) => {
335
+ const deps = {
336
+ viem: `"@armory-sh/client-viem": "latest",
337
+ "@armory-sh/base": "latest",
338
+ "viem": "^2.0.0"`,
339
+ ethers: `"@armory-sh/client-ethers": "latest",
340
+ "@armory-sh/base": "latest",
341
+ "ethers": "^6.0.0"`,
342
+ web3: `"@armory-sh/client-web3": "latest",
343
+ "@armory-sh/base": "latest",
344
+ "web3": "^4.0.0"`
345
+ };
346
+ return `{
138
347
  "name": "${name}",
139
348
  "version": "0.1.0",
140
349
  "type": "module",
@@ -142,11 +351,11 @@ var CLIENT_PACKAGE = (name) => `{
142
351
  "dev": "bun run src/index.ts"
143
352
  },
144
353
  "dependencies": {
145
- "@armory-sh/client-viem": "latest",
146
- "@armory-sh/base": "latest"
354
+ ${deps[library]}
147
355
  }
148
356
  }`;
149
- var CLIENT_INDEX = `import { createX402Client } from "@armory-sh/client-viem";
357
+ };
358
+ var CLIENT_INDEX_VIEM = `import { createX402Client } from "@armory-sh/client-viem";
150
359
  import { privateKeyToAccount } from "viem/accounts";
151
360
  import { USDC_BASE } from "@armory-sh/base";
152
361
  import { client } from "./client.js";
@@ -167,7 +376,7 @@ async function main() {
167
376
 
168
377
  main().catch(console.error);
169
378
  `;
170
- var CLIENT_IMPL = `import type { X402Client } from "@armory-sh/client-viem";
379
+ var CLIENT_IMPL_VIEM = `import type { X402Client } from "@armory-sh/client-viem";
171
380
 
172
381
  export async function fetchData(client: X402Client) {
173
382
  const response = await client.fetch("http://localhost:3000/api/data");
@@ -176,9 +385,46 @@ export async function fetchData(client: X402Client) {
176
385
 
177
386
  export const client = { fetchData };
178
387
  `;
179
- var CLIENT_README = (name) => `# ${name}
388
+ var CLIENT_INDEX_ETHERS = `import { createX402Client } from "@armory-sh/client-ethers";
389
+ import { Wallet } from "ethers";
390
+ import { USDC_BASE } from "@armory-sh/base";
180
391
 
181
- x402-Enabled API Client
392
+ const wallet = new Wallet(process.env.PRIVATE_KEY ?? "0x" + "1".repeat(64));
393
+
394
+ const x402Client = createX402Client({
395
+ wallet: { type: "wallet", wallet },
396
+ token: USDC_BASE,
397
+ version: 2,
398
+ });
399
+
400
+ async function main() {
401
+ const response = await x402Client.fetch("http://localhost:3000/api/data");
402
+ const data = await response.json();
403
+ console.log("Response:", data);
404
+ }
405
+
406
+ main().catch(console.error);
407
+ `;
408
+ var CLIENT_INDEX_WEB3 = `import { createX402Client } from "@armory-sh/client-web3";
409
+ import { USDC_BASE } from "@armory-sh/base";
410
+
411
+ const x402Client = createX402Client({
412
+ wallet: { type: "privateKey", privateKey: process.env.PRIVATE_KEY ?? "0x" + "1".repeat(64) },
413
+ token: USDC_BASE,
414
+ version: 2,
415
+ });
416
+
417
+ async function main() {
418
+ const response = await x402Client.fetch("http://localhost:3000/api/data");
419
+ const data = await response.json();
420
+ console.log("Response:", data);
421
+ }
422
+
423
+ main().catch(console.error);
424
+ `;
425
+ var CLIENT_README = (name, library) => `# ${name}
426
+
427
+ x402-Enabled API Client (${library})
182
428
 
183
429
  ## Environment
184
430
 
@@ -193,52 +439,255 @@ bun install
193
439
  bun run dev
194
440
  \`\`\`
195
441
  `;
196
- async function main() {
197
- const args = process.argv.slice(2);
198
- if (args.length === 0) {
199
- console.log(`
200
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
201
- \u2551 Armory x402 CLI \u2551
202
- \u2551 \u2551
203
- \u2551 Create payment-enabled apps with x402 protocol \u2551
204
- \u2551 \u2551
205
- \u2551 Usage: \u2551
206
- \u2551 npx armory-cli create <template> [project-name] \u2551
207
- \u2551 \u2551
208
- \u2551 Templates: \u2551
209
- \u2551 facilitator - Payment verification & settlement server \u2551
210
- \u2551 server - Simple x402 server \u2551
211
- \u2551 client - x402 client wrapper \u2551
212
- \u2551 \u2551
213
- \u2551 Examples: \u2551
214
- \u2551 npx armory-cli create facilitator my-facilitator \u2551
215
- \u2551 npx armory-cli create server my-api \u2551
216
- \u2551 npx armory-cli create client my-app \u2551
217
- \u2551 \u2551
218
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
219
- `);
220
- process.exit(0);
442
+ function printHelp() {
443
+ console.log("Usage:");
444
+ console.log(" armory <command> [options]");
445
+ console.log("Templates:");
446
+ console.log(" bun-server, express-server, hono-server, elysia-server, next-server");
447
+ console.log(" viem-client, ethers-client, web3-client, facilitator");
448
+ console.log("Examples:");
449
+ console.log(" armory create bun-server my-api");
450
+ console.log(" armory networks");
451
+ console.log(" armory tokens base");
452
+ console.log(`
453
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
454
+ \u2551 Armory x402 CLI \u2551
455
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
456
+ \u2551 Create payment-enabled apps with the x402 protocol \u2551
457
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
458
+ \u2551 \u2551
459
+ \u2551 COMMANDS \u2551
460
+ \u2551 \u2551
461
+ \u2551 create <template> [name] Scaffold a new project \u2551
462
+ \u2551 networks List supported networks \u2551
463
+ \u2551 tokens [network] List tokens (optionally by network) \u2551
464
+ \u2551 validate network <value> Validate a network identifier \u2551
465
+ \u2551 validate token <value> Validate a token identifier \u2551
466
+ \u2551 extensions List available extensions \u2551
467
+ \u2551 verify <url> Check x402 headers on an endpoint \u2551
468
+ \u2551 \u2551
469
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
470
+ \u2551 TEMPLATES \u2551
471
+ \u2551 \u2551
472
+ \u2551 Servers: \u2551
473
+ \u2551 bun-server Bun server with middleware \u2551
474
+ \u2551 express-server Express v5 server \u2551
475
+ \u2551 hono-server Hono server (with extensions) \u2551
476
+ \u2551 elysia-server Elysia server \u2551
477
+ \u2551 next-server Next.js middleware \u2551
478
+ \u2551 \u2551
479
+ \u2551 Clients: \u2551
480
+ \u2551 viem-client Viem x402 client \u2551
481
+ \u2551 ethers-client Ethers.js v6 client \u2551
482
+ \u2551 web3-client Web3.js client \u2551
483
+ \u2551 \u2551
484
+ \u2551 Legacy: \u2551
485
+ \u2551 facilitator Payment verification server (deprecated) \u2551
486
+ \u2551 \u2551
487
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
488
+ \u2551 EXAMPLES \u2551
489
+ \u2551 \u2551
490
+ \u2551 armory create bun-server my-api \u2551
491
+ \u2551 armory create hono-server my-hono-api \u2551
492
+ \u2551 armory create viem-client my-client \u2551
493
+ \u2551 armory networks \u2551
494
+ \u2551 armory tokens base \u2551
495
+ \u2551 armory validate network 8453 \u2551
496
+ \u2551 armory validate token usdc \u2551
497
+ \u2551 armory verify https://api.example.com/data \u2551
498
+ \u2551 \u2551
499
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
500
+ `);
501
+ }
502
+ function formatNetworkTable(networks) {
503
+ console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
504
+ console.log("\u2502 Name \u2502 Chain ID \u2502 RPC URL \u2502");
505
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
506
+ for (const network of networks) {
507
+ const name = network.name.padEnd(19);
508
+ const chainId = String(network.chainId).padEnd(8);
509
+ const rpc = network.rpcUrl.substring(0, 34).padEnd(34);
510
+ console.log(`\u2502 ${name} \u2502 ${chainId} \u2502 ${rpc} \u2502`);
221
511
  }
222
- const [command, ...rest] = args;
223
- if (command !== "create") {
224
- console.error(`Unknown command: ${command}`);
512
+ console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
513
+ }
514
+ function formatTokenTable(tokens) {
515
+ console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
516
+ console.log("\u2502 Symbol \u2502 Name \u2502 Chain ID \u2502 Address \u2502");
517
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
518
+ for (const token of tokens) {
519
+ const symbol = token.symbol.padEnd(7);
520
+ const name = token.name.substring(0, 15).padEnd(15);
521
+ const chainId = String(token.chainId).padEnd(8);
522
+ const address = token.contractAddress.padEnd(40);
523
+ console.log(`\u2502 ${symbol} \u2502 ${name} \u2502 ${chainId} \u2502 ${address} \u2502`);
524
+ }
525
+ console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
526
+ }
527
+ function networksCommand(args) {
528
+ const showTestnets = args.includes("--testnet") || args.includes("-t");
529
+ const showMainnets = args.includes("--mainnet") || args.includes("-m");
530
+ if (showTestnets) {
531
+ console.log("\n\u{1F4E6} Testnet Networks:\n");
532
+ formatNetworkTable(getTestnets());
533
+ } else if (showMainnets) {
534
+ console.log("\n\u{1F310} Mainnet Networks:\n");
535
+ formatNetworkTable(getMainnets());
536
+ } else {
537
+ console.log("\n\u{1F310} Mainnet Networks:\n");
538
+ formatNetworkTable(getMainnets());
539
+ console.log("\u{1F4E6} Testnet Networks:\n");
540
+ formatNetworkTable(getTestnets());
541
+ }
542
+ }
543
+ function tokensCommand(args) {
544
+ const networkFilter = args.find((a) => !a.startsWith("-"));
545
+ if (networkFilter) {
546
+ const resolved = resolveNetwork(networkFilter);
547
+ if (!isResolvedNetwork(resolved)) {
548
+ console.error(`Unknown network: ${networkFilter}`);
549
+ console.log("Run 'armory networks' to see available networks");
550
+ process.exit(1);
551
+ }
552
+ const tokens = getTokensByChain(resolved.chainId);
553
+ if (tokens.length === 0) {
554
+ console.log(`No tokens configured for ${resolved.name}`);
555
+ } else {
556
+ console.log(`
557
+ \u{1F4B0} Tokens on ${resolved.name} (Chain ID: ${resolved.chainId}):
558
+ `);
559
+ formatTokenTable(tokens);
560
+ }
561
+ } else {
562
+ const tokens = getAllTokens();
563
+ console.log("\n\u{1F4B0} All Configured Tokens:\n");
564
+ formatTokenTable(tokens);
565
+ }
566
+ }
567
+ function validateCommand(args) {
568
+ const [type, value] = args;
569
+ if (!type || !value) {
570
+ console.error("Usage: armory validate <network|token> <value>");
571
+ process.exit(1);
572
+ }
573
+ if (type === "network") {
574
+ const resolved = resolveNetwork(value);
575
+ if (isResolvedNetwork(resolved)) {
576
+ console.log(`
577
+ \u2705 Valid network: ${resolved.name}`);
578
+ console.log(` Chain ID: ${resolved.chainId}`);
579
+ console.log(` CAIP-2: ${resolved.caip2Id}`);
580
+ console.log(` RPC: ${resolved.rpcUrl}
581
+ `);
582
+ } else {
583
+ console.error(`
584
+ \u274C Invalid network: ${value}`);
585
+ console.log("Run 'armory networks' to see available networks\n");
586
+ process.exit(1);
587
+ }
588
+ } else if (type === "token") {
589
+ const resolved = resolveToken(value);
590
+ if (isResolvedToken(resolved)) {
591
+ console.log(`
592
+ \u2705 Valid token: ${resolved.symbol}`);
593
+ console.log(` Name: ${resolved.name}`);
594
+ console.log(` Chain ID: ${resolved.chainId}`);
595
+ console.log(` Address: ${resolved.contractAddress}`);
596
+ console.log(` Decimals: ${resolved.decimals ?? 18}
597
+ `);
598
+ } else {
599
+ console.error(`
600
+ \u274C Invalid token: ${value}`);
601
+ console.log("Run 'armory tokens' to see available tokens\n");
602
+ process.exit(1);
603
+ }
604
+ } else {
605
+ console.error(`Unknown validation type: ${type}`);
606
+ console.log("Valid types: network, token");
607
+ process.exit(1);
608
+ }
609
+ }
610
+ function extensionsCommand() {
611
+ console.log("\n\u{1F50C} Available x402 Extensions:\n");
612
+ console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
613
+ console.log("\u2502 Extension \u2502 Description \u2502");
614
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
615
+ for (const ext of EXTENSIONS_INFO) {
616
+ const name = ext.name.padEnd(13);
617
+ const desc = ext.description.substring(0, 51).padEnd(51);
618
+ console.log(`\u2502 ${name} \u2502 ${desc} \u2502`);
619
+ }
620
+ console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
621
+ console.log("Usage:");
622
+ console.log(" import { createSIWxHook, createPaymentIdHook } from '@armory-sh/extensions';\n");
623
+ console.log("Extension Hooks:");
624
+ for (const ext of EXTENSIONS_INFO) {
625
+ console.log(` ${ext.name}: ${ext.hooks.slice(0, 2).join(", ")}`);
626
+ }
627
+ console.log("");
628
+ }
629
+ async function verifyCommand(args) {
630
+ const url = args[0];
631
+ if (!url) {
632
+ console.error("Usage: armory verify <url>");
633
+ process.exit(1);
634
+ }
635
+ console.log(`
636
+ \u{1F50D} Checking x402 headers on: ${url}
637
+ `);
638
+ try {
639
+ const response = await fetch(url);
640
+ const paymentRequired = response.headers.get("x-payment-required");
641
+ const paymentResponse = response.headers.get("x-payment-response");
642
+ console.log(`Status: ${response.status} ${response.statusText}`);
643
+ console.log(`x-payment-required: ${paymentRequired ?? "not set"}`);
644
+ console.log(`x-payment-response: ${paymentResponse ?? "not set"}`);
645
+ if (response.status === 402) {
646
+ console.log("\n\u2705 Endpoint requires payment (402)");
647
+ } else if (paymentRequired) {
648
+ console.log("\n\u26A0\uFE0F Payment header present but status is not 402");
649
+ } else {
650
+ console.log("\n\u2139\uFE0F No payment required for this endpoint");
651
+ }
652
+ } catch (error) {
653
+ console.error(`
654
+ \u274C Failed to fetch: ${error instanceof Error ? error.message : "Unknown error"}`);
225
655
  process.exit(1);
226
656
  }
657
+ }
658
+ async function createCommand(args) {
227
659
  const p = prompts;
228
- let template = rest[0];
229
- let projectName = rest[1];
660
+ let template = args[0];
661
+ let projectName = args[1];
662
+ const templates = [
663
+ "bun-server",
664
+ "express-server",
665
+ "hono-server",
666
+ "elysia-server",
667
+ "next-server",
668
+ "viem-client",
669
+ "ethers-client",
670
+ "web3-client",
671
+ "facilitator",
672
+ "server",
673
+ "client"
674
+ ];
230
675
  if (!template) {
231
676
  const result = await p.select({
232
677
  message: "What do you want to create?",
233
678
  choices: [
234
- { title: "Facilitator - Payment verification & settlement server", value: "facilitator" },
235
- { title: "Server - Simple x402 server", value: "server" },
236
- { title: "Client - x402-enabled fetch wrapper", value: "client" }
679
+ { title: "Bun Server - Simple x402 payment server", value: "bun-server" },
680
+ { title: "Express Server - Express v5 with x402 middleware", value: "express-server" },
681
+ { title: "Hono Server - Hono with extensions support", value: "hono-server" },
682
+ { title: "Elysia Server - Elysia/Bun x402 server", value: "elysia-server" },
683
+ { title: "Next.js Middleware - Next.js payment middleware", value: "next-server" },
684
+ { title: "Viem Client - x402 client with Viem", value: "viem-client" },
685
+ { title: "Ethers Client - x402 client with Ethers.js", value: "ethers-client" },
686
+ { title: "Web3 Client - x402 client with Web3.js", value: "web3-client" }
237
687
  ]
238
688
  });
239
689
  template = result;
240
690
  }
241
- const templates = ["facilitator", "server", "client"];
242
691
  if (!templates.includes(template)) {
243
692
  console.error(`Unknown template: ${template}`);
244
693
  console.log(`Available: ${templates.join(", ")}`);
@@ -247,7 +696,7 @@ async function main() {
247
696
  if (!projectName) {
248
697
  projectName = await p.text({
249
698
  message: "Project name?",
250
- default: `my-${template}`,
699
+ default: `my-${template.replace("-server", "").replace("-client", "")}`,
251
700
  validate: (n) => /^[a-z0-9-]+$/.test(n) || "Use lowercase, numbers, dashes only"
252
701
  });
253
702
  }
@@ -273,6 +722,42 @@ Creating ${template} in ${projectPath}...
273
722
  console.log(` bun run dev
274
723
  `);
275
724
  }
725
+ async function main() {
726
+ const args = process.argv.slice(2);
727
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
728
+ printHelp();
729
+ process.exit(0);
730
+ }
731
+ const [command, ...rest] = args;
732
+ switch (command) {
733
+ case "create":
734
+ await createCommand(rest);
735
+ break;
736
+ case "networks":
737
+ case "network":
738
+ networksCommand(rest);
739
+ break;
740
+ case "tokens":
741
+ case "token":
742
+ tokensCommand(rest);
743
+ break;
744
+ case "validate":
745
+ validateCommand(rest);
746
+ break;
747
+ case "extensions":
748
+ case "ext":
749
+ extensionsCommand();
750
+ break;
751
+ case "verify":
752
+ case "inspect":
753
+ await verifyCommand(rest);
754
+ break;
755
+ default:
756
+ console.error(`Unknown command: ${command}`);
757
+ console.log("Run 'armory --help' for usage");
758
+ process.exit(1);
759
+ }
760
+ }
276
761
  main().catch((err) => {
277
762
  console.error("Error:", err.message);
278
763
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "armory-cli",
3
- "version": "0.2.10",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold x402 payment-enabled apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,8 @@
39
39
  "test": "bun test"
40
40
  },
41
41
  "dependencies": {
42
+ "@armory-sh/base": "0.2.21",
43
+ "@armory-sh/extensions": "0.1.2",
42
44
  "prompts": "2.4.2"
43
45
  },
44
46
  "devDependencies": {