@devlab.group/agent-commerce 0.2.0-beta.0 → 1.0.0
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 +134 -55
- package/dist/{chunk-ASK67X56.js → chunk-BQNE3A5H.js} +3 -3
- package/dist/{chunk-ASK67X56.js.map → chunk-BQNE3A5H.js.map} +1 -1
- package/dist/{chunk-MQMKQGAB.js → chunk-MEPETSP6.js} +88 -32
- package/dist/chunk-MEPETSP6.js.map +1 -0
- package/dist/cli/index.js +179 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/guardrails-Dmwi63KB.d.ts +38 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +111 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +1 -1
- package/dist/{protocol-adapter-CtB0sOnV.d.ts → protocol-adapter-CZPOb9uG.d.ts} +1 -1
- package/dist/{runtime-Bie-9FAM.d.ts → runtime-B-9IjMbO.d.ts} +2 -2
- package/dist/x402.d.ts +20 -6
- package/dist/x402.js +79 -23
- package/dist/x402.js.map +1 -1
- package/package.json +7 -1
- package/dist/chunk-MQMKQGAB.js.map +0 -1
- package/dist/guardrails-Di_yI_DW.d.ts +0 -25
package/README.md
CHANGED
|
@@ -10,14 +10,8 @@
|
|
|
10
10
|
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178c6">
|
|
11
11
|
<img alt="MCP" src="https://img.shields.io/badge/MCP-supported-6b4fbb">
|
|
12
12
|
<img alt="x402" src="https://img.shields.io/badge/x402-supported-0052ff">
|
|
13
|
-
<img alt="Status" src="https://img.shields.io/badge/status-alpha-orange">
|
|
14
13
|
</p>
|
|
15
14
|
|
|
16
|
-
> **Beta.** `v0.2.0-beta` is experimental. Do not use it with production funds
|
|
17
|
-
> without an independent review. See [SECURITY.md](SECURITY.md).
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
15
|
## What it is, in ten seconds
|
|
22
16
|
|
|
23
17
|
You already have an HTTP API. AI agents want to **discover** it, **call** it and
|
|
@@ -87,7 +81,7 @@ const gateway = await createGateway({
|
|
|
87
81
|
paymentProviders: [],
|
|
88
82
|
protocolAdapters: [],
|
|
89
83
|
});
|
|
90
|
-
const { url } = await gateway.listen;
|
|
84
|
+
const { url } = await gateway.listen();
|
|
91
85
|
```
|
|
92
86
|
|
|
93
87
|
### Optional peers — install only the rails you use
|
|
@@ -97,11 +91,12 @@ needs a dependency the rest of the package does not — the x402 rail brings the
|
|
|
97
91
|
whole EVM signing and RPC stack, which a gateway serving a free HTTP resource
|
|
98
92
|
has no business installing.
|
|
99
93
|
|
|
100
|
-
| You want
|
|
101
|
-
|
|
|
102
|
-
| gateway, config, receipts, CLI
|
|
103
|
-
| expose resources as MCP tools
|
|
104
|
-
| accept x402 payments
|
|
94
|
+
| You want | Install | Import |
|
|
95
|
+
| --------------------------------- | ------------------------------ | ------------------------------------------ |
|
|
96
|
+
| gateway, config, receipts, CLI | `@devlab.group/agent-commerce` | `from '@devlab.group/agent-commerce'` |
|
|
97
|
+
| expose resources as MCP tools | `+ @modelcontextprotocol/sdk` | `from '@devlab.group/agent-commerce/mcp'` |
|
|
98
|
+
| accept x402 payments | `+ @x402/core @x402/evm viem` | `from '@devlab.group/agent-commerce/x402'` |
|
|
99
|
+
| authenticate to a CDP facilitator | `+ @coinbase/x402` | (no import — loaded on demand) |
|
|
105
100
|
|
|
106
101
|
```bash
|
|
107
102
|
npm install @devlab.group/agent-commerce @modelcontextprotocol/sdk @x402/core @x402/evm viem
|
|
@@ -118,6 +113,13 @@ one. Import a subpath without its
|
|
|
118
113
|
peer installed and Node fails at load naming the missing package — deliberately,
|
|
119
114
|
rather than starting a gateway that silently serves nothing.
|
|
120
115
|
|
|
116
|
+
`@coinbase/x402` is the odd one out: it has no import of its own and is loaded
|
|
117
|
+
dynamically, only when `facilitator.auth.type: cdp` is configured. It is worth
|
|
118
|
+
avoiding if you can — it brings `@coinbase/cdp-sdk` and `axios`, which carry
|
|
119
|
+
high-severity advisories, while the package itself and the other three peers
|
|
120
|
+
audit clean. `auth.type: bearer` covers any facilitator with a static token and
|
|
121
|
+
installs nothing.
|
|
122
|
+
|
|
121
123
|
## Quickstart
|
|
122
124
|
|
|
123
125
|
Requirements: **Node >= 22**, **npm 10**, **Docker**. Nothing else — no API
|
|
@@ -206,13 +208,13 @@ See [docs/configuration.md](docs/configuration.md).
|
|
|
206
208
|
|
|
207
209
|
## Protocol support
|
|
208
210
|
|
|
209
|
-
| Protocol | Status | Pinned revision
|
|
210
|
-
| --------------------- | --------- |
|
|
211
|
-
| **MCP** | Supported | `@modelcontextprotocol/sdk@1.30.0`
|
|
211
|
+
| Protocol | Status | Pinned revision |
|
|
212
|
+
| --------------------- | --------- | -------------------------------------------------------- |
|
|
213
|
+
| **MCP** | Supported | `@modelcontextprotocol/sdk@1.30.0` |
|
|
212
214
|
| **x402** | Supported | x402 v2 (`@x402/core`, `@x402/evm`), scheme `exact`, EVM |
|
|
213
|
-
| **HTTP** | Supported | native routes
|
|
214
|
-
| UCP | Planned | —
|
|
215
|
-
| ACP · MPP · A2A · AP2 | Planned | —
|
|
215
|
+
| **HTTP** | Supported | native routes |
|
|
216
|
+
| UCP | Planned | — |
|
|
217
|
+
| ACP · MPP · A2A · AP2 | Planned | — |
|
|
216
218
|
|
|
217
219
|
"Planned" means **no code ships for it**. Each adapter reports its own
|
|
218
220
|
`supportedSpec`, `capabilities` and `unsupported` list at runtime via
|
|
@@ -235,19 +237,123 @@ checkable, not marketing. Detail: [docs/protocols.md](docs/protocols.md).
|
|
|
235
237
|
|
|
236
238
|
Detail: [docs/payment-flow.md](docs/payment-flow.md).
|
|
237
239
|
|
|
240
|
+
## Public networks
|
|
241
|
+
|
|
242
|
+
Same gateway, same pipeline — a different `network` and a facilitator that is
|
|
243
|
+
not this process. No code changes, and no "live mode" to switch on.
|
|
244
|
+
|
|
245
|
+
### It has actually settled
|
|
246
|
+
|
|
247
|
+
Not a roadmap entry. Both of these moved 0.01 USDC from a buyer to a merchant
|
|
248
|
+
through a remote facilitator:
|
|
249
|
+
|
|
250
|
+
| Network | Transaction |
|
|
251
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
252
|
+
| Base Sepolia | [`0xea41b234c4…`](https://sepolia.basescan.org/tx/0xea41b234c4645a4d335589ec9753646aa7cccd1b97e9e15823b88bff7b54a247) |
|
|
253
|
+
| Base | [`0x57ec81c2a3…`](https://basescan.org/tx/0x57ec81c2a360d14d59a43cf4e24be09a6bd75cbe6185016372895bda73e42763) |
|
|
254
|
+
|
|
255
|
+
In both, the gateway held no key, signed nothing and paid no gas — the buyer
|
|
256
|
+
signed an EIP-3009 authorisation offline holding no ETH, and the facilitator
|
|
257
|
+
broadcast it. Each run reads the buyer and merchant balances and the
|
|
258
|
+
transaction receipt back off the chain afterwards; the gateway's own report of
|
|
259
|
+
success is not the proof.
|
|
260
|
+
|
|
261
|
+
Reproduce with `npm run test:testnet` / `npm run test:mainnet` — both spend
|
|
262
|
+
real funds, skip themselves without credentials, and never run in CI.
|
|
263
|
+
|
|
264
|
+
### The facilitator model
|
|
265
|
+
|
|
266
|
+
A **facilitator** verifies the buyer's authorisation and broadcasts the
|
|
267
|
+
transfer. It is the only component that needs gas, and it is never this
|
|
268
|
+
gateway on a public network.
|
|
269
|
+
|
|
270
|
+
| `facilitator.mode` | Who signs | Where it is allowed |
|
|
271
|
+
| ------------------ | ----------------------------------- | ------------------------ |
|
|
272
|
+
| `local` | this process, with an Anvil dev key | the local dev chain only |
|
|
273
|
+
| `remote` | an HTTP facilitator you point at | anywhere |
|
|
274
|
+
|
|
275
|
+
With `remote`, the gateway holds **no signing key at all**. The buyer signs an
|
|
276
|
+
EIP-3009 authorisation offline — no ETH required — and the facilitator pays the
|
|
277
|
+
gas. A facilitator cannot redirect your money: the authorisation names its
|
|
278
|
+
recipient, amount and chain, so it can broadcast exactly that transfer or
|
|
279
|
+
nothing. What it can do is see every authorisation you handle, and stop
|
|
280
|
+
answering.
|
|
281
|
+
|
|
282
|
+
Three auth types: `none`, `bearer` (a static token, installs nothing) and `cdp`
|
|
283
|
+
(Coinbase Developer Platform, which signs a fresh JWT per request). Anything
|
|
284
|
+
else is refused at config load rather than sent nothing. You can also run your
|
|
285
|
+
own — `remote` does not care who operates the endpoint.
|
|
286
|
+
|
|
287
|
+
### Base Sepolia
|
|
288
|
+
|
|
289
|
+
```yaml
|
|
290
|
+
payments:
|
|
291
|
+
x402:
|
|
292
|
+
enabled: true
|
|
293
|
+
network: eip155:84532
|
|
294
|
+
rpcUrl: https://base-sepolia-rpc.publicnode.com # health checks only
|
|
295
|
+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" # Circle USDC
|
|
296
|
+
assetName: USDC
|
|
297
|
+
assetVersion: "2"
|
|
298
|
+
assetDecimals: 6
|
|
299
|
+
payTo: ${MERCHANT_WALLET}
|
|
300
|
+
maxTimeoutSeconds: 300
|
|
301
|
+
facilitator:
|
|
302
|
+
mode: remote
|
|
303
|
+
url: https://x402.org/facilitator
|
|
304
|
+
auth: { type: none }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Full config in [`examples/base-sepolia/`](examples/base-sepolia/). Test USDC
|
|
308
|
+
from [faucet.circle.com](https://faucet.circle.com); the buyer needs no ETH.
|
|
309
|
+
`npm run test:testnet` drives the whole flow and reads the result back off the
|
|
310
|
+
chain.
|
|
311
|
+
|
|
312
|
+
Chain id 84532 belongs to **both** Base Sepolia and this project's local dev
|
|
313
|
+
chain, deliberately. Nothing infers "public network" from it — `local`,
|
|
314
|
+
`testnet` and `mainnet` are derived from the network *and* the facilitator
|
|
315
|
+
together, and reported by `doctor`, `health()` and `/.well-known`.
|
|
316
|
+
|
|
317
|
+
### Base mainnet
|
|
318
|
+
|
|
319
|
+
Real funds, so nothing is defaulted. Every one of these is checked at config
|
|
320
|
+
load, and the gateway will not start without them:
|
|
321
|
+
|
|
322
|
+
| Required | |
|
|
323
|
+
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
324
|
+
| `allowMainnet: true` | mainnet is never a default |
|
|
325
|
+
| `facilitator.mode: remote` | `local` needs a funded gas key inside this process |
|
|
326
|
+
| an HTTPS `facilitator.url` | |
|
|
327
|
+
| `allowUnauthenticatedFacilitator: true` | only if that facilitator takes no credential |
|
|
328
|
+
| a non-development `payTo` | |
|
|
329
|
+
| `asset` = USDC on Base, `assetName: "USD Coin"` | **not** `"USDC"` — that deployment predates the rename, and the buyer signs the name into their EIP-712 domain |
|
|
330
|
+
|
|
331
|
+
Full config in [`examples/base-mainnet/`](examples/base-mainnet/), and
|
|
332
|
+
[`examples/base-mainnet-payai/`](examples/base-mainnet-payai/) for an
|
|
333
|
+
unauthenticated facilitator. `npm run test:mainnet` proves it end to end and
|
|
334
|
+
spends real USDC on every run.
|
|
335
|
+
|
|
336
|
+
`agent-commerce validate` reports any of the above before anything starts, and
|
|
337
|
+
`doctor` prints `LIVE MAINNET MODE — REAL FUNDS`.
|
|
338
|
+
|
|
339
|
+
> Neither public-network suite runs in CI — there is no workflow and there must
|
|
340
|
+
> not be one. A workflow means a funded key in repository secrets, spendable by
|
|
341
|
+
> anyone with write access. Both suites run from the machine that holds the
|
|
342
|
+
> wallet, and skip themselves without credentials.
|
|
343
|
+
|
|
238
344
|
## Diagnostics
|
|
239
345
|
|
|
240
346
|
```console
|
|
241
347
|
$ npm run agent-commerce -- doctor --config config-demo.yaml
|
|
242
348
|
|
|
243
|
-
PASS
|
|
244
|
-
PASS
|
|
245
|
-
PASS
|
|
246
|
-
PASS
|
|
247
|
-
PASS
|
|
248
|
-
INFO
|
|
249
|
-
PASS
|
|
250
|
-
PASS
|
|
349
|
+
PASS Config valid — 2 resource(s), merchant "Demo Data Store" (using local chain manifest .deploy/local.json for X402_ASSET, X402_ASSET_NAME, X402_ASSET_VERSION, X402_ASSET_DECIMALS, MERCHANT_WALLET, X402_FACILITATOR_PRIVATE_KEY)
|
|
350
|
+
PASS Gateway healthy and ready at http://127.0.0.1:8080
|
|
351
|
+
PASS Backend 2/2 backend host(s) reachable
|
|
352
|
+
PASS Protocols http=on mcp=on (/mcp)
|
|
353
|
+
PASS Payments x402 v2 (scheme=exact) enabled — LOCAL dev chain (eip155:84532, chain id shared with Base Sepolia), destination=0x7099…79C8, facilitator=local
|
|
354
|
+
INFO Payments (MPP) planned — not implemented in this release
|
|
355
|
+
PASS Storage sqlite schema v1 writable; receipts=2
|
|
356
|
+
PASS Protocol versions reported by gateway /.well-known/agent-commerce
|
|
251
357
|
|
|
252
358
|
Score: 7/7 checks passed
|
|
253
359
|
```
|
|
@@ -277,33 +383,6 @@ reachable by anyone else, know the split:
|
|
|
277
383
|
|
|
278
384
|
[SECURITY.md](SECURITY.md) states plainly what this does and does not protect.
|
|
279
385
|
|
|
280
|
-
## Public networks
|
|
281
|
-
|
|
282
|
-
**Base Sepolia settlement is demonstrated**, not merely configurable: USDC has
|
|
283
|
-
moved from buyer to merchant through the public facilitator, with the gateway
|
|
284
|
-
holding no key and paying no gas. The transaction is in
|
|
285
|
-
[docs/testnet.md](docs/testnet.md). The deterministic local chain
|
|
286
|
-
(Anvil + MockUSDC) is still what the default test suite covers.
|
|
287
|
-
|
|
288
|
-
Base Sepolia (`eip155:84532`), Base mainnet (`eip155:8453`) and a remote HTTP
|
|
289
|
-
facilitator can now be configured, with guardrails that refuse the combinations
|
|
290
|
-
that lose money — mainnet needs an explicit `allowMainnet`, a remote
|
|
291
|
-
facilitator over HTTPS with a credential, a non-development `payTo`, and the
|
|
292
|
-
canonical USDC for the chain. Those are checked at config load, so
|
|
293
|
-
`agent-commerce validate` catches them and the gateway will not start without
|
|
294
|
-
them.
|
|
295
|
-
|
|
296
|
-
A ready-to-run testnet config is in
|
|
297
|
-
[`examples/base-sepolia/`](examples/base-sepolia/), and
|
|
298
|
-
`npm run test:testnet` drives the whole flow against Base Sepolia and reads
|
|
299
|
-
the balances and transaction receipt back off the chain to prove it. It needs
|
|
300
|
-
a funded test wallet, skips itself without one, and is deliberately outside
|
|
301
|
-
`npm test` and `npm run test:e2e` — both of those must stay offline.
|
|
302
|
-
|
|
303
|
-
**Mainnet is a different claim, and it is not made.** `eip155:8453` is
|
|
304
|
-
configurable and guarded, and no payment has been settled on it. See
|
|
305
|
-
[docs/testnet.md](docs/testnet.md).
|
|
306
|
-
|
|
307
386
|
## Development
|
|
308
387
|
|
|
309
388
|
```bash
|
|
@@ -316,7 +395,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
316
395
|
|
|
317
396
|
## Roadmap
|
|
318
397
|
|
|
319
|
-
**Now (
|
|
398
|
+
**Now (v1.0.0)** — MCP, x402 v2, settlement on the local chain, Base Sepolia
|
|
399
|
+
and Base mainnet, receipts, doctor, deterministic demo.
|
|
320
400
|
|
|
321
401
|
**Next** — OpenAPI import · a stronger conformance suite · a `doctor` GitHub
|
|
322
402
|
Action · UCP · MPP · ACP · A2A · AP2 · Shopify and WooCommerce examples ·
|
|
@@ -333,7 +413,6 @@ discipline is a release requirement, not a mood.
|
|
|
333
413
|
| [Payment flow](docs/payment-flow.md) | the paid round trip, and every way it fails |
|
|
334
414
|
| [Protocols](docs/protocols.md) | exactly what is and is not supported |
|
|
335
415
|
| [Configuration](docs/configuration.md) | `config.yaml` reference |
|
|
336
|
-
| [Base Sepolia](docs/testnet.md) | running on a public testnet, and proving it |
|
|
337
416
|
| [Security model](docs/security.md) | trust boundaries, and what we do not defend |
|
|
338
417
|
| [Contracts](docs/contracts.md) | the frozen cross-package contract |
|
|
339
418
|
| [Adapter guide](docs/contributing-adapters.md) | add a protocol or a payment rail |
|
|
@@ -187,8 +187,8 @@ var systemClock = {
|
|
|
187
187
|
nowIso: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
188
188
|
monotonicMs: () => performance.now()
|
|
189
189
|
};
|
|
190
|
-
var PACKAGE_VERSION = "
|
|
190
|
+
var PACKAGE_VERSION = "1.0.0" ;
|
|
191
191
|
|
|
192
192
|
export { COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, NOOP_LOGGER, PACKAGE_VERSION, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, RETRYABLE_ERROR_CODES, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope };
|
|
193
|
-
//# sourceMappingURL=chunk-
|
|
194
|
-
//# sourceMappingURL=chunk-
|
|
193
|
+
//# sourceMappingURL=chunk-BQNE3A5H.js.map
|
|
194
|
+
//# sourceMappingURL=chunk-BQNE3A5H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/domain/event.ts","../src/core/domain/resource.ts","../src/core/domain/wire.ts","../src/core/errors/codes.ts","../src/core/errors/commerce-error.ts","../src/core/interfaces/logger.ts","../src/core/interfaces/protocol-adapter.ts","../src/core/interfaces/runtime.ts","../src/version.ts"],"names":[],"mappings":"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","file":"chunk-ASK67X56.js","sourcesContent":["/**\n * Canonical commerce events.\n *\n * FROZEN CONTRACT. Every event in a single flow shares one `requestId` so that\n * logs, payment attempts, backend calls and receipts are correlatable.\n */\nimport type { IsoTimestamp } from './common.js';\n\nexport const COMMERCE_EVENT_TYPES = [\n 'resource.discovered',\n 'resource.requested',\n 'payment.required',\n 'payment.rejected',\n 'payment.verified',\n 'payment.settled',\n 'backend.called',\n 'backend.failed',\n 'resource.delivered',\n] as const;\n\nexport type CommerceEventType = (typeof COMMERCE_EVENT_TYPES)[number];\n\nexport interface CommerceEvent {\n readonly id: string;\n readonly type: CommerceEventType;\n readonly requestId: string;\n readonly resourceId?: string;\n readonly at: IsoTimestamp;\n /** Protocol adapter that originated the request, e.g. 'mcp' or 'http'. */\n readonly adapter?: string;\n readonly paymentProvider?: string;\n readonly durationMs?: number;\n readonly status?: 'ok' | 'error';\n /** Non-secret, structured detail. Must never contain keys, headers or proofs. */\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Sink for canonical events.\n *\n * Implementations must be non-throwing from the caller's perspective: an event\n * sink failure must never fail an otherwise successful commerce flow.\n */\nexport interface EventSink {\n emit(event: CommerceEvent): Promise<void>;\n}\n","/**\n * Canonical resource model.\n *\n * FROZEN CONTRACT.\n */\nimport type { DecimalAmount, JsonSchema, PaymentMethodName, ProtocolName } from './common.js';\n\n/** HTTP methods a backend handler may use. */\nexport type BackendMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/**\n * Describes how the gateway calls the merchant's existing backend.\n *\n * Backend URLs are administrator-controlled configuration only. User- or\n * agent-controlled backend URLs are forbidden (see docs/security.md, SSRF).\n * Secret header values must come from `${ENV_VAR}` placeholders resolved by\n * the config loader, never from plaintext configuration.\n */\nexport interface BackendHandler {\n readonly type: 'http';\n readonly method: BackendMethod;\n readonly url: string;\n readonly headers?: Readonly<Record<string, string>>;\n /** Hard upper bound on the backend call. Defaults to DEFAULT_BACKEND_TIMEOUT_MS. */\n readonly timeoutMs?: number;\n}\n\n/** Default backend timeout when a resource does not specify one. */\nexport const DEFAULT_BACKEND_TIMEOUT_MS = 10_000;\n\n/**\n * Canonical pricing.\n *\n * `dynamic` exists in the type system for forward compatibility but is\n * REJECTED by config validation in this release.\n */\nexport type Pricing =\n | { readonly type: 'free' }\n | {\n readonly type: 'fixed';\n readonly amount: DecimalAmount;\n readonly currency: string;\n }\n | {\n readonly type: 'dynamic';\n readonly resolver: string;\n };\n\n/**\n * A merchant capability exposed to agents.\n *\n * The canonical resource is the single source of truth: protocol adapters map\n * it outward, they never define their own parallel notion of a resource.\n */\nexport interface CommerceResource {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly inputSchema?: JsonSchema;\n readonly outputSchema?: JsonSchema;\n readonly handler: BackendHandler;\n readonly pricing: Pricing;\n readonly exposedVia: readonly ProtocolName[];\n readonly paymentMethods: readonly PaymentMethodName[];\n}\n\n/** Read-only view of every configured resource. */\nexport interface ResourceRegistry {\n get(id: string): CommerceResource | undefined;\n list(): readonly CommerceResource[];\n /** Resources exposed through a given protocol surface. */\n listExposedVia(protocol: ProtocolName): readonly CommerceResource[];\n has(id: string): boolean;\n}\n","/**\n * Gateway-defined wire envelopes.\n *\n * FROZEN CONTRACT. These are the *only* representations of \"payment required\"\n * and \"error\" that leave the gateway, whichever protocol carries them. The HTTP\n * routes, the MCP adapter and the demo buyer agent all use these helpers so the\n * three can never drift apart.\n */\n\nimport type { CommerceError, CommerceErrorCode } from '../errors/index.js';\nimport type { DecimalAmount, IsoTimestamp, PaymentMethodName } from './common.js';\nimport type { DeliveredOutcome, PaymentRequiredOutcome } from './request.js';\n\n/**\n * Reserved input property carrying a payment proof on protocols that have no\n * header channel (MCP tool calls). Over HTTP the `PAYMENT-SIGNATURE` header is\n * used.\n */\nexport const PAYMENT_INPUT_FIELD = '_payment';\n\n/**\n * HTTP request header carrying the payment proof.\n *\n * x402 v2 header names, lowercased because that is how Node presents incoming\n * headers. v1's `X-PAYMENT` / `X-PAYMENT-RESPONSE` pair is not accepted: this\n * gateway speaks one protocol version, and quietly honouring both would mean\n * two verification paths for the same money.\n */\nexport const PAYMENT_HEADER = 'payment-signature';\n\n/** HTTP response header carrying the settlement result. */\nexport const PAYMENT_RESPONSE_HEADER = 'payment-response';\n\n/**\n * HTTP response header carrying the base64 payment challenge on a 402.\n *\n * The 402 body still carries {@link PaymentRequiredEnvelope} — richer, and the\n * only channel MCP has — but an x402 v2 client reads the challenge from this\n * header and ignores the body, so both are sent.\n */\nexport const PAYMENT_REQUIRED_HEADER = 'payment-required';\n\n/**\n * Key under which a protocol adapter attaches a {@link DeliverySummary} to its\n * result metadata — MCP's `CallToolResult._meta`, and anywhere else a protocol\n * offers an out-of-band metadata channel.\n *\n * Frozen deliberately. The bug this whole mechanism exists to fix was two\n * surfaces disagreeing about what a payer gets back; a key name enforced only\n * by one side's tests would reintroduce exactly that drift. Producer and\n * consumer both import this constant.\n *\n * Follows MCP's `<namespace>/<name>` convention for `_meta` keys.\n */\nexport const DELIVERY_SUMMARY_META_KEY = 'agent-commerce/delivery';\n\nexport interface PaymentRequiredEnvelope {\n readonly status: 'payment-required';\n readonly code: 'PAYMENT_REQUIRED';\n readonly requestId: string;\n readonly resourceId: string;\n readonly message: string;\n readonly payment: {\n readonly provider: PaymentMethodName;\n readonly version: string;\n readonly amount: DecimalAmount;\n readonly currency: string;\n readonly destination: string;\n readonly network?: string;\n readonly asset?: string;\n readonly expiresAt?: IsoTimestamp;\n /** Provider-native requirement objects, passed through verbatim. */\n readonly accepts: readonly Readonly<Record<string, unknown>>[];\n /**\n * The provider's own challenge document, verbatim — see\n * {@link PaymentChallenge.envelope}. Present for providers that have one;\n * a buyer's protocol client can hand this straight to its SDK.\n */\n readonly envelope?: Readonly<Record<string, unknown>>;\n };\n}\n\nexport interface ErrorEnvelope {\n readonly status: 'error';\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * What a buyer learns about their own completed purchase.\n *\n * A payer is entitled to the record of their own transaction — the request it\n * belonged to, whether it settled, and the settlement reference they can check\n * on-chain. They are NOT entitled to the merchant's ledger, which is why\n * `/api/receipts` is an operator route behind `server.adminToken`.\n *\n * Before this existed the two surfaces disagreed: HTTP callers received a\n * settlement summary in the payment-response header while MCP callers received\n * nothing, so an MCP buyer's only route to their own receipt was the merchant's\n * ledger. Both surfaces now emit this same shape.\n */\nexport interface DeliverySummary {\n readonly requestId: string;\n readonly resourceId: string;\n readonly receiptId: string;\n readonly deliveredAt: IsoTimestamp;\n readonly payment?: {\n readonly status: 'verified' | 'settled' | 'rejected';\n readonly amount: DecimalAmount;\n readonly currency: string;\n /** Settlement reference, e.g. an on-chain transaction hash. */\n readonly externalReference?: string;\n readonly network?: string;\n };\n}\n\n/** Build the payer-facing summary of a delivered outcome. */\nexport function toDeliverySummary(outcome: DeliveredOutcome): DeliverySummary {\n const p = outcome.payment;\n return {\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n receiptId: outcome.receipt.id,\n deliveredAt: outcome.receipt.deliveredAt,\n ...(p !== undefined\n ? {\n payment: {\n status: p.status,\n amount: p.amount,\n currency: p.currency,\n ...(p.externalReference !== undefined\n ? { externalReference: p.externalReference }\n : {}),\n ...(p.network !== undefined ? { network: p.network } : {}),\n },\n }\n : {}),\n };\n}\n\nexport function toPaymentRequiredEnvelope(\n outcome: PaymentRequiredOutcome,\n): PaymentRequiredEnvelope {\n const r = outcome.requirement;\n return {\n status: 'payment-required',\n code: 'PAYMENT_REQUIRED',\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n message: `Payment of ${r.amount} ${r.currency} is required for resource \"${outcome.resourceId}\". Retry with a ${r.provider} payment proof.`,\n payment: {\n provider: r.provider,\n version: r.challenge.version,\n amount: r.amount,\n currency: r.currency,\n destination: r.destination,\n ...(r.network !== undefined ? { network: r.network } : {}),\n ...(r.asset !== undefined ? { asset: r.asset } : {}),\n ...(r.expiresAt !== undefined ? { expiresAt: r.expiresAt } : {}),\n accepts: r.challenge.accepts,\n ...(r.challenge.envelope !== undefined ? { envelope: r.challenge.envelope } : {}),\n },\n };\n}\n\nexport function toErrorEnvelope(error: CommerceError): ErrorEnvelope {\n const info = error.toInfo();\n return {\n status: 'error',\n code: info.code,\n message: info.message,\n retryable: info.retryable,\n ...(info.requestId !== undefined ? { requestId: info.requestId } : {}),\n ...(info.resourceId !== undefined ? { resourceId: info.resourceId } : {}),\n ...(info.details !== undefined ? { details: info.details } : {}),\n };\n}\n\nexport function isPaymentRequiredEnvelope(value: unknown): value is PaymentRequiredEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as PaymentRequiredEnvelope).status === 'payment-required' &&\n typeof (value as PaymentRequiredEnvelope).payment === 'object'\n );\n}\n","/**\n * Typed domain error codes.\n *\n * FROZEN CONTRACT. Protocol adapters map these codes into their own response\n * format. Never throw a bare string or untyped Error across a package boundary.\n */\nexport const COMMERCE_ERROR_CODES = [\n 'CONFIG_INVALID',\n 'RESOURCE_NOT_FOUND',\n 'INPUT_INVALID',\n 'PAYMENT_REQUIRED',\n 'PAYMENT_INVALID',\n 'PAYMENT_REPLAYED',\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'PAYMENT_SETTLEMENT_FAILED',\n 'BACKEND_TIMEOUT',\n 'BACKEND_ERROR',\n 'PROTOCOL_UNSUPPORTED',\n 'GATEWAY_BUSY',\n 'STORAGE_ERROR',\n 'INTERNAL_ERROR',\n] as const;\n\nexport type CommerceErrorCode = (typeof COMMERCE_ERROR_CODES)[number];\n\n/**\n * Canonical HTTP status for each error code.\n *\n * Paid resources fail closed: every payment failure maps to a 4xx/5xx status\n * and never to a successful delivery.\n */\nexport const COMMERCE_ERROR_HTTP_STATUS: Readonly<Record<CommerceErrorCode, number>> = {\n CONFIG_INVALID: 500,\n RESOURCE_NOT_FOUND: 404,\n INPUT_INVALID: 400,\n PAYMENT_REQUIRED: 402,\n PAYMENT_INVALID: 402,\n PAYMENT_REPLAYED: 409,\n PAYMENT_PROVIDER_UNAVAILABLE: 503,\n PAYMENT_SETTLEMENT_FAILED: 502,\n BACKEND_TIMEOUT: 504,\n BACKEND_ERROR: 502,\n PROTOCOL_UNSUPPORTED: 501,\n GATEWAY_BUSY: 503,\n STORAGE_ERROR: 500,\n INTERNAL_ERROR: 500,\n};\n\n/** Codes for which a client may reasonably retry the same request. */\nexport const RETRYABLE_ERROR_CODES: ReadonlySet<CommerceErrorCode> = new Set([\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'BACKEND_TIMEOUT',\n // Load shedding is transient by definition: the caller should back off and\n // try again. Before this code existed the MCP adapter's queue-full path threw\n // PROTOCOL_UNSUPPORTED — HTTP 501, retryable:false — so an agent framework\n // keyed on the machine-readable flag (which exists precisely so clients need\n // not parse prose) would read a momentary throttle as a permanent capability\n // gap and stop calling the tool.\n 'GATEWAY_BUSY',\n]);\n","/**\n * The single error type crossing package boundaries.\n *\n * FROZEN CONTRACT.\n */\nimport {\n COMMERCE_ERROR_HTTP_STATUS,\n type CommerceErrorCode,\n RETRYABLE_ERROR_CODES,\n} from './codes.js';\n\nexport interface CommerceErrorOptions {\n /** Structured, non-secret detail safe to return to a client. */\n readonly details?: Readonly<Record<string, unknown>>;\n readonly cause?: unknown;\n readonly requestId?: string;\n readonly resourceId?: string;\n /** Override the default HTTP status for this code. */\n readonly httpStatus?: number;\n}\n\n/** Serialisable wire form of a CommerceError. */\nexport interface CommerceErrorInfo {\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\nexport class CommerceError extends Error {\n readonly code: CommerceErrorCode;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly details?: Readonly<Record<string, unknown>>;\n readonly requestId?: string;\n readonly resourceId?: string;\n\n constructor(code: CommerceErrorCode, message: string, options: CommerceErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'CommerceError';\n this.code = code;\n this.httpStatus = options.httpStatus ?? COMMERCE_ERROR_HTTP_STATUS[code];\n this.retryable = RETRYABLE_ERROR_CODES.has(code);\n if (options.details !== undefined) this.details = options.details;\n if (options.requestId !== undefined) this.requestId = options.requestId;\n if (options.resourceId !== undefined) this.resourceId = options.resourceId;\n Error.captureStackTrace?.(this, CommerceError);\n }\n\n /** Non-secret, client-safe representation. */\n toInfo(): CommerceErrorInfo {\n return {\n code: this.code,\n message: this.message,\n httpStatus: this.httpStatus,\n retryable: this.retryable,\n ...(this.requestId !== undefined ? { requestId: this.requestId } : {}),\n ...(this.resourceId !== undefined ? { resourceId: this.resourceId } : {}),\n ...(this.details !== undefined ? { details: this.details } : {}),\n };\n }\n}\n\nexport function isCommerceError(value: unknown): value is CommerceError {\n return value instanceof CommerceError;\n}\n\n/**\n * Normalise any thrown value into a CommerceError.\n *\n * Used at package boundaries so an unexpected failure never escapes untyped.\n *\n * SECURITY — do not \"improve\" this by propagating `value.message`.\n * `CommerceError.message` is client-visible: it is serialised by `toInfo()`\n * and `toErrorEnvelope()` and returned over HTTP and MCP. An arbitrary thrown\n * Error's message routinely carries internal detail — database connection\n * strings, internal hostnames, filesystem paths, backend URLs. Copying it into\n * a client-facing field turns every unexpected exception into an information\n * disclosure.\n *\n * The original value is preserved on `cause`, which is never serialised, so\n * logs and diagnostics keep the full detail while clients get only the\n * caller's deliberate, reviewed `fallbackMessage`.\n *\n * A caller that *wants* a specific client-facing message passes one:\n * toCommerceError(err, 'BACKEND_ERROR', 'Backend call failed')\n */\nexport function toCommerceError(\n value: unknown,\n fallbackCode: CommerceErrorCode = 'INTERNAL_ERROR',\n fallbackMessage = 'Unexpected internal error',\n): CommerceError {\n if (isCommerceError(value)) return value;\n return new CommerceError(fallbackCode, fallbackMessage, { cause: value });\n}\n","/**\n * Minimal structural logger interface.\n *\n * Core must not depend on pino directly; the gateway injects a pino instance\n * that satisfies this shape. Never log secrets: private keys, seed phrases,\n * Authorization headers or payment authorisation payloads.\n */\nexport interface Logger {\n debug(obj: Record<string, unknown>, msg?: string): void;\n info(obj: Record<string, unknown>, msg?: string): void;\n warn(obj: Record<string, unknown>, msg?: string): void;\n error(obj: Record<string, unknown>, msg?: string): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\n/** A logger that discards everything. Useful in unit tests. */\nexport const NOOP_LOGGER: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => NOOP_LOGGER,\n};\n","/**\n * Protocol adapter boundary.\n *\n * FROZEN CONTRACT. An adapter translates a protocol's wire format into a\n * `CanonicalRequest`, hands it to the execution pipeline and maps the outcome\n * back. It must not call merchant backends, implement payment logic, or hold\n * its own copy of the canonical model.\n *\n * Adapter isolation: a failure while starting or serving one adapter must not\n * prevent the others from operating.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { AdapterDescriptor, AdapterHealth, ProtocolName } from '../domain/common.js';\nimport type { EventSink } from '../domain/event.js';\nimport type { ExecutionPipeline } from '../domain/request.js';\nimport type { ResourceRegistry } from '../domain/resource.js';\nimport type { Logger } from './logger.js';\nimport type { Clock, IdGenerator } from './runtime.js';\n\n/** Everything an adapter is given. Nothing else is available to it. */\nexport interface ProtocolAdapterContext {\n readonly pipeline: ExecutionPipeline;\n readonly resources: ResourceRegistry;\n readonly events: EventSink;\n readonly logger: Logger;\n readonly clock: Clock;\n readonly ids: IdGenerator;\n /** Externally reachable base URL of the gateway, from config. */\n readonly publicBaseUrl: string;\n}\n\nexport interface ProtocolAdapter {\n readonly name: ProtocolName;\n readonly descriptor: AdapterDescriptor;\n\n start(context: ProtocolAdapterContext): Promise<void>;\n health(): Promise<AdapterHealth>;\n stop(): Promise<void>;\n}\n\n/**\n * A protocol adapter served over HTTP.\n *\n * The gateway mounts `handleHttp` at `mountPath` using raw Node request and\n * response objects, so adapters stay independent of the HTTP framework.\n *\n * GUARANTEE — the request body is **unconsumed**.\n *\n * `req` is handed over with its stream unread: the gateway suppresses every\n * body parser on this mount, so an adapter whose SDK reads the body itself\n * (MCP's Streamable HTTP transport does) can do so. The adapter owns reading,\n * size-limiting beyond the gateway's own cap, and parsing.\n *\n * This is load-bearing and easy to break silently. Fastify pre-registers\n * exact-match parsers for `application/json` and `text/plain` and prefers them\n * over a `'*'` wildcard, so registering only a wildcard no-op leaves the\n * built-in JSON parser draining the body first — the adapter then reads an\n * empty stream and every request fails to parse. That exact regression shipped\n * once and no unit test caught it, because adapter conformance suites drive\n * `handleHttp` over a bare `node:http` server and never traverse the gateway.\n *\n * Any change to how adapters are mounted must be covered by a test that POSTs\n * a real JSON body through the real gateway and asserts the adapter received\n * it intact.\n */\nexport interface HttpProtocolAdapter extends ProtocolAdapter {\n /** Path prefix the gateway mounts this adapter at, e.g. '/mcp'. */\n readonly mountPath: string;\n handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void>;\n}\n\nexport function isHttpProtocolAdapter(adapter: ProtocolAdapter): adapter is HttpProtocolAdapter {\n return typeof (adapter as HttpProtocolAdapter).handleHttp === 'function';\n}\n","/**\n * Injected runtime primitives.\n *\n * Everything non-deterministic (time, ids) is injected so tests can be exact.\n */\nimport type { IsoTimestamp } from '../domain/common.js';\n\nexport interface Clock {\n now(): Date;\n nowIso(): IsoTimestamp;\n /** Milliseconds since an arbitrary origin; use for durations only. */\n monotonicMs(): number;\n}\n\nexport interface IdGenerator {\n /** Opaque, collision-resistant identifier. */\n next(prefix?: string): string;\n}\n\nexport const systemClock: Clock = {\n now: () => new Date(),\n nowIso: () => new Date().toISOString(),\n monotonicMs: () => performance.now(),\n};\n","/**\n * The package version, resolved once for the whole package.\n *\n * Two paths, deliberately:\n *\n * - **Development** reads the root manifest, so the number is always live.\n * - **The published bundle cannot.** tsup collapses `src/**` into\n * `dist/index.js` and `dist/cli/index.js`, so any `require('../../package.json')`\n * written against a source path resolves from the wrong depth once bundled —\n * which is exactly how this broke during the single-package refactor, and how\n * it earlier produced a CLI reporting `v0.0.0-unknown`. The build injects\n * `__OAC_PACKAGE_VERSION__` instead (see tsup.config.ts).\n *\n * `typeof` on an undeclared identifier is safe in JS, so the dev path is\n * unaffected by the constant not existing.\n */\nimport { createRequire } from 'node:module';\n\ndeclare const __OAC_PACKAGE_VERSION__: string | undefined;\n\nfunction readFromManifest(): string {\n try {\n const require = createRequire(import.meta.url);\n // src/version.ts -> repository root\n return (require('../package.json') as { version?: string }).version ?? '0.0.0-unknown';\n } catch {\n return '0.0.0-unknown';\n }\n}\n\nexport const PACKAGE_VERSION: string =\n typeof __OAC_PACKAGE_VERSION__ === 'string' ? __OAC_PACKAGE_VERSION__ : readFromManifest();\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/domain/event.ts","../src/core/domain/resource.ts","../src/core/domain/wire.ts","../src/core/errors/codes.ts","../src/core/errors/commerce-error.ts","../src/core/interfaces/logger.ts","../src/core/interfaces/protocol-adapter.ts","../src/core/interfaces/runtime.ts","../src/version.ts"],"names":[],"mappings":"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","file":"chunk-BQNE3A5H.js","sourcesContent":["/**\n * Canonical commerce events.\n *\n * FROZEN CONTRACT. Every event in a single flow shares one `requestId` so that\n * logs, payment attempts, backend calls and receipts are correlatable.\n */\nimport type { IsoTimestamp } from './common.js';\n\nexport const COMMERCE_EVENT_TYPES = [\n 'resource.discovered',\n 'resource.requested',\n 'payment.required',\n 'payment.rejected',\n 'payment.verified',\n 'payment.settled',\n 'backend.called',\n 'backend.failed',\n 'resource.delivered',\n] as const;\n\nexport type CommerceEventType = (typeof COMMERCE_EVENT_TYPES)[number];\n\nexport interface CommerceEvent {\n readonly id: string;\n readonly type: CommerceEventType;\n readonly requestId: string;\n readonly resourceId?: string;\n readonly at: IsoTimestamp;\n /** Protocol adapter that originated the request, e.g. 'mcp' or 'http'. */\n readonly adapter?: string;\n readonly paymentProvider?: string;\n readonly durationMs?: number;\n readonly status?: 'ok' | 'error';\n /** Non-secret, structured detail. Must never contain keys, headers or proofs. */\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Sink for canonical events.\n *\n * Implementations must be non-throwing from the caller's perspective: an event\n * sink failure must never fail an otherwise successful commerce flow.\n */\nexport interface EventSink {\n emit(event: CommerceEvent): Promise<void>;\n}\n","/**\n * Canonical resource model.\n *\n * FROZEN CONTRACT.\n */\nimport type { DecimalAmount, JsonSchema, PaymentMethodName, ProtocolName } from './common.js';\n\n/** HTTP methods a backend handler may use. */\nexport type BackendMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/**\n * Describes how the gateway calls the merchant's existing backend.\n *\n * Backend URLs are administrator-controlled configuration only. User- or\n * agent-controlled backend URLs are forbidden (see docs/security.md, SSRF).\n * Secret header values must come from `${ENV_VAR}` placeholders resolved by\n * the config loader, never from plaintext configuration.\n */\nexport interface BackendHandler {\n readonly type: 'http';\n readonly method: BackendMethod;\n readonly url: string;\n readonly headers?: Readonly<Record<string, string>>;\n /** Hard upper bound on the backend call. Defaults to DEFAULT_BACKEND_TIMEOUT_MS. */\n readonly timeoutMs?: number;\n}\n\n/** Default backend timeout when a resource does not specify one. */\nexport const DEFAULT_BACKEND_TIMEOUT_MS = 10_000;\n\n/**\n * Canonical pricing.\n *\n * `dynamic` exists in the type system for forward compatibility but is\n * REJECTED by config validation in this release.\n */\nexport type Pricing =\n | { readonly type: 'free' }\n | {\n readonly type: 'fixed';\n readonly amount: DecimalAmount;\n readonly currency: string;\n }\n | {\n readonly type: 'dynamic';\n readonly resolver: string;\n };\n\n/**\n * A merchant capability exposed to agents.\n *\n * The canonical resource is the single source of truth: protocol adapters map\n * it outward, they never define their own parallel notion of a resource.\n */\nexport interface CommerceResource {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly inputSchema?: JsonSchema;\n readonly outputSchema?: JsonSchema;\n readonly handler: BackendHandler;\n readonly pricing: Pricing;\n readonly exposedVia: readonly ProtocolName[];\n readonly paymentMethods: readonly PaymentMethodName[];\n}\n\n/** Read-only view of every configured resource. */\nexport interface ResourceRegistry {\n get(id: string): CommerceResource | undefined;\n list(): readonly CommerceResource[];\n /** Resources exposed through a given protocol surface. */\n listExposedVia(protocol: ProtocolName): readonly CommerceResource[];\n has(id: string): boolean;\n}\n","/**\n * Gateway-defined wire envelopes.\n *\n * FROZEN CONTRACT. These are the *only* representations of \"payment required\"\n * and \"error\" that leave the gateway, whichever protocol carries them. The HTTP\n * routes, the MCP adapter and the demo buyer agent all use these helpers so the\n * three can never drift apart.\n */\n\nimport type { CommerceError, CommerceErrorCode } from '../errors/index.js';\nimport type { DecimalAmount, IsoTimestamp, PaymentMethodName } from './common.js';\nimport type { DeliveredOutcome, PaymentRequiredOutcome } from './request.js';\n\n/**\n * Reserved input property carrying a payment proof on protocols that have no\n * header channel (MCP tool calls). Over HTTP the `PAYMENT-SIGNATURE` header is\n * used.\n */\nexport const PAYMENT_INPUT_FIELD = '_payment';\n\n/**\n * HTTP request header carrying the payment proof.\n *\n * x402 v2 header names, lowercased because that is how Node presents incoming\n * headers. v1's `X-PAYMENT` / `X-PAYMENT-RESPONSE` pair is not accepted: this\n * gateway speaks one protocol version, and quietly honouring both would mean\n * two verification paths for the same money.\n */\nexport const PAYMENT_HEADER = 'payment-signature';\n\n/** HTTP response header carrying the settlement result. */\nexport const PAYMENT_RESPONSE_HEADER = 'payment-response';\n\n/**\n * HTTP response header carrying the base64 payment challenge on a 402.\n *\n * The 402 body still carries {@link PaymentRequiredEnvelope} — richer, and the\n * only channel MCP has — but an x402 v2 client reads the challenge from this\n * header and ignores the body, so both are sent.\n */\nexport const PAYMENT_REQUIRED_HEADER = 'payment-required';\n\n/**\n * Key under which a protocol adapter attaches a {@link DeliverySummary} to its\n * result metadata — MCP's `CallToolResult._meta`, and anywhere else a protocol\n * offers an out-of-band metadata channel.\n *\n * Frozen deliberately. The bug this whole mechanism exists to fix was two\n * surfaces disagreeing about what a payer gets back; a key name enforced only\n * by one side's tests would reintroduce exactly that drift. Producer and\n * consumer both import this constant.\n *\n * Follows MCP's `<namespace>/<name>` convention for `_meta` keys.\n */\nexport const DELIVERY_SUMMARY_META_KEY = 'agent-commerce/delivery';\n\nexport interface PaymentRequiredEnvelope {\n readonly status: 'payment-required';\n readonly code: 'PAYMENT_REQUIRED';\n readonly requestId: string;\n readonly resourceId: string;\n readonly message: string;\n readonly payment: {\n readonly provider: PaymentMethodName;\n readonly version: string;\n readonly amount: DecimalAmount;\n readonly currency: string;\n readonly destination: string;\n readonly network?: string;\n readonly asset?: string;\n readonly expiresAt?: IsoTimestamp;\n /** Provider-native requirement objects, passed through verbatim. */\n readonly accepts: readonly Readonly<Record<string, unknown>>[];\n /**\n * The provider's own challenge document, verbatim — see\n * {@link PaymentChallenge.envelope}. Present for providers that have one;\n * a buyer's protocol client can hand this straight to its SDK.\n */\n readonly envelope?: Readonly<Record<string, unknown>>;\n };\n}\n\nexport interface ErrorEnvelope {\n readonly status: 'error';\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * What a buyer learns about their own completed purchase.\n *\n * A payer is entitled to the record of their own transaction — the request it\n * belonged to, whether it settled, and the settlement reference they can check\n * on-chain. They are NOT entitled to the merchant's ledger, which is why\n * `/api/receipts` is an operator route behind `server.adminToken`.\n *\n * Before this existed the two surfaces disagreed: HTTP callers received a\n * settlement summary in the payment-response header while MCP callers received\n * nothing, so an MCP buyer's only route to their own receipt was the merchant's\n * ledger. Both surfaces now emit this same shape.\n */\nexport interface DeliverySummary {\n readonly requestId: string;\n readonly resourceId: string;\n readonly receiptId: string;\n readonly deliveredAt: IsoTimestamp;\n readonly payment?: {\n readonly status: 'verified' | 'settled' | 'rejected';\n readonly amount: DecimalAmount;\n readonly currency: string;\n /** Settlement reference, e.g. an on-chain transaction hash. */\n readonly externalReference?: string;\n readonly network?: string;\n };\n}\n\n/** Build the payer-facing summary of a delivered outcome. */\nexport function toDeliverySummary(outcome: DeliveredOutcome): DeliverySummary {\n const p = outcome.payment;\n return {\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n receiptId: outcome.receipt.id,\n deliveredAt: outcome.receipt.deliveredAt,\n ...(p !== undefined\n ? {\n payment: {\n status: p.status,\n amount: p.amount,\n currency: p.currency,\n ...(p.externalReference !== undefined\n ? { externalReference: p.externalReference }\n : {}),\n ...(p.network !== undefined ? { network: p.network } : {}),\n },\n }\n : {}),\n };\n}\n\nexport function toPaymentRequiredEnvelope(\n outcome: PaymentRequiredOutcome,\n): PaymentRequiredEnvelope {\n const r = outcome.requirement;\n return {\n status: 'payment-required',\n code: 'PAYMENT_REQUIRED',\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n message: `Payment of ${r.amount} ${r.currency} is required for resource \"${outcome.resourceId}\". Retry with a ${r.provider} payment proof.`,\n payment: {\n provider: r.provider,\n version: r.challenge.version,\n amount: r.amount,\n currency: r.currency,\n destination: r.destination,\n ...(r.network !== undefined ? { network: r.network } : {}),\n ...(r.asset !== undefined ? { asset: r.asset } : {}),\n ...(r.expiresAt !== undefined ? { expiresAt: r.expiresAt } : {}),\n accepts: r.challenge.accepts,\n ...(r.challenge.envelope !== undefined ? { envelope: r.challenge.envelope } : {}),\n },\n };\n}\n\nexport function toErrorEnvelope(error: CommerceError): ErrorEnvelope {\n const info = error.toInfo();\n return {\n status: 'error',\n code: info.code,\n message: info.message,\n retryable: info.retryable,\n ...(info.requestId !== undefined ? { requestId: info.requestId } : {}),\n ...(info.resourceId !== undefined ? { resourceId: info.resourceId } : {}),\n ...(info.details !== undefined ? { details: info.details } : {}),\n };\n}\n\nexport function isPaymentRequiredEnvelope(value: unknown): value is PaymentRequiredEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as PaymentRequiredEnvelope).status === 'payment-required' &&\n typeof (value as PaymentRequiredEnvelope).payment === 'object'\n );\n}\n","/**\n * Typed domain error codes.\n *\n * FROZEN CONTRACT. Protocol adapters map these codes into their own response\n * format. Never throw a bare string or untyped Error across a package boundary.\n */\nexport const COMMERCE_ERROR_CODES = [\n 'CONFIG_INVALID',\n 'RESOURCE_NOT_FOUND',\n 'INPUT_INVALID',\n 'PAYMENT_REQUIRED',\n 'PAYMENT_INVALID',\n 'PAYMENT_REPLAYED',\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'PAYMENT_SETTLEMENT_FAILED',\n 'BACKEND_TIMEOUT',\n 'BACKEND_ERROR',\n 'PROTOCOL_UNSUPPORTED',\n 'GATEWAY_BUSY',\n 'STORAGE_ERROR',\n 'INTERNAL_ERROR',\n] as const;\n\nexport type CommerceErrorCode = (typeof COMMERCE_ERROR_CODES)[number];\n\n/**\n * Canonical HTTP status for each error code.\n *\n * Paid resources fail closed: every payment failure maps to a 4xx/5xx status\n * and never to a successful delivery.\n */\nexport const COMMERCE_ERROR_HTTP_STATUS: Readonly<Record<CommerceErrorCode, number>> = {\n CONFIG_INVALID: 500,\n RESOURCE_NOT_FOUND: 404,\n INPUT_INVALID: 400,\n PAYMENT_REQUIRED: 402,\n PAYMENT_INVALID: 402,\n PAYMENT_REPLAYED: 409,\n PAYMENT_PROVIDER_UNAVAILABLE: 503,\n PAYMENT_SETTLEMENT_FAILED: 502,\n BACKEND_TIMEOUT: 504,\n BACKEND_ERROR: 502,\n PROTOCOL_UNSUPPORTED: 501,\n GATEWAY_BUSY: 503,\n STORAGE_ERROR: 500,\n INTERNAL_ERROR: 500,\n};\n\n/** Codes for which a client may reasonably retry the same request. */\nexport const RETRYABLE_ERROR_CODES: ReadonlySet<CommerceErrorCode> = new Set([\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'BACKEND_TIMEOUT',\n // Load shedding is transient by definition: the caller should back off and\n // try again. Before this code existed the MCP adapter's queue-full path threw\n // PROTOCOL_UNSUPPORTED — HTTP 501, retryable:false — so an agent framework\n // keyed on the machine-readable flag (which exists precisely so clients need\n // not parse prose) would read a momentary throttle as a permanent capability\n // gap and stop calling the tool.\n 'GATEWAY_BUSY',\n]);\n","/**\n * The single error type crossing package boundaries.\n *\n * FROZEN CONTRACT.\n */\nimport {\n COMMERCE_ERROR_HTTP_STATUS,\n type CommerceErrorCode,\n RETRYABLE_ERROR_CODES,\n} from './codes.js';\n\nexport interface CommerceErrorOptions {\n /** Structured, non-secret detail safe to return to a client. */\n readonly details?: Readonly<Record<string, unknown>>;\n readonly cause?: unknown;\n readonly requestId?: string;\n readonly resourceId?: string;\n /** Override the default HTTP status for this code. */\n readonly httpStatus?: number;\n}\n\n/** Serialisable wire form of a CommerceError. */\nexport interface CommerceErrorInfo {\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\nexport class CommerceError extends Error {\n readonly code: CommerceErrorCode;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly details?: Readonly<Record<string, unknown>>;\n readonly requestId?: string;\n readonly resourceId?: string;\n\n constructor(code: CommerceErrorCode, message: string, options: CommerceErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'CommerceError';\n this.code = code;\n this.httpStatus = options.httpStatus ?? COMMERCE_ERROR_HTTP_STATUS[code];\n this.retryable = RETRYABLE_ERROR_CODES.has(code);\n if (options.details !== undefined) this.details = options.details;\n if (options.requestId !== undefined) this.requestId = options.requestId;\n if (options.resourceId !== undefined) this.resourceId = options.resourceId;\n Error.captureStackTrace?.(this, CommerceError);\n }\n\n /** Non-secret, client-safe representation. */\n toInfo(): CommerceErrorInfo {\n return {\n code: this.code,\n message: this.message,\n httpStatus: this.httpStatus,\n retryable: this.retryable,\n ...(this.requestId !== undefined ? { requestId: this.requestId } : {}),\n ...(this.resourceId !== undefined ? { resourceId: this.resourceId } : {}),\n ...(this.details !== undefined ? { details: this.details } : {}),\n };\n }\n}\n\nexport function isCommerceError(value: unknown): value is CommerceError {\n return value instanceof CommerceError;\n}\n\n/**\n * Normalise any thrown value into a CommerceError.\n *\n * Used at package boundaries so an unexpected failure never escapes untyped.\n *\n * SECURITY — do not \"improve\" this by propagating `value.message`.\n * `CommerceError.message` is client-visible: it is serialised by `toInfo()`\n * and `toErrorEnvelope()` and returned over HTTP and MCP. An arbitrary thrown\n * Error's message routinely carries internal detail — database connection\n * strings, internal hostnames, filesystem paths, backend URLs. Copying it into\n * a client-facing field turns every unexpected exception into an information\n * disclosure.\n *\n * The original value is preserved on `cause`, which is never serialised, so\n * logs and diagnostics keep the full detail while clients get only the\n * caller's deliberate, reviewed `fallbackMessage`.\n *\n * A caller that *wants* a specific client-facing message passes one:\n * toCommerceError(err, 'BACKEND_ERROR', 'Backend call failed')\n */\nexport function toCommerceError(\n value: unknown,\n fallbackCode: CommerceErrorCode = 'INTERNAL_ERROR',\n fallbackMessage = 'Unexpected internal error',\n): CommerceError {\n if (isCommerceError(value)) return value;\n return new CommerceError(fallbackCode, fallbackMessage, { cause: value });\n}\n","/**\n * Minimal structural logger interface.\n *\n * Core must not depend on pino directly; the gateway injects a pino instance\n * that satisfies this shape. Never log secrets: private keys, seed phrases,\n * Authorization headers or payment authorisation payloads.\n */\nexport interface Logger {\n debug(obj: Record<string, unknown>, msg?: string): void;\n info(obj: Record<string, unknown>, msg?: string): void;\n warn(obj: Record<string, unknown>, msg?: string): void;\n error(obj: Record<string, unknown>, msg?: string): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\n/** A logger that discards everything. Useful in unit tests. */\nexport const NOOP_LOGGER: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => NOOP_LOGGER,\n};\n","/**\n * Protocol adapter boundary.\n *\n * FROZEN CONTRACT. An adapter translates a protocol's wire format into a\n * `CanonicalRequest`, hands it to the execution pipeline and maps the outcome\n * back. It must not call merchant backends, implement payment logic, or hold\n * its own copy of the canonical model.\n *\n * Adapter isolation: a failure while starting or serving one adapter must not\n * prevent the others from operating.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { AdapterDescriptor, AdapterHealth, ProtocolName } from '../domain/common.js';\nimport type { EventSink } from '../domain/event.js';\nimport type { ExecutionPipeline } from '../domain/request.js';\nimport type { ResourceRegistry } from '../domain/resource.js';\nimport type { Logger } from './logger.js';\nimport type { Clock, IdGenerator } from './runtime.js';\n\n/** Everything an adapter is given. Nothing else is available to it. */\nexport interface ProtocolAdapterContext {\n readonly pipeline: ExecutionPipeline;\n readonly resources: ResourceRegistry;\n readonly events: EventSink;\n readonly logger: Logger;\n readonly clock: Clock;\n readonly ids: IdGenerator;\n /** Externally reachable base URL of the gateway, from config. */\n readonly publicBaseUrl: string;\n}\n\nexport interface ProtocolAdapter {\n readonly name: ProtocolName;\n readonly descriptor: AdapterDescriptor;\n\n start(context: ProtocolAdapterContext): Promise<void>;\n health(): Promise<AdapterHealth>;\n stop(): Promise<void>;\n}\n\n/**\n * A protocol adapter served over HTTP.\n *\n * The gateway mounts `handleHttp` at `mountPath` using raw Node request and\n * response objects, so adapters stay independent of the HTTP framework.\n *\n * GUARANTEE — the request body is **unconsumed**.\n *\n * `req` is handed over with its stream unread: the gateway suppresses every\n * body parser on this mount, so an adapter whose SDK reads the body itself\n * (MCP's Streamable HTTP transport does) can do so. The adapter owns reading,\n * size-limiting beyond the gateway's own cap, and parsing.\n *\n * This is load-bearing and easy to break silently. Fastify pre-registers\n * exact-match parsers for `application/json` and `text/plain` and prefers them\n * over a `'*'` wildcard, so registering only a wildcard no-op leaves the\n * built-in JSON parser draining the body first — the adapter then reads an\n * empty stream and every request fails to parse. That exact regression shipped\n * once and no unit test caught it, because adapter conformance suites drive\n * `handleHttp` over a bare `node:http` server and never traverse the gateway.\n *\n * Any change to how adapters are mounted must be covered by a test that POSTs\n * a real JSON body through the real gateway and asserts the adapter received\n * it intact.\n */\nexport interface HttpProtocolAdapter extends ProtocolAdapter {\n /** Path prefix the gateway mounts this adapter at, e.g. '/mcp'. */\n readonly mountPath: string;\n handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void>;\n}\n\nexport function isHttpProtocolAdapter(adapter: ProtocolAdapter): adapter is HttpProtocolAdapter {\n return typeof (adapter as HttpProtocolAdapter).handleHttp === 'function';\n}\n","/**\n * Injected runtime primitives.\n *\n * Everything non-deterministic (time, ids) is injected so tests can be exact.\n */\nimport type { IsoTimestamp } from '../domain/common.js';\n\nexport interface Clock {\n now(): Date;\n nowIso(): IsoTimestamp;\n /** Milliseconds since an arbitrary origin; use for durations only. */\n monotonicMs(): number;\n}\n\nexport interface IdGenerator {\n /** Opaque, collision-resistant identifier. */\n next(prefix?: string): string;\n}\n\nexport const systemClock: Clock = {\n now: () => new Date(),\n nowIso: () => new Date().toISOString(),\n monotonicMs: () => performance.now(),\n};\n","/**\n * The package version, resolved once for the whole package.\n *\n * Two paths, deliberately:\n *\n * - **Development** reads the root manifest, so the number is always live.\n * - **The published bundle cannot.** tsup collapses `src/**` into\n * `dist/index.js` and `dist/cli/index.js`, so any `require('../../package.json')`\n * written against a source path resolves from the wrong depth once bundled —\n * which is exactly how this broke during the single-package refactor, and how\n * it earlier produced a CLI reporting `v0.0.0-unknown`. The build injects\n * `__OAC_PACKAGE_VERSION__` instead (see tsup.config.ts).\n *\n * `typeof` on an undeclared identifier is safe in JS, so the dev path is\n * unaffected by the constant not existing.\n */\nimport { createRequire } from 'node:module';\n\ndeclare const __OAC_PACKAGE_VERSION__: string | undefined;\n\nfunction readFromManifest(): string {\n try {\n const require = createRequire(import.meta.url);\n // src/version.ts -> repository root\n return (require('../package.json') as { version?: string }).version ?? '0.0.0-unknown';\n } catch {\n return '0.0.0-unknown';\n }\n}\n\nexport const PACKAGE_VERSION: string =\n typeof __OAC_PACKAGE_VERSION__ === 'string' ? __OAC_PACKAGE_VERSION__ : readFromManifest();\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommerceError } from './chunk-
|
|
1
|
+
import { CommerceError } from './chunk-BQNE3A5H.js';
|
|
2
2
|
|
|
3
3
|
// src/payments/x402/networks.ts
|
|
4
4
|
var PROFILES = [
|
|
@@ -7,14 +7,24 @@ var PROFILES = [
|
|
|
7
7
|
chainId: 84532,
|
|
8
8
|
displayName: "Base Sepolia",
|
|
9
9
|
kind: "testnet",
|
|
10
|
-
canonicalAsset: {
|
|
10
|
+
canonicalAsset: {
|
|
11
|
+
symbol: "USDC",
|
|
12
|
+
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
13
|
+
name: "USDC",
|
|
14
|
+
version: "2"
|
|
15
|
+
}
|
|
11
16
|
},
|
|
12
17
|
{
|
|
13
18
|
id: "eip155:8453",
|
|
14
19
|
chainId: 8453,
|
|
15
20
|
displayName: "Base",
|
|
16
21
|
kind: "mainnet",
|
|
17
|
-
canonicalAsset: {
|
|
22
|
+
canonicalAsset: {
|
|
23
|
+
symbol: "USDC",
|
|
24
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
25
|
+
name: "USD Coin",
|
|
26
|
+
version: "2"
|
|
27
|
+
}
|
|
18
28
|
}
|
|
19
29
|
];
|
|
20
30
|
var BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
|
|
@@ -83,11 +93,16 @@ function parseHostnameOrThrow(rpcUrl) {
|
|
|
83
93
|
try {
|
|
84
94
|
return new URL(rpcUrl).hostname;
|
|
85
95
|
} catch (cause) {
|
|
86
|
-
throw new CommerceError(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
throw new CommerceError("CONFIG_INVALID", "x402 provider: rpcUrl is not a valid URL", {
|
|
97
|
+
cause
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function describeRpc(rpcUrl) {
|
|
102
|
+
try {
|
|
103
|
+
return new URL(rpcUrl).origin;
|
|
104
|
+
} catch {
|
|
105
|
+
return "[unparseable rpcUrl]";
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
function assertDevKeyIsLocalOnly(rpcUrl, signerPrivateKey) {
|
|
@@ -102,7 +117,7 @@ function assertDevKeyIsLocalOnly(rpcUrl, signerPrivateKey) {
|
|
|
102
117
|
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
103
118
|
throw new CommerceError(
|
|
104
119
|
"CONFIG_INVALID",
|
|
105
|
-
`x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl
|
|
120
|
+
`x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl ${describeRpc(rpcUrl)} does not look like a local/private chain. A public dev key must never sign against a public network \u2014 it is instantly drainable and can grief real settlements via nonce exhaustion. Use a real facilitator key for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
106
121
|
);
|
|
107
122
|
}
|
|
108
123
|
}
|
|
@@ -112,7 +127,7 @@ function assertPayToIsNotDevAddress(rpcUrl, payTo) {
|
|
|
112
127
|
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
113
128
|
throw new CommerceError(
|
|
114
129
|
"CONFIG_INVALID",
|
|
115
|
-
`x402 provider: "payTo" (${payTo}) is a well-known Anvil development address, but rpcUrl
|
|
130
|
+
`x402 provider: "payTo" (${payTo}) is a well-known Anvil development address, but rpcUrl ${describeRpc(rpcUrl)} does not look like a local/private chain. The private key behind that address is public knowledge, so any revenue settled to it is immediately spendable by anyone. Set "payTo" to your own merchant wallet for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
116
131
|
);
|
|
117
132
|
}
|
|
118
133
|
}
|
|
@@ -138,19 +153,27 @@ function resolveX402Deployment(input) {
|
|
|
138
153
|
}
|
|
139
154
|
if (input.facilitator.mode === "remote") {
|
|
140
155
|
assertFacilitatorUrlIsSafe(input.facilitator.url, mode);
|
|
141
|
-
if (mode === "mainnet" && input.facilitator.auth.type === "none") {
|
|
156
|
+
if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
|
|
142
157
|
throw invalid(
|
|
143
|
-
|
|
144
|
-
"payments.x402.
|
|
158
|
+
`payments.x402: facilitator ${describeOrigin(input.facilitator.url)} takes no credential, and this is a mainnet deployment. It will see every payment authorisation you handle, with no account, terms or support behind it. Set payments.x402.allowUnauthenticatedFacilitator: true to accept that, or configure facilitator.auth.`,
|
|
159
|
+
"payments.x402.allowUnauthenticatedFacilitator"
|
|
145
160
|
);
|
|
146
161
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
for (const [field, value] of credentialFields(input.facilitator.auth)) {
|
|
163
|
+
if (value.trim() === "") {
|
|
164
|
+
throw invalid(
|
|
165
|
+
`payments.x402: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
|
|
166
|
+
`payments.x402.facilitator.auth.${field}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
152
169
|
}
|
|
153
170
|
}
|
|
171
|
+
if (/^0x0{40}$/i.test(input.payTo)) {
|
|
172
|
+
throw invalid(
|
|
173
|
+
'payments.x402: "payTo" is the zero address. Every payment settled there is destroyed.',
|
|
174
|
+
"payments.x402.payTo"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
154
177
|
if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
|
|
155
178
|
throw invalid(
|
|
156
179
|
`payments.x402: "payTo" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set payTo to your own merchant wallet.`,
|
|
@@ -158,11 +181,25 @@ function resolveX402Deployment(input) {
|
|
|
158
181
|
);
|
|
159
182
|
}
|
|
160
183
|
const canonical = profile.canonicalAsset;
|
|
161
|
-
if (mode === "mainnet" && canonical
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
if (mode === "mainnet" && canonical) {
|
|
185
|
+
if (!sameAddress(input.asset, canonical.address)) {
|
|
186
|
+
throw invalid(
|
|
187
|
+
`payments.x402: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,
|
|
188
|
+
"payments.x402.asset"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (input.assetName !== void 0 && input.assetName !== canonical.name) {
|
|
192
|
+
throw invalid(
|
|
193
|
+
`payments.x402: assetName "${input.assetName}" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.name}"). Every payment would be refused after the buyer signed.`,
|
|
194
|
+
"payments.x402.assetName"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (input.assetVersion !== void 0 && input.assetVersion !== canonical.version) {
|
|
198
|
+
throw invalid(
|
|
199
|
+
`payments.x402: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.version}").`,
|
|
200
|
+
"payments.x402.assetVersion"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
166
203
|
}
|
|
167
204
|
return { profile, mode };
|
|
168
205
|
}
|
|
@@ -171,29 +208,48 @@ function assertFacilitatorUrlIsSafe(url, mode) {
|
|
|
171
208
|
try {
|
|
172
209
|
parsed = new URL(url);
|
|
173
210
|
} catch (cause) {
|
|
174
|
-
throw new CommerceError(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
211
|
+
throw new CommerceError("CONFIG_INVALID", `payments.x402: facilitator.url is not a valid URL`, {
|
|
212
|
+
cause,
|
|
213
|
+
details: { path: "payments.x402.facilitator.url" }
|
|
214
|
+
});
|
|
179
215
|
}
|
|
180
216
|
if (parsed.protocol === "https:") return;
|
|
181
217
|
if (parsed.protocol !== "http:") {
|
|
182
218
|
throw invalid(
|
|
183
|
-
`payments.x402: facilitator
|
|
219
|
+
`payments.x402: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
|
|
184
220
|
"payments.x402.facilitator.url"
|
|
185
221
|
);
|
|
186
222
|
}
|
|
187
223
|
if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
|
|
188
224
|
throw invalid(
|
|
189
|
-
`payments.x402: facilitator
|
|
225
|
+
`payments.x402: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
|
|
190
226
|
"payments.x402.facilitator.url"
|
|
191
227
|
);
|
|
192
228
|
}
|
|
229
|
+
function describeOrigin(url) {
|
|
230
|
+
try {
|
|
231
|
+
return new URL(url).origin;
|
|
232
|
+
} catch {
|
|
233
|
+
return "[unparseable facilitator.url]";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function credentialFields(auth) {
|
|
237
|
+
switch (auth.type) {
|
|
238
|
+
case "bearer":
|
|
239
|
+
return [["token", auth.token]];
|
|
240
|
+
case "cdp":
|
|
241
|
+
return [
|
|
242
|
+
["apiKeyId", auth.apiKeyId],
|
|
243
|
+
["apiKeySecret", auth.apiKeySecret]
|
|
244
|
+
];
|
|
245
|
+
default:
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
193
249
|
function sameAddress(a, b) {
|
|
194
250
|
return a.toLowerCase() === b.toLowerCase();
|
|
195
251
|
}
|
|
196
252
|
|
|
197
253
|
export { SUPPORTED_NETWORK_IDS, assertDevKeyIsLocalOnly, assertPayToIsNotDevAddress, describeDeploymentMode, requireNetworkProfile, resolveDeploymentMode, resolveX402Deployment };
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
254
|
+
//# sourceMappingURL=chunk-MEPETSP6.js.map
|
|
255
|
+
//# sourceMappingURL=chunk-MEPETSP6.js.map
|