xytara 3.1.12 → 3.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -98,6 +98,11 @@ XYTARA_PROVIDER_FAILOVER_MAX=3
98
98
  # routing reputation recency decay, factor in (0,1): fades older observations so recent performance
99
99
  # weighs more. 1 (default) = no decay (lifetime counts). e.g. 0.95 once the network has real history.
100
100
  XYTARA_PROVIDER_REPUTATION_DECAY=1
101
+ # Signed delivery receipts (one per paid call; public keys at GET /.well-known/delivery-receipt-keys).
102
+ # base64 PKCS#8 DER (or PEM) private keys. Ed25519 is the base signature; ML-DSA-65 adds the
103
+ # post-quantum leg (needs Node >= 24). Unset -> receipts are still issued but honestly marked unsigned.
104
+ XYTARA_DELIVERY_RECEIPT_ED25519_KEY=
105
+ XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY=
101
106
  # Provider payout auto-execution via an EXTERNAL signer (the runtime stays key-less). OFF unless the
102
107
  # URL is set; otherwise payouts are recorded and the operator sends + confirms them. See services/payout-signer.
103
108
  XYTARA_PAYOUT_SIGNER_URL=
package/README.md CHANGED
@@ -4,7 +4,8 @@ Listed on [Smithery](https://smithery.ai/server/naxytra/xytara-mcp-executor) ·
4
4
 
5
5
  **x402 pay-per-call agent-commerce runtime** — the payment and execution layer of the naxytra
6
6
  machine stack. An agent discovers a capability, pays once with x402 (gasless USDC on Base), and gets
7
- a real deliverable, with settlement and revenue reconciled on-chain. The proof-side half is
7
+ a real deliverable, settled on-chain, with the revenue split reconciled in the ledger and paid out
8
+ on-chain, plus a signed delivery receipt. The proof-side half is
8
9
  [xoonya](https://naxytra.com/xoonya).
9
10
 
10
11
  ## Proven live on Base mainnet
@@ -13,8 +14,8 @@ All demonstrated end to end on Base mainnet (provider-payout transactions are pu
13
14
  see **Proof** below):
14
15
 
15
16
  - **Open settling provider network** — a paid x402 call routes to an independently-registered
16
- execution provider, returns a real deliverable, and splits revenue on-chain (reconciled ledger,
17
- platform keeps the remainder).
17
+ execution provider, returns a real deliverable, and splits revenue — reconciled in the ledger and
18
+ paid out on-chain (platform keeps the remainder).
18
19
  - **Automated on-chain provider payouts** — a key-less runtime dispatches to an external signer that
19
20
  sends USDC on Base; reconciled, idempotent, double-pay-safe.
20
21
  - **Cryptographic bounded delegated spend** — a stolen bearer token cannot spend; each spend requires
@@ -30,10 +31,32 @@ endpoint: https://xytara-mcp-executor.onrender.com/mcp
30
31
  tools/list -> free (17 deterministic tools)
31
32
  tools/call -> 402 Payment Required with a price
32
33
  -> sign ONE gasless USDC authorization (x402 / EIP-3009)
33
- -> the Coinbase CDP facilitator verifies + settles it on Base
34
- -> the tool runs; the response carries the result + an on-chain settlement receipt
34
+ -> the Coinbase CDP facilitator verifies it; the tool runs
35
+ -> only if the tool succeeds is the payment settled on Base (a failed call is not charged)
36
+ -> the response carries the result, the on-chain settlement receipt, and a signed
37
+ delivery receipt in result._meta["naxytra.com/delivery-receipt"]
35
38
  ```
36
39
 
40
+ ## Signed delivery receipts
41
+
42
+ Every paid call returns a `naxytra-delivery-receipt/1`: the payment leg (for x402, the network,
43
+ asset, amount, payer, pay_to, and settlement transaction), the provider that delivered, and the
44
+ SHA-256 of the canonical request and of the delivered output — signed over its JCS (RFC 8785)
45
+ canonical form with Ed25519, plus ML-DSA-65 (FIPS-204) where the issuer has a post-quantum key. The
46
+ signed suite is part of the signed bytes, so stripping a signature fails verification.
47
+
48
+ ```
49
+ GET /.well-known/delivery-receipt-keys # the issuer's public keys (pin them)
50
+ GET /v1/delivery-receipts/:receipt_id # fetch a receipt the runtime issued
51
+ POST /v1/delivery-receipts/verify # free signature check
52
+ ```
53
+
54
+ Verify independently: recompute `sha256(JCS(output you received))`, check the settlement
55
+ transaction on-chain, and verify each signature over the canonical bytes with `node:crypto` or
56
+ xoonya's `verifyExternalProofSignature` (the receipt's `kid` is the key ref xoonya reports).
57
+ A receipt proves who delivered what for which paid call, unaltered since signing — **not** whether
58
+ the output was correct or good.
59
+
37
60
  Tools: `summarize`, `text.extract`, `html.to-text`, `text.chunk`, `json.validate`, `zones.lookup`,
38
61
  `keccak256`, `address.validate`, `abi.encode`, `abi.decode`, `merkle.verify`, `eip712.hash`,
39
62
  `x402.decode`, `x402.verify-receipt`, `tx.status`, `reply.draft`, `catalog`.
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,5 @@
1
- `xytara` 3.1.12 makes the public adapter-depth surface report open-network providers truthfully.
1
+ `xytara` 3.1.13 adds signed delivery receipts and makes the direct MCP path charge only on success.
2
2
 
3
- - `/v1/adapter-depth` and `/v1/adapter-depth/summary` now count ACTIVE open-network providers that declared an adapter task as provider-live paths, alongside operator env targets. Each is labelled `routing: open_provider_registry`, and the pack lists its `depth_sources`. A pending provider never counts, and a provider is shown as shadowed (not counted) when an operator env target pins the whole lane, because env targets are routed first.
4
- - Previously the surface only looked at env targets, so it reported `provider_wiring_required` even while paid MCP calls were being served by the live registry provider. It now reports `initial_provider_live_path` for the task that provider serves.
5
- - Internal callers without runtime state keep the env-only view, so no other surface changes.
3
+ - **Signed delivery receipts** (`naxytra-delivery-receipt/1`) on every paid call, from the runtime and from the MCP executor. A receipt binds the payment leg (for x402: network, asset, amount, payer, pay_to, settlement transaction), the provider that delivered, and the SHA-256 of the canonical request and of the delivered output. It is signed over its JCS (RFC 8785) canonical form with Ed25519, plus ML-DSA-65 (FIPS-204) where the issuer has a post-quantum key; the signed suite is inside the signed bytes, so stripping a signature fails verification. Public keys: `GET /.well-known/delivery-receipt-keys`; runtime receipts are retrievable at `GET /v1/delivery-receipts/:id`; free check at `POST /v1/delivery-receipts/verify`. Key ids use the xoonya key-ref format, so xoonya's verifier checks them too. Without a configured key a receipt is still issued and marked unsigned — never a fake signature. Boundary: a receipt proves who delivered what for which paid call, unaltered — not whether the output was correct.
4
+ - **Real x402 settlement records** — for an x402-paid call the runtime's settlement, treasury, and default-path records now carry the x402 rail, network, amount, payer, pay_to, and settlement transaction, instead of the quote's unsubmitted default rail.
5
+ - **MCP executor charges only on success** — `tools/call` now verifies the payment, runs the tool, and settles only if the call succeeded. A tool error, unknown tool, or bad params is never charged; if settlement fails after the work, the output is withheld and the buyer is not charged. A concurrent replay of the same signed authorization is refused (`409 payment_authorization_in_use`).
@@ -11,6 +11,95 @@ const { validateIntegrationSelection } = require("../integrations/dispatch");
11
11
  const { markPaymentAccepted, settleDeferredPayment, verifyPaymentAcceptance } = require("./payment_verification");
12
12
  const { executeExternalTarget, resolveExecutionTarget } = require("./external_execution_runtime");
13
13
  const { externalExecutionBudgetMs } = require("./external_execution_policy");
14
+ const { hashCanonical, issueDeliveryReceipt, loadDeliveryReceiptSigners } = require("./delivery_receipt");
15
+
16
+ const DELIVERY_RECEIPT_ISSUER = "xytara-runtime";
17
+ const DELIVERY_RECEIPT_RETENTION = 5000;
18
+
19
+ // The payment leg of a delivery receipt: on-chain facts for x402, the spend record for credits,
20
+ // and the verification mode for any other lane. Never the buyer's signature or secrets.
21
+ function deliveryReceiptPayment(verification, quote) {
22
+ const v = verification && typeof verification === "object" ? verification : {};
23
+ const paymentPayload = v.payment_payload && typeof v.payment_payload === "object" ? v.payment_payload : {};
24
+ if (v.verification_mode === "x402_facilitator") {
25
+ const settlement = v.settlement && typeof v.settlement === "object" ? v.settlement : null;
26
+ return {
27
+ protocol: "x402",
28
+ scheme: "exact",
29
+ network: paymentPayload.x402_network || (settlement && settlement.network) || null,
30
+ asset: paymentPayload.x402_asset || null,
31
+ amount: paymentPayload.x402_amount !== undefined && paymentPayload.x402_amount !== null ? String(paymentPayload.x402_amount) : null,
32
+ payer: paymentPayload.x402_payer || (settlement && settlement.payer) || null,
33
+ pay_to: paymentPayload.x402_pay_to || null,
34
+ settlement_tx: settlement ? (settlement.transaction || settlement.txid || null) : null,
35
+ settled: Boolean(settlement && settlement.success !== false && (settlement.transaction || settlement.txid))
36
+ };
37
+ }
38
+ if (v.verification_mode === "account_credits" || paymentPayload.credit_spend_id) {
39
+ return {
40
+ protocol: "account_credits",
41
+ credit_spend_id: paymentPayload.credit_spend_id || null,
42
+ quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
43
+ currency: (quote && quote.currency) || null
44
+ };
45
+ }
46
+ return {
47
+ protocol: v.verification_mode || "unspecified",
48
+ quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
49
+ currency: (quote && quote.currency) || null
50
+ };
51
+ }
52
+
53
+ // Issues the signed delivery receipt for a completed paid call, attaches it to the response, and
54
+ // keeps it retrievable by id (bounded). Fail-safe: a receipt problem never breaks a completed call.
55
+ function attachDeliveryReceipt(state, result, payload, quote, verification, externalExecutionOptions) {
56
+ try {
57
+ if (!result || !result.receipt) return;
58
+ const execution = externalExecutionOptions && externalExecutionOptions.externalExecution
59
+ ? externalExecutionOptions.externalExecution
60
+ : null;
61
+ const deliverables = result.command && Array.isArray(result.command.deliverables) ? result.command.deliverables : [];
62
+ const outputs = deliverables.map((deliverable) => (deliverable && deliverable.output_payload !== undefined ? deliverable.output_payload : null));
63
+ const body = payload && payload.body && typeof payload.body === "object" && !Array.isArray(payload.body) ? payload.body : {};
64
+ const taskRef = (payload && payload.task_ref)
65
+ || (Array.isArray(payload && payload.tasks) && payload.tasks[0] && payload.tasks[0].task_ref)
66
+ || null;
67
+ const { signers } = loadDeliveryReceiptSigners("XYTARA_DELIVERY_RECEIPT");
68
+ const deliveryReceipt = issueDeliveryReceipt({
69
+ receipt_id: `drcpt_${result.receipt.receipt_id.replace(/^rcpt_/, "")}`,
70
+ issuer: DELIVERY_RECEIPT_ISSUER,
71
+ call: {
72
+ quote_id: quote && quote.quote_id ? quote.quote_id : null,
73
+ transaction_id: result.transaction ? result.transaction.transaction_id : null,
74
+ runtime_receipt_id: result.receipt.receipt_id,
75
+ task_ref: taskRef,
76
+ tool_name: typeof body.tool_name === "string" ? body.tool_name : null
77
+ },
78
+ payment: deliveryReceiptPayment(verification, quote),
79
+ delivery: {
80
+ status: "completed",
81
+ provider_id: execution && execution.registered_provider_id ? execution.registered_provider_id : null,
82
+ routing: execution
83
+ ? (execution.registered_provider_id ? "open_provider_registry" : "operator_env_target")
84
+ : "runtime_native",
85
+ execution_target_id: execution && execution.target_id ? execution.target_id : null,
86
+ request_sha256: hashCanonical(body),
87
+ request_hash_input: "the request `body` object (e.g. { tool_name, arguments })",
88
+ output_sha256: hashCanonical(outputs.length === 1 ? outputs[0] : outputs),
89
+ output_hash_input: outputs.length === 1
90
+ ? "command.deliverables[0].output_payload (null when the task returns no payload)"
91
+ : "the array of command.deliverables[*].output_payload",
92
+ completed_at: (result.receipt.timing && result.receipt.timing.completed_at_iso) || result.receipt.created_at_iso || null
93
+ }
94
+ }, signers);
95
+ result.delivery_receipt = deliveryReceipt;
96
+ if (!(state.deliveryReceipts instanceof Map)) state.deliveryReceipts = new Map();
97
+ state.deliveryReceipts.set(deliveryReceipt.receipt_id, deliveryReceipt);
98
+ while (state.deliveryReceipts.size > DELIVERY_RECEIPT_RETENTION) {
99
+ state.deliveryReceipts.delete(state.deliveryReceipts.keys().next().value);
100
+ }
101
+ } catch (_) { /* non-fatal: never break a completed, possibly settled call */ }
102
+ }
14
103
  const { resolveRegisteredTargets, hasActiveMatchingProvider, recordProviderHealth, recordProviderRouting, recordProviderSettlement } = require("./provider_registry");
15
104
  const { claimDeferredExecution, releaseDeferredExecution } = require("./execution_dedupe");
16
105
 
@@ -364,17 +453,19 @@ async function executeCommandRequest(state, payload, headers, options) {
364
453
  attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
365
454
  transaction_id: creditSpend.credit_spend.credit_spend_id
366
455
  }, paymentVerification);
456
+ const creditResult = createCommandResult(state, {
457
+ ...payload,
458
+ payment_preference: "credits_first"
459
+ }, quote, {
460
+ paymentVerification,
461
+ creditSpend: creditSpend.credit_spend,
462
+ skipPaymentLedger: true,
463
+ externalExecution: externalExecutionOptions.externalExecution || null
464
+ });
465
+ attachDeliveryReceipt(state, creditResult, payload, quote, paymentVerification, externalExecutionOptions);
367
466
  return {
368
467
  status: 200,
369
- payload: createCommandResult(state, {
370
- ...payload,
371
- payment_preference: "credits_first"
372
- }, quote, {
373
- paymentVerification,
374
- creditSpend: creditSpend.credit_spend,
375
- skipPaymentLedger: true,
376
- externalExecution: externalExecutionOptions.externalExecution || null
377
- })
468
+ payload: creditResult
378
469
  };
379
470
  }
380
471
  if (creditSpend.reason !== "insufficient_credits") {
@@ -551,12 +642,14 @@ async function executeCommandRequest(state, payload, headers, options) {
551
642
  attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
552
643
  settlement_ref: (settledVerification.settlement && (settledVerification.settlement.transaction || settledVerification.settlement.txid)) || null
553
644
  }, settledVerification);
645
+ const paidResult = createCommandResult(state, payload, quote, {
646
+ paymentVerification: settledVerification,
647
+ externalExecution: externalExecutionOptions.externalExecution || null
648
+ });
649
+ attachDeliveryReceipt(state, paidResult, payload, quote, settledVerification, externalExecutionOptions);
554
650
  return {
555
651
  status: 200,
556
- payload: createCommandResult(state, payload, quote, {
557
- paymentVerification: settledVerification,
558
- externalExecution: externalExecutionOptions.externalExecution || null
559
- }),
652
+ payload: paidResult,
560
653
  ...(settledVerification.payment_response_base64
561
654
  ? { paymentResponse: settledVerification.payment_response_base64 }
562
655
  : {})
@@ -609,12 +702,14 @@ async function executeAuthorizedCommandRequest(state, payload, authorizationCont
609
702
  }
610
703
 
611
704
  attributeProviderSettlement(state, externalExecutionOptions, payload, quote, undefined, paymentVerification);
705
+ const authorizedResult = createCommandResult(state, payload, quote, {
706
+ paymentVerification,
707
+ externalExecution: externalExecutionOptions.externalExecution || null
708
+ });
709
+ attachDeliveryReceipt(state, authorizedResult, payload, quote, paymentVerification, externalExecutionOptions);
612
710
  return {
613
711
  status: 200,
614
- payload: createCommandResult(state, payload, quote, {
615
- paymentVerification,
616
- externalExecution: externalExecutionOptions.externalExecution || null
617
- })
712
+ payload: authorizedResult
618
713
  };
619
714
  }
620
715
 
@@ -26,6 +26,26 @@ function assessLatency(declaredLatencyClass, observedLatencyClass) {
26
26
  return observed <= declared ? "within_posture" : "outside_posture";
27
27
  }
28
28
 
29
+ // The x402 facts of a payment verified (and, once settled, settled) through the facilitator, or
30
+ // null for any other lane. For an x402-paid call these replace the quote's default settlement
31
+ // rail in the artifacts, so a consumer never reads an unsubmitted placeholder rail as the real one.
32
+ function x402PaymentFacts(paymentVerification) {
33
+ const verification = paymentVerification && typeof paymentVerification === "object" ? paymentVerification : null;
34
+ if (!verification || verification.verification_mode !== "x402_facilitator") return null;
35
+ const paymentPayload = verification.payment_payload && typeof verification.payment_payload === "object" ? verification.payment_payload : {};
36
+ const settlement = verification.settlement && typeof verification.settlement === "object" ? verification.settlement : null;
37
+ const txid = settlement && (settlement.transaction || settlement.txid) ? String(settlement.transaction || settlement.txid) : null;
38
+ return {
39
+ network: paymentPayload.x402_network || (settlement && settlement.network) || null,
40
+ asset: paymentPayload.x402_asset || null,
41
+ amount: paymentPayload.x402_amount !== undefined && paymentPayload.x402_amount !== null ? String(paymentPayload.x402_amount) : null,
42
+ payer: paymentPayload.x402_payer || (settlement && settlement.payer) || verification.wallet_id || null,
43
+ pay_to: paymentPayload.x402_pay_to || null,
44
+ txid,
45
+ settled: Boolean(settlement && settlement.success !== false && txid)
46
+ };
47
+ }
48
+
29
49
  function createCommandArtifacts(payload, quote, options) {
30
50
  const opts = options || {};
31
51
  const transactionId = `txn_${crypto.randomBytes(6).toString("hex")}`;
@@ -241,28 +261,61 @@ function createCommandArtifacts(payload, quote, options) {
241
261
  ? opts.paymentVerification.payment_payload
242
262
  : null
243
263
  );
244
- const settlement = {
245
- settlement_id: `settlement_${transactionId}`,
246
- mode: quote.settlement_mode || null,
247
- preference: quote.settlement_preference || null,
248
- adapter: quote.settlement_adapter || null,
249
- integration_id: quote.settlement_integration_id || null,
250
- binding_hash: quote.settlement_binding_hash || null,
251
- rail: isBsvTeranodeSettlementMode(quote.settlement_mode) ? "bsv_teranode" : null,
252
- network: bsvHints ? bsvHints.network : null,
253
- hints: bsvHints,
254
- txid: bsvPaymentReference && bsvPaymentReference.txid ? bsvPaymentReference.txid : null,
255
- raw_tx: bsvPaymentReference && (bsvPaymentReference.raw_tx || bsvPaymentReference.rawTx)
256
- ? (bsvPaymentReference.raw_tx || bsvPaymentReference.rawTx)
257
- : null,
258
- status: deferredCompletion ? "pending_completion" : "recorded",
259
- recorded_at_iso: deferredCompletion ? null : completedAt.toISOString()
260
- };
261
- const treasuryDestination = buildTreasuryDestination(quote.account_id, quote.settlement_mode);
264
+ const x402 = x402PaymentFacts(opts.paymentVerification);
265
+ const settlement = x402
266
+ ? {
267
+ settlement_id: `settlement_${transactionId}`,
268
+ mode: "x402_facilitator",
269
+ preference: quote.settlement_preference || null,
270
+ adapter: "x402_facilitator",
271
+ integration_id: "builtin.payment.x402",
272
+ binding_hash: quote.settlement_binding_hash || null,
273
+ rail: "x402",
274
+ network: x402.network,
275
+ asset: x402.asset,
276
+ amount: x402.amount,
277
+ payer: x402.payer,
278
+ pay_to: x402.pay_to,
279
+ hints: null,
280
+ txid: x402.txid,
281
+ raw_tx: null,
282
+ status: x402.settled ? "settled" : deferredCompletion ? "pending_completion" : "verified_not_settled",
283
+ recorded_at_iso: deferredCompletion ? null : completedAt.toISOString()
284
+ }
285
+ : {
286
+ settlement_id: `settlement_${transactionId}`,
287
+ mode: quote.settlement_mode || null,
288
+ preference: quote.settlement_preference || null,
289
+ adapter: quote.settlement_adapter || null,
290
+ integration_id: quote.settlement_integration_id || null,
291
+ binding_hash: quote.settlement_binding_hash || null,
292
+ rail: isBsvTeranodeSettlementMode(quote.settlement_mode) ? "bsv_teranode" : null,
293
+ network: bsvHints ? bsvHints.network : null,
294
+ hints: bsvHints,
295
+ txid: bsvPaymentReference && bsvPaymentReference.txid ? bsvPaymentReference.txid : null,
296
+ raw_tx: bsvPaymentReference && (bsvPaymentReference.raw_tx || bsvPaymentReference.rawTx)
297
+ ? (bsvPaymentReference.raw_tx || bsvPaymentReference.rawTx)
298
+ : null,
299
+ status: deferredCompletion ? "pending_completion" : "recorded",
300
+ recorded_at_iso: deferredCompletion ? null : completedAt.toISOString()
301
+ };
302
+ const treasuryDestination = x402
303
+ ? {
304
+ destination_id: `x402_${x402.network || "network"}_${x402.pay_to || "pay_to"}`,
305
+ destination_kind: "x402_pay_to_address",
306
+ account_id: quote.account_id,
307
+ settlement_mode: "x402_facilitator",
308
+ external_ref: x402.pay_to,
309
+ network: x402.network,
310
+ rail: "x402"
311
+ }
312
+ : buildTreasuryDestination(quote.account_id, quote.settlement_mode);
262
313
  const treasury = {
263
314
  ...treasuryDestination,
264
315
  treasury_receipt_id: `treasury_${receiptId}`,
265
- status: deferredCompletion ? "pending_settlement" : "recorded",
316
+ status: x402
317
+ ? (x402.settled ? "settled" : deferredCompletion ? "pending_settlement" : "recorded")
318
+ : deferredCompletion ? "pending_settlement" : "recorded",
266
319
  amount_minor: quote.amount_minor,
267
320
  currency: quote.currency,
268
321
  payment_id: payment.payment_id,
@@ -271,7 +324,7 @@ function createCommandArtifacts(payload, quote, options) {
271
324
  rail: treasuryDestination.rail || settlement.rail || null,
272
325
  recorded_at_iso: deferredCompletion ? null : completedAt.toISOString()
273
326
  };
274
- const defaultPath = quote.default_path || buildDefaultEconomicPath(
327
+ const quotedDefaultPath = quote.default_path || buildDefaultEconomicPath(
275
328
  Array.isArray(quote.integration_ids)
276
329
  ? quote.integration_ids.find((integrationId) => typeof integrationId === "string" && integrationId.startsWith("builtin.payment."))
277
330
  : null,
@@ -283,6 +336,14 @@ function createCommandArtifacts(payload, quote, options) {
283
336
  status: deferredCompletion ? "pending_completion" : "recorded"
284
337
  }
285
338
  );
339
+ const defaultPath = x402
340
+ ? {
341
+ ...quotedDefaultPath,
342
+ payment_protocol: "x402",
343
+ settlement_mode: "x402_facilitator",
344
+ treasury_destination_id: treasuryDestination.destination_id
345
+ }
346
+ : quotedDefaultPath;
286
347
  const transaction = {
287
348
  transaction_id: transactionId,
288
349
  account_id: quote.account_id,
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+
3
+ // Signed delivery receipts ("naxytra-delivery-receipt/1"). One per paid call, binding WHAT was
4
+ // paid (the x402 settlement or other payment fact), WHO delivered (provider), and WHAT was
5
+ // delivered (sha256 of the canonical request and of the canonical output) — signed so anyone can
6
+ // re-verify it offline against the issuer's published keys.
7
+ //
8
+ // Boundary: a valid receipt proves this issuer attests that this provider returned this exact
9
+ // output for this paid call, unaltered since signing. It does NOT judge whether the output was
10
+ // correct or good — that is the consumer's (e.g. an arbitrator's) decision.
11
+ //
12
+ // Signed bytes = JCS (RFC 8785) canonical JSON of the receipt WITHOUT its `sig` member.
13
+ // Signatures = one per algorithm in `signing.suite` (Ed25519 always; ML-DSA-65 when configured
14
+ // and the runtime supports it). The suite and key ids are INSIDE the signed bytes,
15
+ // so stripping a signature (downgrade) fails verification.
16
+ // Key ids = "<key type>-spki-sha256:<hex>", the same key-ref format the xoonya verifier
17
+ // reports, so a receipt verifies with xoonya's verifyExternalProofSignature too.
18
+ //
19
+ // Dependency-free (node:crypto). This file is shared verbatim by the xytara runtime
20
+ // (lib/delivery_receipt.js) and the MCP executor (services/mcp-execution-target/delivery_receipt.js);
21
+ // a verifier asserts the two copies stay byte-identical.
22
+
23
+ const crypto = require("crypto");
24
+
25
+ const RECEIPT_VERSION = "naxytra-delivery-receipt/1";
26
+ const CANONICALIZATION = "JCS (RFC 8785) of the receipt without `sig`";
27
+ const SUPPORTED_ALGS = { ed25519: "EdDSA", "ml-dsa-65": "ML-DSA-65" };
28
+
29
+ // JCS for the JSON values a receipt carries (strings, finite numbers, booleans, null, arrays,
30
+ // objects). Object keys sorted by UTF-16 code units; `undefined` members dropped like JSON.
31
+ function canonicalize(value) {
32
+ if (value === null || typeof value !== "object") {
33
+ if (typeof value === "number" && !Number.isFinite(value)) return "null";
34
+ return value === undefined ? "null" : JSON.stringify(value);
35
+ }
36
+ if (Array.isArray(value)) return "[" + value.map((item) => canonicalize(item === undefined ? null : item)).join(",") + "]";
37
+ return "{" + Object.keys(value)
38
+ .filter((key) => value[key] !== undefined)
39
+ .sort()
40
+ .map((key) => JSON.stringify(key) + ":" + canonicalize(value[key]))
41
+ .join(",") + "}";
42
+ }
43
+
44
+ function sha256Hex(input) {
45
+ return crypto.createHash("sha256").update(Buffer.isBuffer(input) ? input : Buffer.from(String(input), "utf8")).digest("hex");
46
+ }
47
+
48
+ // The hash a consumer recomputes from the JSON value they hold (request body or delivered output).
49
+ function hashCanonical(value) {
50
+ return sha256Hex(canonicalize(value === undefined ? null : value));
51
+ }
52
+
53
+ function keyRef(publicKey) {
54
+ const spki = publicKey.export({ type: "spki", format: "der" });
55
+ return `${publicKey.asymmetricKeyType}-spki-sha256:${sha256Hex(spki)}`;
56
+ }
57
+
58
+ function parsePrivateKey(raw) {
59
+ const text = String(raw || "").trim();
60
+ if (!text) return null;
61
+ if (text.includes("BEGIN ")) return crypto.createPrivateKey({ key: text, format: "pem" });
62
+ return crypto.createPrivateKey({ key: Buffer.from(text, "base64"), format: "der", type: "pkcs8" });
63
+ }
64
+
65
+ // Loads the issuer's signing keys from environment variables `${prefix}_ED25519_KEY` and
66
+ // `${prefix}_MLDSA65_KEY` (base64 PKCS#8 DER, or PEM). Never throws: a missing or unusable key is
67
+ // reported in `problems` and simply not used, so receipts degrade to fewer signatures (or to
68
+ // unsigned) and say so — they never claim a signature that was not made.
69
+ const signerCache = new Map();
70
+ function loadDeliveryReceiptSigners(prefix, env = process.env) {
71
+ const sources = [
72
+ { variable: `${prefix}_ED25519_KEY`, expected: "ed25519" },
73
+ { variable: `${prefix}_MLDSA65_KEY`, expected: "ml-dsa-65" }
74
+ ];
75
+ const cacheKey = sources.map((source) => `${source.variable}=${env[source.variable] || ""}`).join("\n");
76
+ if (signerCache.has(cacheKey)) return signerCache.get(cacheKey);
77
+ const signers = [];
78
+ const problems = [];
79
+ for (const source of sources) {
80
+ const raw = env[source.variable];
81
+ if (!raw || !String(raw).trim()) continue;
82
+ try {
83
+ const privateKey = parsePrivateKey(raw);
84
+ if (privateKey.asymmetricKeyType !== source.expected) {
85
+ problems.push({ variable: source.variable, reason: "key_type_mismatch", detail: `expected ${source.expected}, got ${privateKey.asymmetricKeyType}` });
86
+ continue;
87
+ }
88
+ const publicKey = crypto.createPublicKey(privateKey);
89
+ signers.push({ alg: SUPPORTED_ALGS[source.expected], kid: keyRef(publicKey), privateKey, publicKey });
90
+ } catch (error) {
91
+ problems.push({
92
+ variable: source.variable,
93
+ reason: source.expected === "ml-dsa-65" ? "ml_dsa_key_unusable_or_unsupported_on_this_runtime" : "key_unparseable",
94
+ detail: error && error.message ? error.message.slice(0, 160) : "unparseable"
95
+ });
96
+ }
97
+ }
98
+ // Deterministic suite order: EdDSA first, then ML-DSA-65.
99
+ signers.sort((a, b) => (a.alg === "EdDSA" ? -1 : 1) - (b.alg === "EdDSA" ? -1 : 1));
100
+ const loaded = { signers, problems };
101
+ signerCache.set(cacheKey, loaded);
102
+ return loaded;
103
+ }
104
+
105
+ let mlDsaSupport = null;
106
+ function runtimeSupportsMlDsa() {
107
+ if (mlDsaSupport === null) {
108
+ try {
109
+ mlDsaSupport = Boolean(crypto.generateKeyPairSync("ml-dsa-44").publicKey);
110
+ } catch (_) {
111
+ mlDsaSupport = false;
112
+ }
113
+ }
114
+ return mlDsaSupport;
115
+ }
116
+
117
+ // Builds and signs a receipt. `fields` = { receipt_id, issuer, call, payment, delivery }.
118
+ function issueDeliveryReceipt(fields, signers, now = new Date()) {
119
+ const f = fields && typeof fields === "object" ? fields : {};
120
+ const activeSigners = Array.isArray(signers) ? signers : [];
121
+ const body = {
122
+ v: RECEIPT_VERSION,
123
+ receipt_id: f.receipt_id || `drcpt_${crypto.randomBytes(8).toString("hex")}`,
124
+ issuer: f.issuer || null,
125
+ issued_at: now.toISOString(),
126
+ call: f.call || null,
127
+ payment: f.payment || null,
128
+ delivery: f.delivery || null,
129
+ hashing: { algorithm: "sha256", canonicalization: "JCS (RFC 8785)" },
130
+ signing: {
131
+ canonicalization: CANONICALIZATION,
132
+ suite: activeSigners.map((signer) => signer.alg),
133
+ kids: activeSigners.map((signer) => signer.kid),
134
+ policy: "every_listed_signature_must_verify"
135
+ }
136
+ };
137
+ if (activeSigners.length === 0) {
138
+ return { ...body, sig: { state: "unsigned_signing_key_not_configured", signatures: [] } };
139
+ }
140
+ const signedBytes = Buffer.from(canonicalize(body), "utf8");
141
+ return {
142
+ ...body,
143
+ sig: {
144
+ state: "signed",
145
+ signatures: activeSigners.map((signer) => ({
146
+ alg: signer.alg,
147
+ kid: signer.kid,
148
+ value: crypto.sign(null, signedBytes, signer.privateKey).toString("base64")
149
+ }))
150
+ }
151
+ };
152
+ }
153
+
154
+ // The issuer's public key set (safe to publish).
155
+ function publicKeySet(signers) {
156
+ return (Array.isArray(signers) ? signers : []).map((signer) => ({
157
+ kid: signer.kid,
158
+ alg: signer.alg,
159
+ public_key_spki_der_base64: signer.publicKey.export({ type: "spki", format: "der" }).toString("base64"),
160
+ ...(signer.alg === "EdDSA" ? { jwk: signer.publicKey.export({ format: "jwk" }) } : {})
161
+ }));
162
+ }
163
+
164
+ // Verifies a receipt against a set of trusted public keys ([{ kid, alg, public_key_spki_der_base64 }]).
165
+ // Valid only if the receipt is signed, every algorithm in the SIGNED suite has a signature from the
166
+ // SIGNED kid, every such kid is in the trusted set, and every signature verifies.
167
+ function verifyDeliveryReceipt(receipt, trustedKeys) {
168
+ const r = receipt && typeof receipt === "object" && !Array.isArray(receipt) ? receipt : null;
169
+ if (!r) return { valid: false, reason: "receipt_required" };
170
+ if (r.v !== RECEIPT_VERSION) return { valid: false, reason: "unsupported_receipt_version", version: r.v || null };
171
+ const signing = r.signing && typeof r.signing === "object" ? r.signing : null;
172
+ const sig = r.sig && typeof r.sig === "object" ? r.sig : null;
173
+ if (!signing || !Array.isArray(signing.suite) || !Array.isArray(signing.kids) || signing.suite.length === 0) {
174
+ return { valid: false, reason: "receipt_unsigned" };
175
+ }
176
+ if (!sig || sig.state !== "signed" || !Array.isArray(sig.signatures)) return { valid: false, reason: "receipt_unsigned" };
177
+ const { sig: _omit, ...unsigned } = r;
178
+ const signedBytes = Buffer.from(canonicalize(unsigned), "utf8");
179
+ const trusted = new Map((Array.isArray(trustedKeys) ? trustedKeys : []).map((key) => [key.kid, key]));
180
+ const results = signing.suite.map((alg, index) => {
181
+ const kid = signing.kids[index];
182
+ const entry = sig.signatures.find((item) => item && item.alg === alg && item.kid === kid);
183
+ if (!entry) return { alg, kid, valid: false, reason: "signature_missing_for_signed_suite" };
184
+ const key = trusted.get(kid);
185
+ if (!key || key.alg !== alg) return { alg, kid, valid: false, reason: "kid_not_in_trusted_key_set" };
186
+ try {
187
+ const publicKey = crypto.createPublicKey({ key: Buffer.from(key.public_key_spki_der_base64, "base64"), format: "der", type: "spki" });
188
+ if (keyRef(publicKey) !== kid) return { alg, kid, valid: false, reason: "kid_does_not_match_key" };
189
+ const ok = crypto.verify(null, signedBytes, publicKey, Buffer.from(String(entry.value || ""), "base64"));
190
+ return { alg, kid, valid: ok, reason: ok ? null : "signature_invalid" };
191
+ } catch (error) {
192
+ return { alg, kid, valid: false, reason: "verification_error", detail: error && error.message ? error.message.slice(0, 120) : null };
193
+ }
194
+ });
195
+ const valid = results.length > 0 && results.every((item) => item.valid);
196
+ return { valid, reason: valid ? null : "signature_check_failed", signatures: results, signed_bytes_sha256: sha256Hex(signedBytes) };
197
+ }
198
+
199
+ // A short, self-contained recipe for verifying a receipt without trusting the issuer's server.
200
+ function verificationRecipe(keysUrl) {
201
+ return {
202
+ signed_bytes: "JCS (RFC 8785) canonical JSON of the receipt with its `sig` member removed (UTF-8).",
203
+ keys: keysUrl || null,
204
+ steps: [
205
+ "1. Fetch the issuer's public keys; check each kid in receipt.signing.kids is listed (and pin them).",
206
+ "2. For every alg in receipt.signing.suite, take the signature with the matching kid from receipt.sig.signatures.",
207
+ "3. Verify it over the signed bytes with node:crypto (crypto.verify(null, bytes, spkiPublicKey, signature)) or xoonya's verifyExternalProofSignature({ material, signature, algorithm, public_key, expected_key_ref: kid }).",
208
+ "4. Recompute sha256(JCS(output you received)) and compare with delivery.output_sha256; for x402 calls check payment.settlement_tx on-chain (amount, payer, pay_to)."
209
+ ],
210
+ boundary: "Proves who delivered what for which paid call, unaltered since signing — not whether the output was correct or good."
211
+ };
212
+ }
213
+
214
+ module.exports = {
215
+ RECEIPT_VERSION,
216
+ canonicalize,
217
+ hashCanonical,
218
+ keyRef,
219
+ loadDeliveryReceiptSigners,
220
+ runtimeSupportsMlDsa,
221
+ issueDeliveryReceipt,
222
+ publicKeySet,
223
+ verifyDeliveryReceipt,
224
+ verificationRecipe
225
+ };
@@ -196,7 +196,8 @@ async function verifyPaymentAcceptance(state, headers, requiredHeaders, quote, o
196
196
  x402_payer: auth.authorization.payer,
197
197
  x402_network: auth.authorization.paymentRequirements.network,
198
198
  x402_asset: auth.authorization.paymentRequirements.asset,
199
- x402_amount: auth.authorization.paymentRequirements.amount
199
+ x402_amount: auth.authorization.paymentRequirements.amount,
200
+ x402_pay_to: auth.authorization.paymentRequirements.payTo || null
200
201
  }
201
202
  };
202
203
  // Deferred settlement: verify now, settle only AFTER execution succeeds, so a
@@ -121,6 +121,7 @@ function buildProviderNetworkContract() {
121
121
  economics: {
122
122
  settlement: "Buyers pay via x402 (gasless USDC on Base). The runtime settles on-chain only AFTER your execution succeeds — a failed call does not charge the buyer.",
123
123
  revenue_share: "Your provider_share = floor(settled_amount * revenue_share_bps / 10000); the platform keeps the remainder (incl. rounding). settled_amount = min(quote, the amount actually settled on-chain for the call) — never money that was not collected; each ledger row records its settlement_basis.",
124
+ delivery_receipts: "Every paid call routed to you returns the buyer a signed delivery receipt (naxytra-delivery-receipt/1) naming your provider_id and the sha256 of the request and of the output you returned, bound to the payment (for x402, the settlement tx). Keys: GET /.well-known/delivery-receipt-keys. It attests what you delivered — never whether it was correct.",
124
125
  payouts: "Your accrued share is paid to your payout_address by the operator (reconciled ledger: create -> on-chain send -> confirm). Amounts are tracked in the quote's minor unit (USD_CENTS). Ledger rows and payouts are scoped to your registration: a removed-and-re-registered provider_id starts a fresh ledger and never inherits another owner's rows."
125
126
  },
126
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xytara",
3
- "version": "3.1.12",
3
+ "version": "3.1.13",
4
4
  "description": "x402 pay-per-call agent-commerce runtime: gasless USDC on Base, an open settling provider network with a ledger-reconciled revenue split and automated on-chain payouts (key-less runtime, delegated signer), and bounded cryptographic delegated spend. Proven live on Base mainnet.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -139,6 +139,12 @@ async function main() {
139
139
  assert.strictEqual(settleFails.json.reason, "payment_settlement_failed", "settlement failure reason");
140
140
 
141
141
  // 6. Verified + settled -> 200 with a result and a PAYMENT-RESPONSE receipt.
142
+ // (Delivery-receipt signing keys for this run only: Ed25519 + ML-DSA-65 hybrid.)
143
+ const crypto = require("crypto");
144
+ const receiptEd = crypto.generateKeyPairSync("ed25519");
145
+ const receiptMl = crypto.generateKeyPairSync("ml-dsa-65");
146
+ process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY = receiptEd.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
147
+ process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY = receiptMl.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
142
148
  const settleBefore = facilitator.calls.settle;
143
149
  const paid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
144
150
  assert.strictEqual(paid.status, 200, "verified+settled payment must 200");
@@ -152,6 +158,70 @@ async function main() {
152
158
  assert.strictEqual(receipt.success, true, "settlement receipt must report success");
153
159
  assert.strictEqual(receipt.transaction, "0xmocktx", "settlement receipt transaction mismatch");
154
160
 
161
+ // 6a. The runtime records the REAL x402 settlement (not the quote's default placeholder rail).
162
+ const recorded = paid.json.receipt.settlement;
163
+ assert.strictEqual(recorded.rail, "x402", "x402-paid call records the x402 rail");
164
+ assert.strictEqual(recorded.mode, "x402_facilitator", "x402 settlement mode recorded");
165
+ assert.strictEqual(recorded.txid, "0xmocktx", "settlement record carries the settled tx");
166
+ assert.strictEqual(recorded.network, "eip155:84532", "settlement record carries the network");
167
+ assert.strictEqual(recorded.pay_to, ACCEPTS[0].payTo, "settlement record carries pay_to");
168
+ assert.strictEqual(recorded.status, "settled", "settlement record status");
169
+ assert.strictEqual(recorded.live, undefined, "no placeholder BSV settlement record for an x402 call");
170
+ assert.strictEqual(paid.json.receipt.treasury.rail, "x402", "treasury record on the x402 rail");
171
+ assert.strictEqual(paid.json.receipt.default_path.settlement_mode, "x402_facilitator", "default path reflects x402");
172
+
173
+ // 6b. Signed delivery receipt: binds the settlement, verifies against the published keys,
174
+ // is retrievable by id, and rejects tampering and signature stripping.
175
+ const delivery = paid.json.delivery_receipt;
176
+ assert.ok(delivery, "paid result carries a delivery receipt");
177
+ assert.strictEqual(delivery.v, "naxytra-delivery-receipt/1", "delivery receipt version");
178
+ assert.deepStrictEqual(delivery.signing.suite, ["EdDSA", "ML-DSA-65"], "hybrid suite");
179
+ assert.strictEqual(delivery.payment.protocol, "x402", "receipt payment protocol");
180
+ assert.strictEqual(delivery.payment.settlement_tx, "0xmocktx", "receipt binds the settlement tx");
181
+ assert.strictEqual(delivery.payment.pay_to, ACCEPTS[0].payTo, "receipt carries pay_to");
182
+ assert.strictEqual(delivery.payment.amount, ACCEPTS[0].amount, "receipt carries the settled amount");
183
+ assert.strictEqual(delivery.call.quote_id, paid.json.transaction.quote_id, "receipt binds the quote");
184
+ assert.strictEqual(delivery.account_id, undefined, "receipt carries no account id");
185
+ const receiptLib = require("../lib/delivery_receipt");
186
+ assert.strictEqual(delivery.delivery.request_sha256, receiptLib.hashCanonical(execBody.body), "request hash recomputes from the request body");
187
+ const keysRes = await fetch(`${baseUrl}/.well-known/delivery-receipt-keys`);
188
+ const keys = await keysRes.json();
189
+ assert.strictEqual(keys.signing_state, "signing", "keys endpoint reports signing");
190
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(delivery, keys.keys).valid, true, "receipt verifies against the published keys");
191
+ const verifyRes = await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: delivery });
192
+ assert.strictEqual(verifyRes.json.valid, true, "verify endpoint accepts the genuine receipt");
193
+ assert.strictEqual(verifyRes.json.issuer_matches, true, "verify endpoint matches the issuer");
194
+ const tamperedDelivery = JSON.parse(JSON.stringify(delivery));
195
+ tamperedDelivery.payment.amount = "1";
196
+ assert.strictEqual((await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: tamperedDelivery })).json.valid, false, "tampered receipt rejected");
197
+ const strippedDelivery = JSON.parse(JSON.stringify(delivery));
198
+ strippedDelivery.sig.signatures = strippedDelivery.sig.signatures.filter((entry) => entry.alg !== "ML-DSA-65");
199
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(strippedDelivery, keys.keys).valid, false, "stripping the ML-DSA leg (downgrade) rejected");
200
+ const fetched = await fetch(`${baseUrl}/v1/delivery-receipts/${encodeURIComponent(delivery.receipt_id)}`);
201
+ assert.strictEqual(fetched.status, 200, "receipt retrievable by id");
202
+ assert.deepStrictEqual((await fetched.json()).delivery_receipt, delivery, "retrieved receipt is identical");
203
+ assert.strictEqual((await fetch(`${baseUrl}/v1/delivery-receipts/drcpt_nope`)).status, 404, "unknown receipt 404");
204
+ // The xoonya verifier accepts the same signatures (kid == xoonya key ref). Checked when the
205
+ // sibling xoonya checkout is present (workspace); skipped in a standalone xytara checkout.
206
+ let verifyExternalProofSignature = null;
207
+ try { ({ verifyExternalProofSignature } = require("../../xoonya/proof_sdk")); } catch (_) { verifyExternalProofSignature = null; }
208
+ const { sig: _omit, ...unsignedDelivery } = delivery;
209
+ for (const entry of verifyExternalProofSignature ? delivery.sig.signatures : []) {
210
+ const key = keys.keys.find((item) => item.kid === entry.kid);
211
+ const viaXoonya = verifyExternalProofSignature({ material: receiptLib.canonicalize(unsignedDelivery), signature: entry.value, algorithm: entry.alg, public_key: key.public_key_spki_der_base64, expected_key_ref: entry.kid });
212
+ assert.strictEqual(viaXoonya.signature_valid && viaXoonya.trust_anchored, true, `xoonya verifies + anchors the ${entry.alg} leg`);
213
+ }
214
+ delete process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY;
215
+ delete process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY;
216
+ // Without keys the receipt is still issued but honestly unsigned (never a fake signature).
217
+ const unsignedPaid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
218
+ assert.strictEqual(unsignedPaid.json.delivery_receipt.sig.state, "unsigned_signing_key_not_configured", "no keys -> honestly unsigned");
219
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(unsignedPaid.json.delivery_receipt, keys.keys).valid, false, "unsigned receipt never verifies");
220
+ // The shared module is byte-identical in the runtime and the MCP executor.
221
+ const fs = require("fs");
222
+ const path = require("path");
223
+ assert.ok(fs.readFileSync(path.join(__dirname, "..", "lib", "delivery_receipt.js")).equals(fs.readFileSync(path.join(__dirname, "..", "services", "mcp-execution-target", "delivery_receipt.js"))), "delivery_receipt.js copies must stay byte-identical");
224
+
155
225
  // The facilitator must receive a clean v2 PaymentRequirements: no `resource` field (which
156
226
  // strict facilitators like CDP reject), and it must equal the payment payload's `accepted`.
157
227
  const verifyBody = facilitator.calls.lastVerifyBody;
package/server.js CHANGED
@@ -198,6 +198,14 @@ const {
198
198
  buildProviderEarningsView
199
199
  } = require("./lib/provider_registry");
200
200
  const { loadPayoutSignerConfig, dispatchProviderPayout } = require("./lib/provider_payout_execution");
201
+ const {
202
+ RECEIPT_VERSION: DELIVERY_RECEIPT_VERSION,
203
+ loadDeliveryReceiptSigners,
204
+ publicKeySet: deliveryReceiptPublicKeySet,
205
+ runtimeSupportsMlDsa,
206
+ verifyDeliveryReceipt,
207
+ verificationRecipe: deliveryReceiptVerificationRecipe
208
+ } = require("./lib/delivery_receipt");
201
209
  const { buildProviderNetworkContract } = require("./lib/provider_network_contract");
202
210
  const {
203
211
  createEconomicsBudget,
@@ -7808,6 +7816,51 @@ async function routeRequest(req, res) {
7808
7816
  return;
7809
7817
  }
7810
7818
 
7819
+ // Signed delivery receipts: the issuer's PUBLIC key set (pin these), fetch a receipt by id, and a
7820
+ // free stateless verify. Receipts carry only public payment facts + hashes (no account, no secrets).
7821
+ if (req.method === "GET" && (url.pathname === "/.well-known/delivery-receipt-keys" || url.pathname === "/v1/delivery-receipts/keys")) {
7822
+ const { signers, problems } = loadDeliveryReceiptSigners("XYTARA_DELIVERY_RECEIPT");
7823
+ sendJson(res, 200, {
7824
+ ok: true,
7825
+ issuer: "xytara-runtime",
7826
+ receipt_version: DELIVERY_RECEIPT_VERSION,
7827
+ signing_state: signers.length > 0 ? "signing" : "unsigned_signing_key_not_configured",
7828
+ suite: signers.map((signer) => signer.alg),
7829
+ keys: deliveryReceiptPublicKeySet(signers),
7830
+ runtime_supports_ml_dsa: runtimeSupportsMlDsa(),
7831
+ key_problems: problems.map((problem) => ({ variable: problem.variable, reason: problem.reason })),
7832
+ verification: deliveryReceiptVerificationRecipe("/.well-known/delivery-receipt-keys")
7833
+ });
7834
+ return;
7835
+ }
7836
+
7837
+ if (req.method === "POST" && url.pathname === "/v1/delivery-receipts/verify") {
7838
+ const body = await readJsonBody(req);
7839
+ const receipt = body && typeof body === "object" && body.receipt && typeof body.receipt === "object" ? body.receipt : body;
7840
+ const { signers } = loadDeliveryReceiptSigners("XYTARA_DELIVERY_RECEIPT");
7841
+ const result = verifyDeliveryReceipt(receipt, deliveryReceiptPublicKeySet(signers));
7842
+ sendJson(res, 200, {
7843
+ ok: true,
7844
+ ...result,
7845
+ issuer_expected: "xytara-runtime",
7846
+ issuer_matches: Boolean(receipt && receipt.issuer === "xytara-runtime"),
7847
+ checked_against: "this runtime's current published keys (see /.well-known/delivery-receipt-keys)",
7848
+ boundary: "signature validity only: settle the payment leg on-chain and recompute output_sha256 from the output you hold yourself"
7849
+ });
7850
+ return;
7851
+ }
7852
+
7853
+ if (req.method === "GET" && url.pathname.startsWith("/v1/delivery-receipts/")) {
7854
+ const receiptId = getDecodedSuffixId(url.pathname, "/v1/delivery-receipts/");
7855
+ const stored = state.deliveryReceipts instanceof Map ? state.deliveryReceipts.get(receiptId) : null;
7856
+ if (!stored) {
7857
+ sendJson(res, 404, { ok: false, reason: "delivery_receipt_not_found" });
7858
+ return;
7859
+ }
7860
+ sendJson(res, 200, { ok: true, delivery_receipt: stored });
7861
+ return;
7862
+ }
7863
+
7811
7864
  // Public, machine-readable integration contract — how to become a provider. No auth, no secrets.
7812
7865
  if (req.method === "GET" && url.pathname === "/v1/provider-network/contract") {
7813
7866
  sendJson(res, 200, buildProviderNetworkContract());