@haven_ai/sdk 0.1.32-alpha.0 → 0.1.34-alpha.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 CHANGED
@@ -125,7 +125,8 @@ if (apiResponse.status === 402) {
125
125
  const receipt = await haven.authorizeX402(paymentRequired, {
126
126
  idempotencyKey: 'paid-api-data-2026-05-22',
127
127
  })
128
- // Retry with { 'X-PAYMENT': receipt.paymentHeader }
128
+ // Retry with { 'PAYMENT-SIGNATURE': receipt.paymentHeader }, and on the
129
+ // EIP-3009 bridge 'X-PAYMENT' too. NEVER both on erc7710 — see below.
129
130
  console.log(receipt.explorerUrl)
130
131
  }
131
132
  ```
@@ -209,7 +210,7 @@ const response = await haven.payX402Quote(quote)
209
210
  const data = await response.json()
210
211
  ```
211
212
 
212
- Merchant-verified x402 retries use the official EIP-3009 `exact` scheme on Base USDC (`base` / `eip155:8453`) and send the payment as `X-PAYMENT`. Haven's older tx-hash proof helper remains exported for Haven-native integrations, but `haven.fetch()` does not send `PAYMENT-SIGNATURE`.
213
+ Merchant-verified x402 retries use the official EIP-3009 `exact` scheme on Base USDC (`base` / `eip155:8453`). `haven.fetch()` sends the payment under `PAYMENT-SIGNATURE` (the x402 v2 name), and on the EIP-3009 bridge also under `X-PAYMENT` (v1), so a merchant on either version reads it. **On erc7710 it sends the v2 name ALONE**: that payload is always x402 v2, and its header carries a whole delegation chain, so duplicating it overflows the merchant's header limit and the request is refused with HTTP 431. If you build the retry yourself, follow the same rule. Haven's older tx-hash proof helper remains exported for Haven-native integrations; it is a different payload that happens to have shared the v2 name, and it is not what `haven.fetch()` sends.
213
214
 
214
215
  For standard x402, the `x402-wallet` identity is the agent delegate wallet, because that is the wallet that signs and settles the merchant payment. Integrations that scope access by Haven wallet/Safe address should use a Haven-native flow instead of standard merchant x402.
215
216
 
@@ -364,7 +365,7 @@ x402 tool-window failures:
364
365
  | `expired` | Payment expired before completion. | yes |
365
366
  | `failed` | Haven could not complete the payment. | yes |
366
367
 
367
- The merchant settlement leg of x402 (and the MPP retry) is the agent's own request to the merchant — it does not have a Haven `phase`. The payment is `funding_sent` until the agent retries with `X-PAYMENT` (x402) or the MPP proof header; from Haven's perspective the payment becomes `executed` only after the agent successfully resumes.
368
+ The merchant settlement leg of x402 (and the MPP retry) is the agent's own request to the merchant — it does not have a Haven `phase`. The payment is `funding_sent` until the agent retries with the payment header (`PAYMENT-SIGNATURE`, plus `X-PAYMENT` on this bridged path) (x402) or the MPP proof header; from Haven's perspective the payment becomes `executed` only after the agent successfully resumes.
368
369
 
369
370
  ### `nextAction` reference
370
371
 
@@ -471,7 +472,9 @@ Think of bridged x402 as two separate legs:
471
472
  `txHash` describe this leg. It is automatic and bounded by the budget — no
472
473
  human step.
473
474
  - Merchant x402 leg: after the funding leg is complete, the agent resumes the
474
- same payment id and retries the original merchant request with `X-PAYMENT`.
475
+ same payment id and retries the original merchant request with the payment
476
+ header, under `PAYMENT-SIGNATURE` and — on this bridged path only —
477
+ `X-PAYMENT`.
475
478
  Do not treat a new 402 probe or a new MCP session as a resume.
476
479
 
477
480
  For manual HTTP stacks, use `resumeAuthorizedX402()` to get the merchant header
@@ -485,15 +488,58 @@ const receipt = await haven.resumeAuthorizedX402({
485
488
  })
486
489
 
487
490
  await fetch('https://paid-api.example.com/data', {
488
- headers: { 'X-PAYMENT': receipt.paymentHeader! },
491
+ headers: {
492
+ 'PAYMENT-SIGNATURE': receipt.paymentHeader!,
493
+ // Bridged (EIP-3009) resume only. On erc7710 send the v2 name ALONE —
494
+ // that header carries a delegation chain and duplicating it is refused
495
+ // with HTTP 431.
496
+ 'X-PAYMENT': receipt.paymentHeader!,
497
+ },
489
498
  })
