@seedhape/x402-merchant-sdk 0.5.2 → 0.5.3

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 CHANGED
@@ -3,14 +3,18 @@
3
3
  ## One catalog for agent commerce
4
4
 
5
5
  Use `createMerchantCommerce` to define products once. The same product
6
- definitions can feed the SDK's current x402 paywall and future ACP/UCP/MPP
7
- adapters; merchants do not maintain protocol-specific catalogs.
6
+ definitions feed the SDK's x402 paywall, ACP/UCP catalog previews, and MPP
7
+ metadata; merchants do not maintain protocol-specific catalogs.
8
+
9
+ Set `kind` to tell fulfillment-aware clients what the buyer receives:
10
+ `api` (default), `digital_download`, `license`, or `subscription`. Payment
11
+ selection remains independent of this value.
8
12
 
9
13
  ```ts
10
14
  const commerce = createMerchantCommerce({
11
15
  paywall: { payTo, ...base, facilitator: base.facilitator },
12
16
  products: [{
13
- id: "weather", name: "Weather API", description: "Current weather",
17
+ id: "weather", kind: "api", name: "Weather API", description: "Current weather",
14
18
  endpoint: "/weather",
15
19
  price: { amount: 50_000n, ...base },
16
20
  }],
@@ -20,10 +24,290 @@ commerce.catalog(); // publish to your agent-facing catalog
20
24
  commerce.paywallFor("weather"); // pass to existing paywall middleware
21
25
  ```
22
26
 
27
+ Each catalog entry includes `paymentMethods`, such as `['x402']` or
28
+ `['x402', 'mpp']`, based on the server-side configuration. Agents can use
29
+ that metadata to select a compatible route; merchants still define one
30
+ product and keep all payment credentials on the server.
31
+
32
+ The seller facade also exposes protocol-shaped discovery previews from the
33
+ same fixed-price products:
34
+
35
+ ```ts
36
+ const ucpProducts = seller.ucpCatalog("https://api.example.com");
37
+ const acpCart = seller.acpCart(
38
+ [{ id: "weather", quantity: 1 }],
39
+ "https://api.example.com",
40
+ );
41
+ ```
42
+
43
+ These helpers serialize catalog and cart data only. For UCP HTTP discovery and
44
+ checkout, use the fetch-compatible `seller.ucpHandler` below; ACP cart
45
+ serialization remains host-owned and does not create a payment.
46
+
23
47
  The helper normalizes discovery metadata and product lookup. Fulfillment and
24
48
  refunds remain merchant-owned and should use the purchase ID as their durable
25
49
  idempotency key.
26
50
 
