armory-cli 0.2.11 → 0.3.1-alpha.3.13

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