@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 +371 -8
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +180 -0
- package/dist/cli.js.map +1 -0
- package/dist/commerce.d.ts +2 -0
- package/dist/commerce.d.ts.map +1 -1
- package/dist/commerce.js +1 -0
- package/dist/commerce.js.map +1 -1
- package/dist/fulfillment.d.ts +153 -0
- package/dist/fulfillment.d.ts.map +1 -0
- package/dist/fulfillment.js +369 -0
- package/dist/fulfillment.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +3 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +7 -3
- package/dist/middleware.js.map +1 -1
- package/dist/protocol-catalog.d.ts +67 -0
- package/dist/protocol-catalog.d.ts.map +1 -0
- package/dist/protocol-catalog.js +76 -0
- package/dist/protocol-catalog.js.map +1 -0
- package/dist/proxy.d.ts +19 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +100 -0
- package/dist/proxy.js.map +1 -0
- package/dist/seller.d.ts +24 -0
- package/dist/seller.d.ts.map +1 -1
- package/dist/seller.js +195 -28
- package/dist/seller.js.map +1 -1
- package/dist/stripe-mpp.d.ts +2 -2
- package/dist/stripe-mpp.d.ts.map +1 -1
- package/dist/stripe-mpp.js +8 -2
- package/dist/stripe-mpp.js.map +1 -1
- package/dist/ucp-handler.d.ts +8 -0
- package/dist/ucp-handler.d.ts.map +1 -0
- package/dist/ucp-handler.js +132 -0
- package/dist/ucp-handler.js.map +1 -0
- package/package.json +6 -3
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
|
|
7
|
-
|
|
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
|
|
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.
|
|
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']`,
|
|
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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/commerce.d.ts
CHANGED
|
@@ -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
|
package/dist/commerce.d.ts.map
CHANGED
|
@@ -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;
|
|
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