51
+ ### UCP REST adapter (preview)
52
+
53
+ For the simplest UCP integration, mount the SDK's fetch-compatible handler at
54
+ your public app origin. It serves the UCP profile, catalog search and lookup,
55
+ and checkout-session endpoints from the same product list:
56
+
57
+ ```ts
58
+ import { base, createSeedhapeSeller } from "@seedhape/x402-merchant-sdk";
59
+
60
+ const seller = createSeedhapeSeller({
61
+ products: [{
62
+ id: "weather",
63
+ name: "Weather API",
64
+ description: "Current weather data",
65
+ endpoint: "/weather",
66
+ price: "0.50",
67
+ fulfill: async ({ request }) => fetch("https://api.example.com/weather", {
68
+ headers: { authorization: request.headers.get("authorization") ?? "" },
69
+ }),
70
+ }],
71
+ x402: {
72
+ payTo: process.env.MERCHANT_WALLET!,
73
+ network: base.network,
74
+ asset: base.asset,
75
+ decimals: base.decimals,
76
+ facilitator: base.facilitator,
77
+ },
78
+ });
79
+
80
+ const ucp = seller.ucpHandler({
81
+ origin: process.env.PUBLIC_ORIGIN!, // e.g. https://merchant.example
82
+ currency: "USD",
83
+ });
84
+
85
+ // Pass every request to `ucp(request)` from your framework route. The handler
86
+ // recognizes /.well-known/ucp and /api/ucp/*.
87
+ export const GET = ucp;
88
+ export const POST = ucp;
89
+ ```
90
+
91
+ Use a stable HTTPS `PUBLIC_ORIGIN`; agents use `/.well-known/ucp` to discover
92
+ the service and send `UCP-Agent`, `Request-Id`, and (for checkout mutations)
93
+ `Idempotency-Key` headers. Checkout completion accepts either an MPP credential
94
+ or an x402 payment signature and reuses the seller's existing payment path.
95
+ Keep wallet, Stripe, facilitator, and MPP secrets on the server. The preview
96
+ adapter keeps checkout sessions in memory, so replace that session store with a
97
+ durable database before running multiple production instances.
98
+
99
+ ### Fulfillment responses
100
+
101
+ The SDK does not store merchant files or expose an unprotected download path.
102
+ After payment, a merchant can return a signed URL, license, or webhook
103
+ reference using the protocol-neutral helpers:
104
+
105
+ ```ts
106
+ import { createDownloadDelivery, createSeedhapeSeller } from "@seedhape/x402-merchant-sdk";
107
+
108
+ const seller = createSeedhapeSeller({
109
+ products: [{
110
+ id: "report",
111
+ name: "Research report",
112
+ description: "One PDF report",
113
+ endpoint: "/report",
114
+ price: "5.00",
115
+ fulfill: async ({ orderId }) => createDownloadDelivery({
116
+ downloadUrl: await issueShortLivedDownloadUrl(orderId),
117
+ filename: "research-report.pdf",
118
+ maxDownloads: 1,
119
+ }),
120
+ }],
121
+ });
122
+ ```
123
+
124
+ For a Seedhape-hosted download route, use a grant containing only opaque
125
+ merchant identifiers. Keep the signing secret on the server and resolve the
126
+ asset through private storage after verification:
127
+
128
+ ```ts
129
+ import { createDownloadGrant, verifyDownloadGrant } from "@seedhape/x402-merchant-sdk";
130
+
131
+ const { token } = await createDownloadGrant({
132
+ secret: process.env.DOWNLOAD_GRANT_SECRET!,
133
+ assetId: "asset_123",
134
+ orderId: "order_123",
135
+ productId: "report",
136
+ maxDownloads: 1,
137
+ });
138
+
139
+ // GET /downloads/:token
140
+ const grant = await verifyDownloadGrant(token, process.env.DOWNLOAD_GRANT_SECRET!);
141
+ // Look up grant.assetId in private storage, then stream the file.
142
+ ```
143
+
144
+ For a small hosted route, `createDownloadHandler` supplies the HTTP boundary
145
+ around that lookup. Use an atomic `consumeGrant` implementation when enforcing
146
+ download limits:
147
+
148
+ ```ts
149
+ const download = createDownloadHandler({
150
+ secret: process.env.DOWNLOAD_GRANT_SECRET!,
151
+ consumeGrant: (grant) => orders.consumeDownload(grant.orderId, grant.assetId),
152
+ resolveAsset: (grant) => storage.readPrivateAsset(grant.assetId),
153
+ });
154
+ ```
155
+
156
+ The handler rejects invalid or expired grants, limits methods to GET and HEAD,
157
+ sets private download headers, and never exposes the storage key.
158
+
159
+ For a small Node deployment, the SDK also includes a traversal-safe local file
160
+ resolver:
161
+
162
+ ```ts
163
+ const resolveAsset = createLocalDownloadResolver({
164
+ rootDir: "/srv/private-assets",
165
+ assets: {
166
+ report: {
167
+ relativePath: "reports/report.pdf",
168
+ filename: "research-report.pdf",
169
+ contentType: "application/pdf",
170
+ },
171
+ },
172
+ });
173
+
174
+ const download = createDownloadHandler({
175
+ secret: process.env.DOWNLOAD_GRANT_SECRET!,
176
+ resolveAsset,
177
+ });
178
+ ```
179
+
180
+ The resolver rejects absolute paths, `..` escapes, symlink escapes, and
181
+ directories. Use an object-storage resolver with the same interface when the
182
+ merchant needs a multi-instance deployment.
183
+
184
+ For S3, R2, or another object store, resolve a short-lived presigned URL only
185
+ after the grant is verified:
186
+
187
+ ```ts
188
+ const resolveAsset = createRemoteDownloadResolver({
189
+ getAsset: async (grant) => ({
190
+ url: await storage.createPresignedGetUrl(grant.assetId),
191
+ filename: "research-report.pdf",
192
+ contentType: "application/pdf",
193
+ }),
194
+ maxBytes: 50_000_000,
195
+ });
196
+
197
+ const download = createDownloadHandler({
198
+ secret: process.env.DOWNLOAD_GRANT_SECRET!,
199
+ resolveAsset,
200
+ });
201
+ ```
202
+
203
+ The resolver validates HTTPS and outbound network policy, blocks redirects,
204
+ enforces a response-size limit, and never returns the presigned URL to the
205
+ buyer.
206
+
207
+ Use `createFulfillmentRouter` to keep product delivery selection in one place:
208
+
209
+ ```ts
210
+ const fulfill = createFulfillmentRouter({
211
+ api: ({ request }) => proxy(request as Request),
212
+ digital_download: ({ orderId }) => issueDownload(orderId),
213
+ license: ({ orderId }) => issueLicense(orderId),
214
+ subscription: ({ orderId }) => activateSubscription(orderId),
215
+ });
216
+
217
+ // Each product can use the same callback:
218
+ const seller = createSeedhapeSeller({
219
+ products: products.map((product) => ({ ...product, fulfill })),
220
+ // payment configuration...
221
+ });
222
+ ```
223
+
224
+ ### Paid API proxy
225
+
226
+ Merchants can keep an API on their own infrastructure while exposing a
227
+ Seedhape-controlled payment URL. Wrap the proxy in the seller fulfillment
228
+ callback so payment is verified before the upstream request is made:
229
+
230
+ ```ts
231
+ import { createApiProxyHandler, createSeedhapeSeller } from "@seedhape/x402-merchant-sdk";
232
+
233
+ const proxy = createApiProxyHandler({
234
+ upstream: "https://api.merchant.example/v1/quote",
235
+ allowedHosts: ["api.merchant.example"],
236
+ upstreamHeaders: { authorization: `Bearer ${process.env.UPSTREAM_API_KEY!}` },
237
+ });
238
+
239
+ const seller = createSeedhapeSeller({
240
+ products: [{
241
+ id: "quote",
242
+ name: "Price quote",
243
+ description: "One quote request",
244
+ endpoint: "/quote",
245
+ price: "0.01",
246
+ fulfill: ({ request }) => proxy(request as Request),
247
+ }],
248
+ x402: { /* merchant wallet, Base USDC, and facilitator */ },
249
+ });
250
+ ```
251
+
252
+ The proxy only forwards a small request-header allowlist, adds merchant-owned
253
+ upstream headers server-side, rejects non-HTTPS or non-allowlisted origins,
254
+ blocks redirects, applies a timeout and response-size limit, and strips
255
+ cookies and payment headers from the upstream request.
256
+
257
+ ### License and subscription fulfillment
258
+
259
+ Use signed webhooks when the merchant owns a licensing or entitlement system:
260
+
261
+ ```ts
262
+ await sendFulfillmentWebhook({
263
+ url: process.env.MERCHANT_FULFILLMENT_URL!,
264
+ secret: process.env.MERCHANT_FULFILLMENT_SECRET!,
265
+ event: {
266
+ id: `license_${orderId}`,
267
+ type: "license.issue",
268
+ orderId,
269
+ productId: "pro-plan",
270
+ paymentReference,
271
+ },
272
+ });
273
+ ```
274
+
275
+ Events include an idempotency key and an HMAC signature over the timestamp and
276
+ request body. Supported event types cover license issuance/revocation and
277
+ subscription activation, renewal, and cancellation. The receiving merchant
278
+ system remains responsible for creating keys and changing entitlements.
279
+
280
+ On the receiving side, verify the signature before parsing or applying the
281
+ event:
282
+
283
+ ```ts
284
+ const valid = verifyFulfillmentWebhookSignature({
285
+ secret: process.env.MERCHANT_FULFILLMENT_SECRET!,
286
+ timestamp: request.headers.get("x-seedhape-timestamp") ?? "",
287
+ signature: request.headers.get("x-seedhape-signature") ?? "",
288
+ body: await request.text(),
289
+ });
290
+ if (!valid) return new Response("Invalid webhook", { status: 401 });
291
+ const event = parseFulfillmentWebhookEvent(body);
292
+ // Deduplicate event.id, then issue or revoke the license.
293
+ ```
294
+
295
+ Use `sendFulfillmentWebhookWithRetry` from a queue worker when the merchant
296
+ endpoint may be temporarily unavailable. Every attempt reuses the same event
297
+ ID, so the merchant can safely deduplicate it:
298
+
299
+ ```ts
300
+ await sendFulfillmentWebhookWithRetry(input, {
301
+ attempts: 5,
302
+ baseDelayMs: 500,
303
+ maxDelayMs: 10_000,
304
+ });
305
+ ```
306
+
307
+ The URL must be an HTTPS URL controlled by the merchant and should be
308
+ short-lived. Storage, file access, license issuance, retries, and revocation
309
+ remain merchant-owned until a managed Seedhape fulfillment service is enabled.
310
+
27
311
  ## Unified seller facade (preview)