490
499
  ```
491
500
 
501
+ **When YOU make the retry, report the outcome (#2292).** `haven.fetch()` and
502
+ the `payX402*` helpers call the merchant themselves and write the evidence or
503
+ reconciliation record from what they observed. `resumeAuthorizedX402()` and the
504
+ raw MCP/SSE flow below deliberately do not — you hold the header and make the
505
+ call — so Haven cannot learn what happened unless you tell it:
506
+
507
+ ```typescript
508
+ const response = await fetch('https://paid-api.example.com/data', {
509
+ headers: {
510
+ 'PAYMENT-SIGNATURE': receipt.paymentHeader!,
511
+ // Bridged (EIP-3009) resume only — see the erc7710 note above.
512
+ 'X-PAYMENT': receipt.paymentHeader!,
513
+ },
514
+ })
515
+
516
+ await haven.reportX402MerchantOutcome({
517
+ paymentId: status.paymentId,
518
+ outcome: response.ok ? 'accepted' : 'rejected',
519
+ merchantStatus: response.status,
520
+ })
521
+ ```
522
+
523
+ A `rejected` report writes the same open `merchant_retry_rejected_after_payment`
524
+ reconciliation event the built-in retry writes, so the next
525
+ `getPaymentStatus()` answers `phase: funded_but_unsettled` /
526
+ `nextAction: sweep_stranded_funds` instead of reading as complete for the
527
+ fifteen-minute merchant-report grace window. An `accepted` report records the
528
+ merchant response so a delivered payment never enters that window at all.
529
+
530
+ It is evidence, not authority. The funding transaction hash and resource URL are
531
+ read from the payment's own record rather than taken from you — they are not
532
+ parameters — the call is scoped to your own agent's payments, and it changes no
533
+ amount, recipient or status. `outcome` must agree with `merchantStatus`
534
+ (`accepted` only for a 2xx), and an acceptance is terminal: a rejection reported
535
+ after a recorded merchant response is refused rather than re-flagging a
536
+ delivered payment as stranded.
537
+
492
538
  For MCP/SSE x402 tools, keep the same MCP session and JSON-RPC payload where the
493
539
  merchant requires it: initialize, retain `mcp-session-id`, send the original
494
540
  `tools/call`, parse the 402 challenge, wait for the funding leg to confirm if
495
541
  the payment is bridged, then resume with the same `payment_id` and retry the
496
- original `tools/call` with `X-PAYMENT`. Use a stable `idempotencyKey` for the
542
+ original `tools/call`, setting BOTH `PAYMENT-SIGNATURE` (x402 v2) and `X-PAYMENT` (v1) to the header. Use a stable `idempotencyKey` for the
497
543
  user intent so fresh merchant quotes or sessions do not become duplicate Haven
498
544
  payments.
499
545
 
package/dist/index.cjs CHANGED
@@ -617,6 +617,7 @@ function normalizePaymentRequired(value) {
617
617
  const resourceUrl = candidate.resource?.url ?? first.resource;
618
618
  if (!resourceUrl) return null;
619
619
  const resource = {
620
+ ...candidate.resource && typeof candidate.resource === "object" ? candidate.resource : {},
620
621
  url: resourceUrl,
621
622
  description: candidate.resource?.description ?? first.description,
622
623
  mimeType: candidate.resource?.mimeType ?? first.mimeType
@@ -667,6 +668,41 @@ var NETWORK_TOKENS = {
667
668
  "eip155:84532": BASE_SEPOLIA_TOKENS,
668
669
  "base-sepolia": BASE_SEPOLIA_TOKENS
669
670
  };
671
+ var X402_PAYMENT_HEADER_NAME = "PAYMENT-SIGNATURE";
672
+ var X402_LEGACY_PAYMENT_HEADER_NAME = "X-PAYMENT";
673
+ var X402_PAYMENT_REQUIRED_HEADER_NAME = "PAYMENT-REQUIRED";
674
+ var X402_PAYMENT_RESPONSE_HEADER_NAME = "PAYMENT-RESPONSE";
675
+ var X402_PAYMENT_HEADER_NAMES_SENT = `${X402_PAYMENT_HEADER_NAME}, ${X402_LEGACY_PAYMENT_HEADER_NAME}`;
676
+ var X402_PAYMENT_HEADER_NAMES = [
677
+ X402_PAYMENT_HEADER_NAME,
678
+ X402_LEGACY_PAYMENT_HEADER_NAME
679
+ ];
680
+ function x402PaymentHeaderNamesFor(paymentHeader) {
681
+ const both = [X402_PAYMENT_HEADER_NAME, X402_LEGACY_PAYMENT_HEADER_NAME];
682
+ let decoded;
683
+ try {
684
+ decoded = decodeBase64Json(paymentHeader);
685
+ } catch {
686
+ return both;
687
+ }
688
+ const accepted = decoded?.accepted;
689
+ if (!accepted || typeof accepted !== "object" || Array.isArray(accepted)) return both;
690
+ return isErc7710Option(accepted) ? [X402_PAYMENT_HEADER_NAME] : both;
691
+ }
692
+ function x402PaymentHeaderNamesSent(paymentHeader) {
693
+ return x402PaymentHeaderNamesFor(paymentHeader).join(", ");
694
+ }
695
+ function x402V2PaymentEnvelope(paymentRequired, accepted, payload) {
696
+ const resource = paymentRequired.resource;
697
+ const extensions = paymentRequired.extensions;
698
+ return {
699
+ x402Version: paymentRequired.x402Version,
700
+ ...resource && typeof resource === "object" && !Array.isArray(resource) ? { resource } : {},
701
+ accepted,
702
+ payload,
703
+ ...extensions && typeof extensions === "object" && !Array.isArray(extensions) ? { extensions } : {}
704
+ };
705
+ }
670
706
  function parsePaymentRequired(response) {
671
707
  const v2Header = response.headers.get("PAYMENT-REQUIRED");
672
708
  if (v2Header) {
@@ -846,7 +882,15 @@ async function validateStandardX402PaymentHeader(paymentHeader, context) {
846
882
  throw new Error("context");
847
883
  }
848
884
  } else {
849
- if (!hasOnlyKeys(decoded, ["x402Version", "accepted", "payload"])) throw new Error("shape");
885
+ if (!hasOnlyKeys(decoded, ["x402Version", "accepted", "payload"], ["resource", "extensions"])) {
886
+ throw new Error("shape");
887
+ }
888
+ if ("resource" in decoded && (!decoded.resource || typeof decoded.resource !== "object" || Array.isArray(decoded.resource))) {
889
+ throw new Error("shape");
890
+ }
891
+ if ("extensions" in decoded && (!decoded.extensions || typeof decoded.extensions !== "object" || Array.isArray(decoded.extensions))) {
892
+ throw new Error("shape");
893
+ }
850
894
  const accepted = selectStandardPaymentOption([decoded.accepted]);
851
895
  if (!accepted || !matchesHeaderContext(accepted, context)) throw new Error("context");
852
896
  }
@@ -898,8 +942,8 @@ async function validateStandardX402PaymentHeader(paymentHeader, context) {
898
942
  throw new X402PaymentHeaderValidationError();
899
943
  }
900
944
  }
901
- function hasOnlyKeys(value, allowed) {
902
- return Object.keys(value).every((key) => allowed.includes(key)) && allowed.every((key) => key in value);
945
+ function hasOnlyKeys(value, required, optional = []) {
946
+ return Object.keys(value).every((key) => required.includes(key) || optional.includes(key)) && required.every((key) => key in value);
903
947
  }
904
948
  function sameAddress2(left, right) {
905
949
  return left.toLowerCase() === right.toLowerCase();
@@ -1157,7 +1201,7 @@ function nextActionForStatus(status) {
1157
1201
  if (status === "submitted") return AgentPaymentNextAction.CheckStatusLater;
1158
1202
  if (status === "confirmed") return AgentPaymentNextAction.None;
1159
1203
  if (status === "pending" || status === "pending_approval") return AgentPaymentNextAction.StopAndTellUser;
1160
- if (status === "approved") return AgentPaymentNextAction.WaitForUserToCompletePayment;
1204
+ if (status === "approved") return AgentPaymentNextAction.StopAndTellUser;
1161
1205
  if (status === "proposed") return AgentPaymentNextAction.StopAndTellUser;
1162
1206
  if (status === "executed") return AgentPaymentNextAction.StopAndTellUser;
1163
1207
  if (status === "rejected") return AgentPaymentNextAction.StopAndTellUser;
@@ -1169,6 +1213,9 @@ function messageForState(label, status, paymentId, nextAction) {
1169
1213
  if (status === "pending" || status === "pending_approval") {
1170
1214
  return `${label} is not payable: it is outside the agent's on-chain budget and no approval is pending (payment_id: ${paymentId}). Ask the user to grant or raise the budget in Haven.`;