28
312
 
29
313
  `createSeedhapeSeller` is the single-drop-in entry point for a server-side
@@ -61,6 +345,29 @@ const seller = createSeedhapeSeller({
61
345
  app.all("/weather", seller.route("weather"));
62
346
  ```
63
347
 
348
+ To enable signed agent evidence, pass `trust` with the merchant identity,
349
+ controller resolver, and signature reader. The Express adapter validates the
350
+ buyer's signed request before the x402/MPP handler can settle, then signs the
351
+ bounded final fulfillment response. For non-empty request bodies, install a
352
+ raw-body parser before the route so verification uses the transmitted bytes:
353
+
354
+ ```ts
355
+ app.post(
356
+ "/weather",
357
+ express.raw({ type: "*/*", limit: "1mb" }),
358
+ seller.route("weather"),
359
+ );
360
+ ```
361
+
362
+ When TLS terminates at a trusted reverse proxy, configure Express `trust proxy`
363
+ so the route reconstructs the same public HTTPS URL that the buyer signed.
364
+
365
+ `express.raw` makes the exact body bytes available as `req.body`. JSON parsers
366
+ must use their `verify` callback to preserve a `req.rawBody` copy. If the exact
367
+ body is unavailable or exceeds the configured limit, the route returns 400
368
+ before payment. The fetch adapter works directly with Hono's `app.all(path,
369
+ c => seller.fetch("weather")(c.req.raw))` pattern.
370
+
64
371
  The facade preserves the existing x402 Bazaar discovery challenge. It selects
65
372
  Stripe MPP when a client advertises the MPP payment scheme and selects x402
66
373
  otherwise. ACP/UCP checkout remains a separate integration gate; it will
@@ -75,13 +382,39 @@ Add an x402 payment gate to an existing Express, Hono, or `node:http` service. T
75
382
  npm install @seedhape/x402-merchant-sdk
76
383
  ```
77
384
 
385
+ ## Start a merchant service
386
+
387
+ Run the initializer in your service directory. It writes an Express paywall
388
+ configuration, a product metadata file, and a private ES256 signing key:
389
+
390
+ ```bash
391
+ npx @seedhape/x402-merchant-sdk init
392
+ # Or pass the payout address and use the defaults for price, route, and name:
393
+ npx @seedhape/x402-merchant-sdk init --pay-to 0xYourBaseWallet --price 0.05 --route /api/weather --name "Weather API"
394
+ npm install @seedhape/x402-merchant-sdk express
395
+ ```
396
+
397
+ Mount the generated middleware with `app.use(paywall)` and keep the generated
398
+ `.seedhape/merchant-keys.json` file private. The initializer adds that key file
399
+ to `.gitignore`. Set `PUBLIC_ORIGIN` to the service's HTTPS origin when issuing
400
+ AP2 checkout tokens. Rotate and inspect public keys with:
401
+
402
+ ```bash
403
+ npx @seedhape/x402-merchant-sdk keys:generate
404
+ npx @seedhape/x402-merchant-sdk keys:rotate
405
+ npx @seedhape/x402-merchant-sdk keys:jwks
406
+ ```
407
+
408
+ `keys:rotate` activates a new ES256 key while retaining earlier public keys in
409
+ the JWKS output so existing signed checkout tokens can still be verified.
410
+
78
411
  ## Stripe machine payments (MPP)
79
412
 
80
- Install the optional MPP runtime when you want agents to pay with Stripe cards
81
- or Shared Payment Tokens:
413
+ Install the optional MPP runtime and Stripe server client when you want agents
414
+ to pay with Stripe cards or Shared Payment Tokens:
82
415
 
83
416
  ```bash
84
- npm install mppx
417
+ npm install mppx stripe
85
418
  ```
86
419
 
87
420
  Create one Stripe MPP paywall per seller endpoint. The seller keeps the Stripe
@@ -107,11 +440,41 @@ export async function weather(request: Request) {
107
440
  }
108
441
  ```
109
442
 
443
+ For stablecoin MPP, create a Stripe client from the same merchant-owned key and
444
+ provide the merchant’s Stripe-managed deposit address:
445
+
446
+ ```ts
447
+ import Stripe from "stripe";
448
+ import { createStripeMppPaywall } from "@seedhape/x402-merchant-sdk";
449
+
450
+ const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!);
451
+ const mpp = await createStripeMppPaywall({
452
+ stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
453
+ stripeClient,
454
+ profileId: process.env.STRIPE_PROFILE_ID!,
455
+ mppSecretKey: process.env.MPP_SECRET_KEY!,
456
+ amount: "0.50",
457
+ depositAddresses: { tempo: process.env.TEMPO_DEPOSIT_ADDRESS! },
458
+ });
459
+ ```
460
+
110
461
  An unpaid agent receives HTTP `402`; a valid credential is verified and
111
462
  settled before the fulfillment callback runs. For a Stripe Connect direct
112
463
  charge, pass `connect: { stripeAccount: "acct_..." }`. To enable Stripe's
113
464
  stablecoin MPP rail as well, supply a Stripe SDK client and a test Tempo deposit
114
- address. Use `profile_test_...` and `sk_test_...` in a Stripe Sandbox.
465
+ address. The adapter rejects a deposit address without that client and does not
466
+ combine stablecoin deposits with a Connect direct charge. Use
467
+ `profile_test_...` and `sk_test_...` in a Stripe Sandbox.
468
+
469
+ ### Stripe account ownership
470
+
471
+ The SDK is designed for a merchant-owned Stripe account (Standard/direct mode):
472
+ `stripeSecretKey` and `profileId` must belong to the same merchant account. Do
473
+ not pass Seedhape's platform key or the web app's Express connected-account ID
474
+ to an SDK integration. If a platform uses OAuth, give the SDK the merchant's
475
+ OAuth access token in place of the secret key and leave `connect` unset. The
476
+ `connect` option remains available for platform-managed direct charges, but it
477
+ is separate from the default merchant-owned SDK setup.
115
478
 
116
479
  ## Express
117
480
 
@@ -162,7 +525,7 @@ app.use(expressPaywall(merchantConfig(baseSepolia, {
162
525
  })));
163
526
  ```
164
527
 
165
- `paymentMethods` defaults to `['eip3009']`, the broadly supported payment method. Set `['eip3009', 'erc7710']` to let the buyer/client choose both methods. Only advertise ERC-7710 when the configured facilitator supports it. If the methods use different facilitators, configure `facilitators: { eip3009: {...}, erc7710: {...} }`; the SDK selects the facilitator matching the buyer's selected method.
528
+ `paymentMethods` defaults to `['eip3009']`, which every x402 facilitator settles. Add `'erc7710'` only when your facilitator settles ERC-7710 delegations (the MetaMask facilitator does, and `init` sets it up that way); buyers such as Seedhape prefer ERC-7710 whenever it is offered, so advertising it without a facilitator that settles it makes payments fail. If the methods use different facilitators, configure `facilitators: { eip3009: {...}, erc7710: {...} }`; the SDK selects the facilitator matching the buyer's selected method.
166
529
 
167
530
  ### Base and Solana on one route
168
531
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ import { generateKeyPairSync, randomUUID } from "node:crypto";
3
+ import { mkdir, open, readFile, rename, writeFile } from "node:fs/promises";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { stdin, stdout } from "node:process";
6
+ import { dirname, resolve } from "node:path";
7
+ const configPath = resolve(process.cwd(), "seedhape.config.json");
8
+ const keyPath = resolve(process.cwd(), ".seedhape/merchant-keys.json");
9
+ const USAGE = `Usage: x402-merchant-sdk <command>
10
+
11
+ init [--pay-to 0x…] [--price 0.05] [--route /api/weather] [--name "Weather API"]
12
+ write seedhape.config.mjs and seedhape.config.json (reuses existing keys)
13
+ keys:generate add a signing key (creates the key file if there is none)
14
+ keys:rotate switch to a new signing key; old public keys stay in the JWKS
15
+ keys:jwks print the public keys to serve at your JWKS URL`;
16
+ function flag(args, name) {
17
+ const index = args.indexOf(name);
18
+ return index >= 0 ? args[index + 1] : undefined;
19
+ }
20
+ function createMerchantKey() {
21
+ const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
22
+ const kid = randomUUID();
23
+ return {
24
+ kid,
25
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
26
+ publicJwk: { ...publicKey.export({ format: "jwk" }), kid, alg: "ES256", use: "sig" },
27
+ };
28
+ }
29
+ async function saveKeyFile(file, exclusive = false) {
30
+ await mkdir(dirname(keyPath), { recursive: true, mode: 0o700 });
31
+ const temporaryPath = `${keyPath}.${process.pid}.tmp`;
32
+ await writeFile(temporaryPath, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
33
+ if (exclusive) {
34
+ try {
35
+ const target = await open(keyPath, "wx", 0o600);
36
+ try {
37
+ await target.writeFile(await readFile(temporaryPath));
38
+ }
39
+ finally {
40
+ await target.close();
41
+ }
42
+ const { unlink } = await import("node:fs/promises");
43
+ await unlink(temporaryPath);
44
+ }
45
+ catch (error) {
46
+ const { unlink } = await import("node:fs/promises");
47
+ await unlink(temporaryPath).catch(() => undefined);
48
+ throw error;
49
+ }
50
+ }
51
+ else {
52
+ await rename(temporaryPath, keyPath);
53
+ }
54
+ }
55
+ async function loadKeyFile() {
56
+ const value = JSON.parse(await readFile(keyPath, "utf8"));
57
+ if (!value.activeKid || !Array.isArray(value.keys) || !value.keys.some((key) => key.kid === value.activeKid)) {
58
+ throw new Error(`Invalid key file: ${keyPath}`);
59
+ }
60
+ return value;
61
+ }
62
+ async function init(args) {
63
+ const rl = createInterface({ input: stdin, output: stdout });
64
+ try {
65
+ const payTo = flag(args, "--pay-to") ?? (stdin.isTTY ? await rl.question("Base USDC payout address: ") : "");
66
+ if (!/^0x(?!0{40}$)[0-9a-fA-F]{40}$/.test(payTo.trim()))
67
+ throw new Error("Pass a valid --pay-to Ethereum address (or run init in a terminal to enter it).");
68
+ const price = flag(args, "--price") ?? ((stdin.isTTY ? await rl.question("Price in USDC [0.05]: ") : "") || "0.05");
69
+ if (!/^\d+(?:\.\d{1,6})?$/.test(price) || Number(price) <= 0)
70
+ throw new Error("--price must be a positive USDC amount with at most six decimals.");
71
+ const routePath = flag(args, "--route") ?? ((stdin.isTTY ? await rl.question("Paid route [/api/weather]: ") : "") || "/api/weather");
72
+ if (!routePath.startsWith("/") || routePath.startsWith("//"))
73
+ throw new Error("--route must be an application path starting with one slash.");
74
+ const name = flag(args, "--name") ?? ((stdin.isTTY ? await rl.question("Service name [Weather API]: ") : "") || "Weather API");
75
+ const configModulePath = resolve(process.cwd(), "seedhape.config.mjs");
76
+ const exists = async (path) => {
77
+ try {
78
+ await readFile(path);
79
+ return true;
80
+ }
81
+ catch (error) {
82
+ if (error.code === "ENOENT")
83
+ return false;
84
+ throw error;
85
+ }
86
+ };
87
+ const existingConfig = (await Promise.all([configPath, configModulePath].map(async (path) => (await exists(path)) ? path : null))).filter(Boolean);
88
+ if (existingConfig.length)
89
+ throw new Error(`Refusing to overwrite ${existingConfig.join(" and ")}.`);
90
+ // Reuse keys made earlier with keys:generate; create one only if there is none.
91
+ const createdKey = !(await exists(keyPath));
92
+ let key;
93
+ if (createdKey) {
94
+ key = createMerchantKey();
95
+ await saveKeyFile({ activeKid: key.kid, keys: [key] }, true);
96
+ }
97
+ else {
98
+ const file = await loadKeyFile();
99
+ key = file.keys.find((item) => item.kid === file.activeKid);
100
+ if (!key.privateKeyPem)
101
+ throw new Error(`The active key in ${keyPath} has no private key. Run keys:rotate.`);
102
+ }
103
+ const config = {
104
+ network: "eip155:8453",
105
+ asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
106
+ decimals: 6,
107
+ payTo: payTo.trim(),
108
+ price,
109
+ route: { path: routePath, name, description: `${name} paid API`, inputSchema: { type: "object", properties: {} } },
110
+ facilitatorUrl: "https://tx-sentinel-base-mainnet.api.cx.metamask.io/platform/v2/x402",
111
+ keyFile: ".seedhape/merchant-keys.json",
112
+ };
113
+ const moduleSource = `import { readFileSync } from "node:fs";\nimport { base, expressPaywall, merchantConfig } from "@seedhape/x402-merchant-sdk";\n\nconst merchant = JSON.parse(readFileSync(new URL("./seedhape.config.json", import.meta.url), "utf8"));\nconst keyFile = JSON.parse(readFileSync(new URL(merchant.keyFile, import.meta.url), "utf8"));\nconst activeKey = keyFile.keys.find((key) => key.kid === keyFile.activeKid);\nif (!activeKey?.privateKeyPem) throw new Error("No active Seedhape merchant key. Run keys:rotate.");\nif (!process.env.PUBLIC_ORIGIN) throw new Error("Set PUBLIC_ORIGIN to your service HTTPS origin.");\nconst toBaseUnits = (amount) => {\n const [whole, fraction = ""] = String(amount).split(".");\n return BigInt(whole) * 10n ** BigInt(base.decimals) + BigInt((fraction + "0".repeat(base.decimals)).slice(0, base.decimals));\n};\n\nexport const config = merchantConfig(base, {\n payTo: merchant.payTo,\n paymentMethods: ["eip3009", "erc7710"],\n price: ({ path }) => path === merchant.route.path\n ? { amount: toBaseUnits(merchant.price), ...base }\n : null,\n route: merchant.route,\n facilitator: { url: process.env.FACILITATOR_URL ?? merchant.facilitatorUrl },\n ap2: { issuer: process.env.PUBLIC_ORIGIN, privateKeyPem: activeKey.privateKeyPem, keyId: activeKey.kid },\n});\n\nexport const paywall = expressPaywall(config);\n`;
114
+ try {
115
+ await writeFile(configModulePath, moduleSource, { flag: "wx", mode: 0o644 });
116
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { flag: "wx", mode: 0o644 });
117
+ }
118
+ catch (error) {
119
+ const { unlink } = await import("node:fs/promises");
120
+ await unlink(configModulePath).catch(() => undefined);
121
+ if (createdKey)
122
+ await unlink(keyPath).catch(() => undefined);
123
+ throw error;
124
+ }
125
+ console.log(createdKey ? `Created ${configModulePath}, ${configPath}, and ${keyPath}.` : `Created ${configModulePath} and ${configPath}, using the existing keys in ${keyPath}.`);
126
+ console.log(`The active ES256 key is ${key.kid}. Keep ${keyPath} private and serve its public keys at your JWKS URL.`);
127
+ console.log("Install @seedhape/x402-merchant-sdk and express, mount the generated middleware with app.use(paywall), and set PUBLIC_ORIGIN before issuing AP2 checkout tokens.");
128
+ }
129
+ finally {
130
+ rl.close();
131
+ }
132
+ }
133
+ async function generateKey(rotate) {
134
+ let file;
135
+ try {
136
+ file = await loadKeyFile();
137
+ }
138
+ catch (error) {
139
+ if (!rotate && error.code === "ENOENT") {
140
+ const key = createMerchantKey();
141
+ await saveKeyFile({ activeKid: key.kid, keys: [key] }, true);
142
+ console.log(`Created ES256 key ${key.kid} in ${keyPath}.`);
143
+ return;
144
+ }
145
+ throw new Error(`Could not read ${keyPath}. Run init or keys:generate first.`, { cause: error });
146
+ }
147
+ const key = createMerchantKey();
148
+ file.keys = rotate
149
+ ? [...file.keys.map(({ kid, publicJwk }) => ({ kid, publicJwk })), key]
150
+ : [...file.keys, key];
151
+ if (rotate)
152
+ file.activeKid = key.kid;
153
+ await saveKeyFile(file);
154
+ console.log(`${rotate ? "Rotated to" : "Generated"} ES256 key ${key.kid}. Previous public keys remain in JWKS for validating existing tokens.`);
155
+ }
156
+ async function printJwks() {
157
+ const file = await loadKeyFile();
158
+ console.log(JSON.stringify({ keys: file.keys.map(({ publicJwk }) => publicJwk) }, null, 2));
159
+ }
160
+ async function main() {
161
+ const [command, ...args] = process.argv.slice(2);
162
+ if (!command || ["help", "--help", "-h"].includes(command)) {
163
+ console.log(USAGE);
164
+ return;
165
+ }
166
+ if (command === "init")
167
+ return init(args);
168
+ if (command === "keys:generate")
169
+ return generateKey(false);
170
+ if (command === "keys:rotate")
171
+ return generateKey(true);
172
+ if (command === "keys:jwks")
173
+ return printJwks();
174
+ throw new Error(`Unknown command "${command}".\n${USAGE}`);
175
+ }
176
+ main().catch((error) => {
177
+ console.error(error instanceof Error ? error.message : String(error));
178
+ process.exitCode = 1;
179
+ });
180
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import type { Money, PaywallConfig, RouteMetadata } from "./middleware.js";
2
2
  /** A merchant's protocol-neutral, agent-readable product definition. */
3
3
  export interface MerchantProduct {
4
4
  id: string;
5
+ kind?: "api" | "digital_download" | "license" | "subscription";
5
6
  name: string;
6
7
  description: string;
7
8
  price: Money;
@@ -23,6 +24,7 @@ export interface MerchantCatalogProduct extends Omit<MerchantProduct, "price"> {
23
24
  decimals: number;
24
25
  };
25
26
  capabilities: readonly ["quote", "purchase", "receipt"];
27
+ paymentMethods: readonly ["x402"];
26
28
  }
27
29
  /**
28
30
  * One merchant integration surface for catalogs and paid routes. Protocol
@@ -1 +1 @@
1
- {"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IAC5E,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,YAAY,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB;eAKtD,sBAAsB,EAAE;gBAOvB,MAAM,GAAG,eAAe,GAAG,IAAI;mBAG5B,MAAM,GAAG,aAAa;EAWxC"}
1
+ {"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,KAAK,GAAG,kBAAkB,GAAG,SAAS,GAAG,cAAc,CAAC;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IAC5E,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,YAAY,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB;eAKtD,sBAAsB,EAAE;gBAQvB,MAAM,GAAG,eAAe,GAAG,IAAI;mBAG5B,MAAM,GAAG,aAAa;EAWxC"}
package/dist/commerce.js CHANGED
@@ -14,6 +14,7 @@ export function createMerchantCommerce(config) {
14
14
  ...product,
15
15
  price: { ...product.price, amount: product.price.amount.toString() },
16
16
  capabilities: ["quote", "purchase", "receipt"],
17
+ paymentMethods: ["x402"],
17
18
  }));
18
19
  },
19
20
  product(id) {