1171
1215
  }
1216
+ if (status === "approved") {
1217
+ return `This payment carries a retired status ("approved") that no live Haven rail produces (payment_id: ${paymentId}). Nothing is waiting to be completed \u2014 tell the user to review this payment in Haven.`;
1218
+ }
1172
1219
  if (status === "executed") {
1173
1220
  return `This payment carries a retired status ("executed") that no live Haven rail produces (payment_id: ${paymentId}). Do not retry it \u2014 tell the user to review this payment in Haven.`;
1174
1221
  }
@@ -1386,10 +1433,37 @@ var McpMerchantTransport = class {
1386
1433
  hasBazaarExtension(response) {
1387
1434
  return responseHasBazaarExtension(response);
1388
1435
  }
1389
- /** Deliver an already-signed x402 header without changing the caller body. */
1436
+ /**
1437
+ * Deliver an already-signed x402 header without changing the caller body.
1438
+ *
1439
+ * #2289: x402 v2 reads `PAYMENT-SIGNATURE`; v1 reads `X-PAYMENT`. Sending
1440
+ * only the legacy name meant a strict v2 merchant never saw the header —
1441
+ * indistinguishable, from the merchant's side, from sending no header at
1442
+ * all, while on the EIP-3009 bridge the funding leg had already moved the
1443
+ * money.
1444
+ *
1445
+ * #2341: WHICH names go on is per-payload, not always both — see
1446
+ * `x402PaymentHeaderNamesFor`. Both for EIP-3009; `PAYMENT-SIGNATURE` alone
1447
+ * for erc7710, whose header carries a whole delegation chain and answered
1448
+ * HTTP 431 when duplicated. The decision is made here rather than by the
1449
+ * caller so every path inherits it, and it is read from the payload rather
1450
+ * than passed in, because a flag a caller supplies is a flag a caller can
1451
+ * get wrong.
1452
+ *
1453
+ * Always `set`, never `append`, so a stale header on the caller's `init` is
1454
+ * replaced rather than added to — a merchant that reads the first of two
1455
+ * values would otherwise verify a superseded authorization. The name NOT
1456
+ * being sent is deleted for the same reason: on erc7710 a stale `X-PAYMENT`
1457
+ * left in place would be a superseded authorization we chose not to
1458
+ * overwrite, which is worse than the duplicate this change removes.
1459
+ */
1390
1460
  async deliverPayment(url, init, paymentHeader) {
1391
1461
  const headers = new Headers(init?.headers);
1392
- headers.set("X-PAYMENT", paymentHeader);
1462
+ const send = x402PaymentHeaderNamesFor(paymentHeader);
1463
+ for (const name of X402_PAYMENT_HEADER_NAMES) {
1464
+ if (send.includes(name)) headers.set(name, paymentHeader);
1465
+ else headers.delete(name);
1466
+ }
1393
1467
  return this.fetch(url, { ...init, headers });
1394
1468
  }
1395
1469
  async notifyInitialized(url, init, sessionId, wallet) {
@@ -2074,11 +2148,7 @@ var X402FundingLeg = class {
2074
2148
  );
2075
2149
  if (paymentRequired.x402Version < 2) return header;
2076
2150
  const payment = decodeBase64Json(header);
2077
- return encodeBase64Json({
2078
- x402Version: paymentRequired.x402Version,
2079
- accepted: option,
2080
- payload: payment.payload
2081
- });
2151
+ return encodeBase64Json(x402V2PaymentEnvelope(paymentRequired, option, payment.payload));
2082
2152
  }
2083
2153
  // ── Receipt mapping ──────────────────────────────────────────────
2084
2154
  receiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
@@ -2235,10 +2305,11 @@ var X402Erc7710 = class {
2235
2305
  * settled when this returns** — that is why it does not return an
2236
2306
  * `X402Receipt`.
2237
2307
  *
2238
- * Requires a delegation-rail account. The backend enforces that
2239
- * (`validateGenericSchemeRail`), and so does this method, before building a
2240
- * request the backend would only reject: an error a client can explain is
2241
- * worth more than a 400 it has to decode.
2308
+ * Requires a delegation-rail account. The backend enforces that at the
2309
+ * rail seam a non-delegation account gets the #1986 retired-rail 410 from
2310
+ * `POST /x402/authorize` whatever scheme it asks for (#2245) and so does
2311
+ * this method, before building a request the backend would only reject: an
2312
+ * error a client can explain is worth more than a refusal it has to decode.
2242
2313
  *
2243
2314
  * **MCP callers must pass `options.resourceUrl`.** An in-band MCP 402
2244
2315
  * challenge frequently carries no `resource` object at all, so
@@ -2288,6 +2359,16 @@ var X402Erc7710 = class {
2288
2359
  const amountAtomic = x402AuthorizationAmount(option);
2289
2360
  const raw = await this.post("/x402", {
2290
2361
  url: options.resourceUrl ?? paymentRequired.resource?.url,
2362
+ // #2373: the full 402 challenge, persisted verbatim by the backend
2363
+ // (#1355) so the settle handoff can echo its resource/extensions into
2364
+ // the X-PAYMENT envelope (#2361). This scheme decomposes the challenge
2365
+ // into the fields below for AUTHORITY; the stored copy exists for the
2366
+ // echo, which cannot be reconstructed from the decomposition — omitting
2367
+ // it is how every erc7710 payment failed a merchant that enforces the
2368
+ // spec's extensions-echo MUST. Same ≤64KB guard and omission behaviour
2369
+ // as the 3009 path (client.ts): an oversized challenge omits the field
2370
+ // rather than failing the payment, and the settle echo then omits too.
2371
+ ...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {},
2291
2372
  // payTo = the MERCHANT is what selects direct settlement server-side.
2292
2373
  // The explicit settlementScheme must AGREE with that shape (#1360) —
2293
2374
  // disagreement is a 400 by design, so that a stale delegate address
@@ -2532,7 +2613,7 @@ var MerchantCompletion = class {
2532
2613
  merchantStatus: retryResponse.status,
2533
2614
  challengePayload: paymentRequired,
2534
2615
  selectedPayment: receipt.accepted,
2535
- paymentProofHeaderName: "X-PAYMENT",
2616
+ paymentProofHeaderName: x402PaymentHeaderNamesSent(receipt.paymentHeader),
2536
2617
  paymentProofHeader: receipt.paymentHeader,
2537
2618
  protocolReceiptHeaderName: "PAYMENT-RESPONSE",
2538
2619
  protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
@@ -2642,6 +2723,109 @@ var MerchantCompletion = class {
2642
2723
  } catch {
2643
2724
  }
2644
2725
  }
2726
+ /**
2727
+ * #2292: record what a merchant said to a retry **Haven did not make**.
2728
+ *
2729
+ * On the plain-HTTP x402 path Haven tells the agent to call the merchant
2730
+ * itself — that is the keyless design, not an oversight — so the two writes
2731
+ * above were reachable only from `completeX402MerchantCall`, where Haven IS
2732
+ * the caller. A manual retry had nowhere to put its outcome, which left
2733
+ * `intentStateFor`'s merchant-rejected branch dead on the one flow Haven
2734
+ * prescribes and made the 15-minute grace window the only route to
2735
+ * `funded_but_unsettled`.
2736
+ *
2737
+ * Three properties distinguish this from `recordRetryRejected` /
2738
+ * `reportEvidence`, and each is deliberate:
2739
+ *
2740
+ * 1. **It does not swallow.** Those two are bookkeeping hung off a call
2741
+ * whose outcome is already decided, so an exception there would turn a
2742
+ * completed payment into a reported failure. Here the report IS the
2743
+ * caller's request; silently dropping it would recreate the exact
2744
+ * unobservability #2292 exists to remove.
2745
+ * 2. **The anchor is server-side.** `txHash` and `resourceUrl` come from
2746
+ * the payment's own Haven record, never from the reporter — so a report
2747
+ * cannot be pointed at a different transaction or a different resource,
2748
+ * and it can never CONFIRM an intent (an erc7710 intent has no Haven
2749
+ * tx hash and is refused here rather than completed from a supplied one,
2750
+ * which is #2092's verified seam and stays its own path).
2751
+ * 3. **It is evidence, never authority.** Haven does not and must not check
2752
+ * the claim: verifying it would mean calling the merchant, which is the
2753
+ * property this whole path exists to preserve. What bounds a false
2754
+ * report is scope — the backend routes resolve the payment
2755
+ * `WHERE agent_id = $`, so a caller can only ever describe its own
2756
+ * payment — plus the fact that nothing financial keys off the claim:
2757
+ * the sweep is balance-driven, the intent's status/amount/recipient are
2758
+ * untouched, and a false `accepted` runs the server's own on-chain
2759
+ * residue check, which re-flags stranded funds independently.
2760
+ */
2761
+ async reportMerchantOutcome(input) {
2762
+ if (!Number.isInteger(input.merchantStatus) || input.merchantStatus < 100 || input.merchantStatus > 599) {
2763
+ throw new HavenApiError(
2764
+ `merchant_status must be an integer HTTP status from 100 to 599 (received ${input.merchantStatus}).`,
2765
+ 400,
2766
+ void 0,
2767
+ input.paymentId
2768
+ );
2769
+ }
2770
+ const looksAccepted = input.merchantStatus >= 200 && input.merchantStatus < 300;
2771
+ if (looksAccepted !== (input.outcome === "accepted")) {
2772
+ throw new HavenApiError(
2773
+ `outcome "${input.outcome}" contradicts merchant_status ${input.merchantStatus}: report "accepted" only for a 2xx and "rejected" only for a non-2xx.`,
2774
+ 400,
2775
+ void 0,
2776
+ input.paymentId
2777
+ );
2778
+ }
2779
+ const status = await this.getPaymentStatus(input.paymentId);
2780
+ if (status.rail !== "x402") {
2781
+ throw new HavenPaymentStateError(
2782
+ `Payment ${status.paymentId} is ${status.rail}, not x402 \u2014 there is no merchant retry to report.`,
2783
+ 409,
2784
+ status
2785
+ );
2786
+ }
2787
+ if (status.status !== "confirmed" || !status.txHash) {
2788
+ throw new HavenPaymentStateError(
2789
+ `Payment ${status.paymentId} has no confirmed Haven funding transaction to anchor a merchant report to (status ${status.status}). ${status.message}`,
2790
+ paymentStateStatusCode(status.status, 409),
2791
+ status
2792
+ );
2793
+ }
2794
+ const resourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
2795
+ if (!resourceUrl) {
2796
+ throw new HavenApiError(
2797
+ `Payment ${status.paymentId} has no recorded resource URL, so a merchant report cannot be stored.`,
2798
+ 409,
2799
+ status,
2800
+ status.paymentId
2801
+ );
2802
+ }
2803
+ const txHash = status.txHash;
2804
+ if (input.outcome === "rejected") {
2805
+ await this.post("/machine-payments/reconciliation-events", {
2806
+ paymentId: status.paymentId,
2807
+ rail: "x402",
2808
+ eventType: "merchant_retry_rejected_after_payment",
2809
+ txHash,
2810
+ reason: `Agent-reported: merchant returned HTTP ${input.merchantStatus} to a manual retry after Haven payment confirmation`,
2811
+ details: {
2812
+ resource_url: resourceUrl,
2813
+ retry_status: input.merchantStatus,
2814
+ retry_body: input.merchantBody?.slice(0, MERCHANT_BODY_SNIPPET_LIMIT) || null,
2815
+ reported_by: "agent_manual_retry"
2816
+ }
2817
+ });
2818
+ return { paymentId: status.paymentId, outcome: "rejected", txHash, resourceUrl, recorded: "reconciliation_event" };
2819
+ }
2820
+ await this.post("/machine-payments/evidence", {
2821
+ paymentId: status.paymentId,
2822
+ rail: "x402",
2823
+ txHash,
2824
+ resourceUrl,
2825
+ merchantStatus: input.merchantStatus
2826
+ });
2827
+ return { paymentId: status.paymentId, outcome: "accepted", txHash, resourceUrl, recorded: "evidence" };
2828
+ }
2645
2829
  async reportEvidence(input) {
2646
2830
  const body = {
2647
2831
  paymentId: input.paymentId,
@@ -3528,7 +3712,7 @@ var HavenClient = class {
3528
3712
  * Deliver an already-signed x402 payment header to the merchant and return
3529
3713
  * the merchant's response. Used by the hosted MCP server to complete the
3530
3714
  * merchant leg of an MCP tool payment after the edge signer has built the
3531
- * `X-PAYMENT` header.
3715
+ * merchant payment header.
3532
3716
  *
3533
3717
  * Custody note: this never needs the delegate key. It relays a signed,
3534
3718
  * amount/merchant/nonce-bound EIP-3009 authorization the edge signer already
@@ -3538,13 +3722,14 @@ var HavenClient = class {
3538
3722
  * says the merchant was Bazaar-discoverable, runs a fresh `initialize`
3539
3723
  * handshake (the quote-time session is gone once funding confirms; the x402
3540
3724
  * challenge is stateless w.r.t. the MCP session, so a fresh session is
3541
- * accepted), threads the session + wallet headers, sets `X-PAYMENT`, and
3725
+ * accepted), threads the session + wallet headers, sets the x402 payment
3726
+ * header under the names that scheme requires (#2341), and
3542
3727
  * collapses an SSE JSON-RPC response to its `result`.
3543
3728
  */
3544
3729
  /**
3545
3730
  * Wait for a payment's Safe→delegate funding tx to reach ≥1 on-chain
3546
3731
  * confirmation. The hosted x402 completion path MUST call this after funding
3547
- * and before delivering the X-PAYMENT header, so the merchant's
3732
+ * and before delivering the merchant payment header, so the merchant's
3548
3733
  * balanceOf(delegate) / transferWithAuthorization verification sees the funded
3549
3734
  * balance — otherwise it rejects with "Payment verification failed". The
3550
3735
  * SDK's local path already does this (see `X402FundingLeg.authorize`); the hosted
@@ -3619,7 +3804,7 @@ var HavenClient = class {
3619
3804
  txHash: evidenceTxHash,
3620
3805
  resourceUrl: evidenceContext.resourceUrl,
3621
3806
  merchantStatus: surfaced.status,
3622
- paymentProofHeaderName: "X-PAYMENT",
3807
+ paymentProofHeaderName: x402PaymentHeaderNamesSent(input.paymentHeader),
3623
3808
  paymentProofHeader: input.paymentHeader,
3624
3809
  protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
3625
3810
  protocolReceiptHeader
@@ -3634,6 +3819,18 @@ var HavenClient = class {
3634
3819
  settlementTxHash: settlement.settlementTxHash ?? void 0
3635
3820
  };
3636
3821
  }
3822
+ /**
3823
+ * #2292: report the outcome of a merchant retry the AGENT performed.
3824
+ *
3825
+ * The hosted `haven_complete_mcp_tool` / `completeX402MerchantCall` path is
3826
+ * for merchants Haven calls itself. On the plain-HTTP x402 path Haven never
3827
+ * talks to the merchant, so the outcome of that retry had no way back —
3828
+ * see `MerchantCompletion.reportMerchantOutcome` for what is verified about
3829
+ * a caller-asserted report and what deliberately is not.
3830
+ */
3831
+ async reportX402MerchantOutcome(input) {
3832
+ return await this.merchantCompletion.reportMerchantOutcome(input);
3833
+ }
3637
3834
  /**
3638
3835
  * GET /x402/:id/merchant-call-context — the settle-leg twin of #1263's
3639
3836
  * sign-context fetch (#1307). Re-serves the stored merchant MCP-tool call
@@ -3795,12 +3992,12 @@ var toolDescriptions = {
3795
3992
  payX402OneShot: {
3796
3993
  summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
3797
3994
  selectionGuidance: "Prefer this over the quote+pay split when the agent just wants the paid resource and does not need to inspect the price first. If you already have a quote from haven_quote_x402, use haven_pay_x402_quote instead. Do not use for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
3798
- behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the payment locally, then retries the original request with the X-PAYMENT header and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
3995
+ behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
3799
3996
  nextActionGuidance: "Preserve the returned resume_state or paymentId \u2014 either identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=fund_safe_or_raise_allowance, the payment cannot be retried until the account is funded or the agent budget raised \u2014 stop and tell the user the shortfall reported on the response."
3800
3997
  },
3801
3998
  resumeX402: {
3802
3999
  summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
3803
- behavior: "Accepts either resume_state or payment_id, validates the original x402 details against the authorized Haven funding, and retries the merchant request with the X-PAYMENT header. No new Haven payment is created.",
4000
+ behavior: "Accepts either resume_state or payment_id, validates the original x402 details against the authorized Haven funding, and retries the merchant request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only). No new Haven payment is created.",
3804
4001
  nextActionGuidance: "Only call this after haven_get_payment_status reports nextAction=retry_original_x402_request \u2014 that means Haven's funding leg confirmed but no merchant response was ever recorded, most often because the process crashed between funding and the merchant retry. Any other nextAction reports a conflict instead of retrying, so do not call this speculatively. Do not start a new merchant session and do not pay again \u2014 that would pay twice for one resource."
3805
4002
  },
3806
4003
  // #1328: quoteMpp / payMpp / resumeMpp (the mpp_demo challenge/quote/resume
@@ -3986,7 +4183,7 @@ var resumeX402Schema = {
3986
4183
  var MAKE_PAYMENT_DESCRIPTION = "Request and sign a payment from the user-controlled account within its on-chain budget. For read-only allowance, budget, spend-limit, remaining-amount, or reset-period questions, use get_allowances instead of making a payment. Haven authenticates the agent and relays the signed transaction that redeems the agent budget delegation; it does not hold keys or control funds. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
3987
4184
  var GET_STATUS_DESCRIPTION = toolDescriptions.getPaymentStatus.summary + " Accepts payment intent IDs. Returns the current status, phase, next_action, transaction hash if available, and payment details.";
3988
4185
  var GET_ALLOWANCES_DESCRIPTION = composeDescription(toolDescriptions.getAllowances);
3989
- var AUTHORIZE_X402_DESCRIPTION = composeDescription(toolDescriptions.payX402) + " In this SDK tool set, the allowance lookup tool is get_allowances. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key; funding, when the scheme needs it, is redeemed from the agent budget delegation and is bounded by it. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. A payment outside the on-chain budget is declined before any money moves \u2014 report the decline and ask the user to raise the budget in Haven; do not loop retries and do not wait for an approval, because none is queued. Preserve the original merchant/MCP session and x402 details. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
4186
+ var AUTHORIZE_X402_DESCRIPTION = composeDescription(toolDescriptions.payX402) + " In this SDK tool set, the allowance lookup tool is get_allowances. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key; funding, when the scheme needs it, is redeemed from the agent budget delegation and is bounded by it. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. A payment outside the on-chain budget is declined before any money moves \u2014 report the decline and ask the user to raise the budget in Haven; do not loop retries and do not wait for an approval, because none is queued. Preserve the original merchant/MCP session and x402 details. On a manual HTTP retry always set PAYMENT-SIGNATURE (x402 v2) to the returned payment_header; a strict v2 merchant reads only that name. Also set X-PAYMENT (v1) on the EIP-3009 funding path for legacy merchants, but NEVER on erc7710 \u2014 that header carries a delegation chain and duplicating it is refused with HTTP 431.";
3990
4187
  var RESUME_X402_DESCRIPTION = toolDescriptions.resumeX402.summary + " Only call this after get_payment_status reports nextAction=retry_original_x402_request \u2014 that means Haven's funding leg confirmed but no merchant response was ever recorded (typically a crash between funding and the merchant retry). Any other nextAction reports a conflict instead of retrying \u2014 do not call this speculatively, and do not pay again.";
3991
4188
  var SWEEP_DELEGATE_DESCRIPTION = composeDescription(toolDescriptions.sweep_delegate);
3992
4189
  var sweepDelegateSchema = {
@@ -4198,8 +4395,14 @@ The paid call always obtains a fresh quote before it creates any intent. Then
4198
4395
  continue \`mcp__haven__haven_pay_mcp_tool\` \u2192
4199
4396
  \`mcp__haven-signer__haven_sign\` \u2192 \`mcp__haven__haven_submit\` \u2192
4200
4397
  \`mcp__haven-signer__haven_x402_sign_header\` \u2192
4201
- \`mcp__haven__haven_complete_mcp_tool\`. Pass \`payment_required\`,
4202
- \`arguments\`, and \`mcp_transport\` verbatim from the quote/prepare result.
4398
+ \`mcp__haven__haven_complete_mcp_tool\`. Call that last step with
4399
+ \`payment_id\` and the signer's \`payment_header\` ONLY. It does not take
4400
+ \`payment_required\`: Haven rehydrates the merchant call context
4401
+ (\`merchant_url\`, \`tool_name\`, \`arguments\`, \`mcp_transport\`) and the
4402
+ 402 server-side from \`payment_id\`, exactly as at settle. Pass that context
4403
+ explicitly only as a version-skew fallback when Haven has no stored context
4404
+ for the id \u2014 \`merchant_url\` and \`tool_name\` both or none together, never
4405
+ just one.
4203
4406
  The returned \`expires_at\` is the signing window; if a tool returns
4204
4407
  \`PAYMENT_WINDOW_EXPIRED\`, re-run the same quote/prepare tool with the same
4205
4408
  \`idempotency_key\`. Do not call the merchant yourself \u2014 Haven completes the
@@ -4209,8 +4412,20 @@ merchant leg for you.
4209
4412
  recipient, amount, and token for a plain transfer. For an arbitrary,
4210
4413
  non-MCP x402 paywall: \`mcp__haven__haven_quote_x402\` to get a quote, then
4211
4414
  \`mcp__haven__haven_pay_x402_quote\` \u2014 follow the result's guidance fields
4212
- first and sign in the local Haven signer. The pay tool performs the merchant
4213
- retry itself, so do not wait on a signal while it runs. If the process
4415
+ first and sign in the local Haven signer. On THIS path Haven does not talk to
4416
+ the merchant: \`mcp__haven-signer__haven_sign_x402\` returns both
4417
+ \`signature\` and \`payment_header\`; relay \`signature\` with
4418
+ \`mcp__haven__haven_submit\`, then retry the paywalled URL yourself with
4419
+ \`payment_header\`. Do not pass that call's \`x402_binding\` to
4420
+ \`mcp__haven-signer__haven_x402_sign_header\` \u2014 the one-shot already spent it
4421
+ building the header, so the call can only refuse. Then tell Haven what the
4422
+ merchant answered: \`mcp__haven__haven_report_x402_outcome\` with the
4423
+ \`payment_id\`, \`outcome\` (\`"accepted"\` for a 2xx, else \`"rejected"\`)
4424
+ and the \`merchant_status\` you got. Because Haven never contacted that
4425
+ merchant, this is the only way it can learn the purchase failed \u2014 without it a
4426
+ failed purchase reads as complete for fifteen minutes. (The SDK's own
4427
+ \`haven_pay_x402\` tool does perform the merchant retry itself; that tool is
4428
+ not part of the hosted MCP surface.) If the process
4214
4429
  crashes after payment, a later \`mcp__haven__haven_get_payment_status\` call
4215
4430
  may report \`nextAction: 'retry_original_x402_request'\` \u2014 only then call
4216
4431
  \`mcp__haven__haven_resume_x402_payment\` with the preserved resume state or
@@ -4225,9 +4440,11 @@ payment id, instead of paying again.
4225
4440
  result, never a catalog price. \`haven_discover_tools\` prices are indicative
4226
4441
  (\`price_is_indicative\`) and can be stale. A read-only quote is informational
4227
4442
  only and does not reserve a price; the later paid call re-quotes and enforces
4228
- the cap. The pay-tool result's \`amount\` / \`amount_atomic\` is the amount
4229
- Haven authorizes for that call \u2014 a ceiling the merchant settles at or below \u2014
4230
- so present it as the most the user will pay.
4443
+ the cap. The pay-tool result's \`amount\` / \`amount_atomic\` is the merchant's
4444
+ own quoted price for that call \u2014 a ceiling the merchant settles at or below \u2014
4445
+ so present it as the most the user will pay. It is a price, not an approval:
4446
+ the payment goes through only if it also fits the cap you set and the on-chain
4447
+ budget the user signed, which is enforced on-chain rather than by Haven.
4231
4448
 
4232
4449
  **Status:** \`mcp__haven__haven_get_payment_status\` with a \`payment_id\` to
4233
4450
  check on in-flight payments. Do not poll in a tight loop.
@@ -4422,7 +4639,12 @@ exports.TRANSFER_WITH_AUTHORIZATION_TYPES = TRANSFER_WITH_AUTHORIZATION_TYPES;
4422
4639
  exports.X402AlreadySettledError = X402AlreadySettledError;
4423
4640
  exports.X402PaymentHeaderValidationError = X402PaymentHeaderValidationError;
4424
4641
  exports.X402UnexpectedStatusError = X402UnexpectedStatusError;
4642
+ exports.X402_LEGACY_PAYMENT_HEADER_NAME = X402_LEGACY_PAYMENT_HEADER_NAME;
4425
4643
  exports.X402_MAX_AUTHORIZATION_WINDOW_SECONDS = X402_MAX_AUTHORIZATION_WINDOW_SECONDS;
4644
+ exports.X402_PAYMENT_HEADER_NAME = X402_PAYMENT_HEADER_NAME;
4645
+ exports.X402_PAYMENT_HEADER_NAMES_SENT = X402_PAYMENT_HEADER_NAMES_SENT;
4646
+ exports.X402_PAYMENT_REQUIRED_HEADER_NAME = X402_PAYMENT_REQUIRED_HEADER_NAME;
4647
+ exports.X402_PAYMENT_RESPONSE_HEADER_NAME = X402_PAYMENT_RESPONSE_HEADER_NAME;
4426
4648
  exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
4427
4649
  exports.addressFromKey = addressFromKey;
4428
4650
  exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
@@ -4462,5 +4684,6 @@ exports.verifySignature = verifySignature;
4462
4684
  exports.x402AssetTransferMethod = x402AssetTransferMethod;
4463
4685
  exports.x402AuthorizationAmount = x402AuthorizationAmount;
4464
4686
  exports.x402FacilitatorAddresses = x402FacilitatorAddresses;
4687
+ exports.x402V2PaymentEnvelope = x402V2PaymentEnvelope;
4465
4688
  //# sourceMappingURL=index.cjs.map
4466
4689
  //# sourceMappingURL=index.cjs.map