@haven_ai/sdk 0.2.0-alpha.0 → 0.3.0-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/dist/index.cjs CHANGED
@@ -114,43 +114,51 @@ var AgentPaymentNextAction = {
114
114
  * the agent's per-token allowance needs to be raised before the payment
115
115
  * can succeed. A user approval will not fix this state on its own.
116
116
  *
117
- * #2908: the wire twin `fund_account_or_raise_allowance`
118
- * ({@link AgentPaymentNextActionAccountAlias}) means the same thing; the
119
- * server keeps emitting THIS value until #2914. Compare via
120
- * {@link canonicalAgentPaymentNextAction}.
117
+ * #2914: the account-vocabulary spelling, and the only one — the
118
+ * pre-#2907 `fund_safe_or_raise_allowance` wire value (and the
119
+ * `AgentPaymentNextActionAccountAlias` seam #2908 added to bridge it) are
120
+ * retired along with the rest of the #2908 compatibility window.
121
121
  */
122
- FundSafeOrRaiseAllowance: "fund_safe_or_raise_allowance",
122
+ FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance",
123
123
  /**
124
124
  * The delegate wallet may hold funds that were sent from the Safe but never
125
125
  * settled to the merchant. The wallet owner should initiate a sweep to
126
126
  * return those funds to the originating Safe.
127
127
  */
128
- SweepStrandedFunds: "sweep_stranded_funds"
129
- };
130
- var AgentPaymentNextActionAccountAlias = {
131
- /** Account-vocabulary twin of `fund_safe_or_raise_allowance`; same meaning. */
132
- FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance"
128
+ SweepStrandedFunds: "sweep_stranded_funds",
129
+ /**
130
+ * #2970: a `submitted` erc7710 x402 intent whose settlement window has
131
+ * passed with no on-chain settlement evidence Haven could verify. Distinct
132
+ * from {@link CheckStatusLater}, which this REPLACES once the window is
133
+ * past — but it is not futile: Haven's settlement sweep (120s tick) scans
134
+ * each candidate over its own window plus a 120s clock-skew allowance, so
135
+ * it can still attribute the settlement for a short while after this value
136
+ * first appears. Poll {@link CheckStatusLater}'s tool
137
+ * (`haven_get_payment_status`) once more, roughly two minutes later; if it
138
+ * still shows no evidence, tell the user the goods were delivered but
139
+ * Haven holds no verified settlement evidence for this payment. If the
140
+ * agent holds the merchant's real settlement transaction hash (from
141
+ * `PAYMENT-RESPONSE`'s `transaction` field, or a prior settle/complete
142
+ * result's `settlement_tx_hash`), report it with the hosted
143
+ * `haven_report_settlement_evidence` tool instead of waiting —
144
+ * `haven_report_x402_outcome` takes no hash and refuses a non-`confirmed`
145
+ * intent.
146
+ */
147
+ AwaitingSettlementEvidence: "awaiting_settlement_evidence"
133
148
  };
134
- function canonicalAgentPaymentNextAction(value) {
135
- if (value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance) {
136
- return AgentPaymentNextAction.FundSafeOrRaiseAllowance;
137
- }
138
- return value;
139
- }
140
- function isFundAccountOrRaiseAllowance(value) {
141
- return value === AgentPaymentNextAction.FundSafeOrRaiseAllowance || value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance;
142
- }
143
149
  var AgentPaymentFailureCode = {
144
150
  /** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
145
151
  PriceExceedsMax: "PRICE_EXCEEDS_MAX",
146
152
  /** The x402 funding/quote window expired before the signer or hosted settle step could finish. */
147
153
  PaymentWindowExpired: "PAYMENT_WINDOW_EXPIRED",
148
- /** The Haven funding leg succeeded, but the merchant rejected the paid retry. */
154
+ /** The merchant rejected the paid retry. On eip3009 the funding leg had succeeded (sweep);
155
+ * on erc7710 there is no funding leg — nothing to sweep, follow the message (#2983). */
149
156
  MerchantRejectedAfterFunding: "MERCHANT_REJECTED_AFTER_FUNDING",
150
157
  /** #1300 review: funding is on-chain but the merchant never ANSWERED the
151
158
  * paid retry within the timeout. NOT proof of rejection — the merchant
152
- * holds a valid EIP-3009 authorization and may still settle late, so the
153
- * guidance is verify-then-sweep, never blind sweep. */
159
+ * may still settle late, so the guidance is verify-then-act. On eip3009
160
+ * the funding leg had succeeded (verify-then-sweep); on erc7710 there is
161
+ * no funding leg — nothing to sweep, follow the message (#3000). */
154
162
  MerchantUnresponsiveAfterFunding: "MERCHANT_UNRESPONSIVE_AFTER_FUNDING",
155
163
  /**
156
164
  * #1307: the caller omitted merchant_url/tool_name (asking Haven to
@@ -176,7 +184,17 @@ var AgentPaymentFailureCode = {
176
184
  * asset can represent (truncating it would silently change the user's cap).
177
185
  * The fallback is the exact atomic `max_amount`.
178
186
  */
179
- MaxAmountUnconvertible: "MAX_AMOUNT_UNCONVERTIBLE"
187
+ MaxAmountUnconvertible: "MAX_AMOUNT_UNCONVERTIBLE",
188
+ /**
189
+ * #2979: the merchant answered a `tools/call` probe with its own
190
+ * machine-readable "cannot settle right now" refusal (HTTP 503,
191
+ * `{ error: 'merchant_not_ready', reason_code, ... }`) instead of a 402
192
+ * challenge — e.g. its settlement wallet is out of gas. No 402 was ever
193
+ * issued and no payment was created; this is honest and (per
194
+ * `retry_after_s`, when present) usually transient, unlike a permanent
195
+ * endpoint miss.
196
+ */
197
+ MerchantNotReady: "MERCHANT_NOT_READY"
180
198
  };
181
199
  var AgentPaymentRail = {
182
200
  /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
@@ -222,18 +240,20 @@ var AgentPaymentNextActionDescriptions = {
222
240
  [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
223
241
  [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
224
242
  [AgentPaymentNextAction.PaymentWindowExpired]: "The x402 funding/quote window expired. Re-quote with the same idempotency key before asking the signer to build a merchant payment header again.",
225
- [AgentPaymentNextAction.FundSafeOrRaiseAllowance]: "Stop and tell the user that the account needs to be funded or the agent budget raised before the payment can succeed.",
243
+ [AgentPaymentNextAction.FundAccountOrRaiseAllowance]: "Stop and tell the user that the account needs to be funded or the agent budget raised before the payment can succeed.",
226
244
  [AgentPaymentNextAction.RetryWithExplicitContext]: "Retry the same tool call, this time passing merchant_url, tool_name, arguments, and mcp_transport explicitly \u2014 the server had no stored context to rehydrate for this payment id.",
227
- [AgentPaymentNextAction.SweepStrandedFunds]: "Tell the user that funds may be stranded in the delegate wallet and prompt them to initiate a sweep in Haven to return them to the originating account."
245
+ [AgentPaymentNextAction.SweepStrandedFunds]: "Tell the user that funds may be stranded in the delegate wallet and prompt them to initiate a sweep in Haven to return them to the originating account.",
246
+ [AgentPaymentNextAction.AwaitingSettlementEvidence]: "The settlement window passed with no verified on-chain evidence yet. If you hold the merchant's real settlement transaction hash, report it with haven_report_settlement_evidence. Otherwise, Haven's settlement sweep may still attribute it within about two minutes \u2014 poll getPaymentStatus once more, then tell the user the goods were delivered but unverified if it still shows nothing."
228
247
  };
229
248
  var AgentPaymentFailureCodeDescriptions = {
230
249
  [AgentPaymentFailureCode.PriceExceedsMax]: "The merchant-authoritative x402 amount exceeds the caller's max_amount cap. No funding transfer was created; ask the user before retrying with a larger cap.",
231
250
  [AgentPaymentFailureCode.PaymentWindowExpired]: "The x402 funding/quote window expired before the signer or hosted settle step could finish. Re-quote via haven_pay_mcp_tool with the same idempotency key to avoid duplicate funding.",
232
- [AgentPaymentFailureCode.MerchantRejectedAfterFunding]: "The Haven funding leg succeeded, but the merchant rejected the paid retry. Stop retrying the merchant and reconcile stranded delegate funds with haven_sweep_delegate.",
233
- [AgentPaymentFailureCode.MerchantUnresponsiveAfterFunding]: "The Haven funding leg succeeded, but the merchant did not answer the paid retry before the timeout. The merchant may still settle late \u2014 check haven_get_payment_status (and retry haven_complete_mcp_tool once) BEFORE sweeping; sweep only if no settlement appears.",
251
+ [AgentPaymentFailureCode.MerchantRejectedAfterFunding]: "The merchant rejected the paid retry. eip3009: the funding leg succeeded \u2014 stop retrying the merchant and reconcile stranded delegate funds with haven_sweep_delegate. erc7710: no funding leg, nothing to sweep \u2014 follow the message (re-quote later, or check haven_get_payment_status after the window first).",
252
+ [AgentPaymentFailureCode.MerchantUnresponsiveAfterFunding]: "The merchant did not answer the paid retry before the timeout. The merchant may still settle late. eip3009: the funding leg succeeded \u2014 check haven_get_payment_status, retry haven_complete_mcp_tool once, sweep only if no settlement appears. erc7710: no funding leg, nothing to sweep, and haven_complete_mcp_tool has no erc7710 branch \u2014 do not retry it; check haven_get_payment_status after the payment window and re-quote only if it shows no settlement.",
234
253
  [AgentPaymentFailureCode.MerchantCallContextUnavailable]: "merchant_url/tool_name were omitted and no stored merchant call context is available for this payment_id. Re-send merchant_url, tool_name, arguments, and mcp_transport explicitly.",
235
254
  [AgentPaymentFailureCode.AmbiguousMaxAmount]: "Both max_amount (atomic units) and max_amount_human (whole tokens) were supplied for one purchase. Nothing was contacted and nothing was spent. Re-send with exactly ONE: max_amount_human for a cap the user stated in tokens, max_amount for an exact atomic figure.",
236
- [AgentPaymentFailureCode.MaxAmountUnconvertible]: "max_amount_human could not be converted to atomic units against this quote's asset \u2014 either its decimals are unknown to Haven or the cap has more decimal places than the asset supports. Nothing was spent. Round the cap, or re-send it as an exact atomic max_amount."
255
+ [AgentPaymentFailureCode.MaxAmountUnconvertible]: "max_amount_human could not be converted to atomic units against this quote's asset \u2014 either its decimals are unknown to Haven or the cap has more decimal places than the asset supports. Nothing was spent. Round the cap, or re-send it as an exact atomic max_amount.",
256
+ [AgentPaymentFailureCode.MerchantNotReady]: 'The merchant refused the probe with its own "cannot settle right now" signal instead of a 402 challenge. No payment was created. Often transient \u2014 retry later (see retry_after_s in the message, if given) rather than treating this as a broken or wrong endpoint.'
237
257
  };
238
258
  var AgentPaymentWarningCode = {
239
259
  /** No max_amount cap was supplied — the live quoted price was accepted as-is. */
@@ -264,7 +284,24 @@ var AgentPaymentWarningCode = {
264
284
  * on-chain policy re-checks at redemption either way; this only says the
265
285
  * guidance shown here may be optimistic.
266
286
  */
267
- AllowanceReadOptimistic: "ALLOWANCE_READ_OPTIMISTIC"
287
+ AllowanceReadOptimistic: "ALLOWANCE_READ_OPTIMISTIC",
288
+ /**
289
+ * #2991: the quote tools' `expected_settlement_scheme` prediction of what
290
+ * `haven_prepare_catalog_purchase` / `haven_pay_mcp_tool` will actually
291
+ * select could not be computed — the agent's execution rail could not be
292
+ * read from Haven, so `expected_settlement_scheme` is `null` rather than a
293
+ * guess. `accepted_scheme` (the merchant's offer) is unaffected.
294
+ */
295
+ X402SchemeUnknown: "X402_SCHEME_UNKNOWN",
296
+ /**
297
+ * #2968: the merchant answered 200 and handed over goods, but Haven holds NO
298
+ * on-chain evidence that the payment moved. `settled: false` beside this code
299
+ * is not a failure — it is the absence of proof, and the two must travel
300
+ * together so an agent can tell "the user has the goods" apart from "the
301
+ * money moved". Carries the intent's `expires_at`: after that instant the
302
+ * settlement can no longer land at all.
303
+ */
304
+ SettlementUnconfirmed: "SETTLEMENT_UNCONFIRMED"
268
305
  };
269
306
  var AgentPaymentRailDescriptions = {
270
307
  [AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled account, redeeming the agent's on-chain budget delegation.",
@@ -328,8 +365,18 @@ var MerchantTimeoutError = class extends HavenApiError {
328
365
  };
329
366
  var X402UnexpectedStatusError = class extends HavenApiError {
330
367
  x402ErrorCode = "unexpected_non_402_status";
331
- constructor(message, statusCode) {
332
- super(message, statusCode);
368
+ /**
369
+ * #2979: `body` is the merchant's own JSON, when the non-402 response
370
+ * carried one — e.g. the demo merchant's `/mcp` readiness gate answers
371
+ * `503 { error: 'merchant_not_ready', reason_code, ... }`. Optional and
372
+ * best-effort: a non-JSON or unreadable body leaves this `undefined`, same
373
+ * as before this field existed. Consumers key on it (not on the message
374
+ * string) to distinguish an honest, machine-readable merchant refusal from
375
+ * a genuine "this is not the x402 endpoint" miss, which otherwise look
376
+ * identical — both are just "some non-402 status".
377
+ */
378
+ constructor(message, statusCode, body) {
379
+ super(message, statusCode, body);
333
380
  this.name = "X402UnexpectedStatusError";
334
381
  }
335
382
  };
@@ -368,6 +415,17 @@ var HavenSigningError = class extends HavenError {
368
415
  this.name = "HavenSigningError";
369
416
  }
370
417
  };
418
+ var HavenZeroSettlementHashError = class extends HavenError {
419
+ constructor(paymentId) {
420
+ super(
421
+ "settlement_tx_hash is the zero hash (0x00\u202600), which is never a real settlement transaction \u2014 refused before any report was sent.",
422
+ "ZERO_SETTLEMENT_HASH",
423
+ 400,
424
+ paymentId
425
+ );
426
+ this.name = "HavenZeroSettlementHashError";
427
+ }
428
+ };
371
429
  var SignerRefusalCode = {
372
430
  /** `SUPPORTED_X402_EXPECTED_VERSIONS` in `@haven_ai/signer` does not include the received version. */
373
431
  UnsupportedExpectedContextVersion: "UNSUPPORTED_EXPECTED_CONTEXT_VERSION",
@@ -1116,6 +1174,15 @@ var HavenApiTransport = class {
1116
1174
  };
1117
1175
 
1118
1176
  // src/payment-mappers.ts
1177
+ function mapParties(raw) {
1178
+ if (!raw) return void 0;
1179
+ return {
1180
+ treasuryAccount: raw.treasury_account,
1181
+ delegate: raw.delegate,
1182
+ delegateAccount: raw.delegate_account,
1183
+ merchant: raw.merchant
1184
+ };
1185
+ }
1119
1186
  function mapPaymentResult(raw, buildExplorerUrl2) {
1120
1187
  return {
1121
1188
  paymentId: raw.payment_id,
@@ -1146,15 +1213,13 @@ function mapPaymentStatusResult(raw) {
1146
1213
  rail: raw.rail,
1147
1214
  status: raw.status,
1148
1215
  phase: raw.phase,
1149
- // #2908: the account-vocabulary alias collapses onto the canonical value
1150
- // so every `=== AgentPaymentNextAction.X` downstream keeps working when
1151
- // the server flips its emit at #2914.
1152
- nextAction: canonicalAgentPaymentNextAction(raw.next_action),
1216
+ nextAction: raw.next_action,
1153
1217
  amount: raw.amount,
1154
1218
  token: raw.token,
1155
1219
  resourceUrl: raw.resource_url,
1156
1220
  merchantAddress: raw.merchant_address,
1157
1221
  payerAddress: raw.payer_address ?? null,
1222
+ parties: mapParties(raw.parties),
1158
1223
  txHash: raw.tx_hash,
1159
1224
  expiresAt: raw.expires_at,
1160
1225
  chainId: raw.chain_id,
@@ -1188,10 +1253,13 @@ function mapPaymentReceipt(raw) {
1188
1253
  rail: raw.rail,
1189
1254
  proofStatus: raw.proof_status,
1190
1255
  txHash: raw.tx_hash,
1256
+ fundingTxHash: raw.funding_tx_hash ?? null,
1257
+ settlementTxHash: raw.settlement_tx_hash ?? null,
1191
1258
  chainId: raw.chain_id,
1192
1259
  resourceUrl: raw.resource_url,
1193
1260
  merchantAddress: raw.merchant_address,
1194
1261
  payerAddress: raw.payer_address,
1262
+ parties: mapParties(raw.parties),
1195
1263
  settlementAddress: raw.settlement_address,
1196
1264
  tokenSymbol: raw.token_symbol,
1197
1265
  tokenAddress: raw.token_address,
@@ -1281,7 +1349,7 @@ function messageForState(label, status, paymentId, nextAction) {
1281
1349
  function paymentStateFromRaw(label, raw) {
1282
1350
  if (!raw.payment_id || !raw.status) return null;
1283
1351
  const phase = raw.phase ?? phaseForStatus(raw.status);
1284
- const nextAction = canonicalAgentPaymentNextAction(raw.next_action) ?? nextActionForStatus(raw.status);
1352
+ const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
1285
1353
  if (!phase || !nextAction) return null;
1286
1354
  const amount = raw.amount ?? raw.requested ?? "";
1287
1355
  const token = raw.token ?? "";
@@ -1604,20 +1672,6 @@ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
1604
1672
  return { verified: true, recoveredSigner: recovered };
1605
1673
  }
1606
1674
 
1607
- // src/account-naming.ts
1608
- function readAccountAddress(raw) {
1609
- return raw.account_address ?? raw.safe_address ?? void 0;
1610
- }
1611
- function readAccountId(raw) {
1612
- return raw.account_id ?? raw.safe_id ?? void 0;
1613
- }
1614
- function accountAddressTwins(address) {
1615
- return { accountAddress: address, safeAddress: address };
1616
- }
1617
- function readX402ReceiptPayer(raw) {
1618
- return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account ?? raw.safe_address ?? raw.sign_data?.components?.safe;
1619
- }
1620
-
1621
1675
  // src/account-reads.ts
1622
1676
  function safeBigInt(value) {
1623
1677
  try {
@@ -1676,10 +1730,13 @@ var AccountReads = class {
1676
1730
  const raw = await this.transport.get("/machine-payments/allowances");
1677
1731
  return {
1678
1732
  agentId: raw.agent_id,
1679
- // #2908: one mapper, both names, same value — `readAccountAddress`
1680
- // prefers the server's `account_address` twin and falls back to
1681
- // `safe_address` for a pre-#2907 server.
1682
- ...accountAddressTwins(readAccountAddress(raw)),
1733
+ // `account_address` is required on the wire contract, so the declared
1734
+ // type stays `string`; a server that omits it is off-contract and the
1735
+ // cast is the one place that case is allowed through as `undefined`
1736
+ // rather than a fabricated `''` (a present-but-blank address downstream
1737
+ // — the hosted MCP output spreads this object, and the sweep uses it as
1738
+ // a destination).
1739
+ accountAddress: raw.account_address,
1683
1740
  delegateAddress: raw.delegate_address,
1684
1741
  chainId: raw.chain_id,
1685
1742
  allowances: raw.allowances.map((allowance) => ({
@@ -1768,10 +1825,10 @@ var AccountReads = class {
1768
1825
  id: raw.id,
1769
1826
  name: raw.name,
1770
1827
  status: raw.status,
1771
- // #2908: both camelCase names off whichever snake_case name the server
1772
- // sent (new first). The hosted MCP's `haven_get_agent` spreads this
1773
- // object, so this is also the hosted output's dual-emit point.
1774
- ...accountAddressTwins(readAccountAddress(raw)),
1828
+ // See the comment on `getAllowances` above: `account_address` is
1829
+ // required on the wire contract, so an omission here is off-contract
1830
+ // and comes through as `undefined` rather than a fabricated `''`.
1831
+ accountAddress: raw.account_address,
1775
1832
  delegateAddress: raw.delegate_address,
1776
1833
  chainId: raw.chain_id,
1777
1834
  executionRail: raw.execution_rail === "delegation" ? "delegation" : "legacy"
@@ -2103,6 +2160,13 @@ function assertCanResumeX402(status, paymentRequired, option) {
2103
2160
  );
2104
2161
  }
2105
2162
  }
2163
+
2164
+ // src/account-naming.ts
2165
+ function readX402ReceiptPayer(raw) {
2166
+ return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account;
2167
+ }
2168
+
2169
+ // src/x402-funding-leg.ts
2106
2170
  var X402FundingLeg = class {
2107
2171
  delegateKey;
2108
2172
  delegateAddress;
@@ -2915,15 +2979,57 @@ var MerchantCompletion = class {
2915
2979
  for (let attempt = 0; ; attempt += 1) {
2916
2980
  try {
2917
2981
  await this.post("/machine-payments/evidence", body);
2918
- return;
2982
+ return { outcome: "confirmed" };
2919
2983
  } catch (err) {
2920
- const retryable = err instanceof HavenApiError && err.statusCode === EVIDENCE_RETRYABLE_STATUS;
2921
- if (!retryable || attempt >= EVIDENCE_RETRY_DELAYS_MS.length) return;
2984
+ const statusCode = err instanceof HavenApiError ? err.statusCode : void 0;
2985
+ const retryable = statusCode === EVIDENCE_RETRYABLE_STATUS;
2986
+ if (!retryable) {
2987
+ return { outcome: "refused", statusCode: statusCode ?? 0 };
2988
+ }
2989
+ if (attempt >= EVIDENCE_RETRY_DELAYS_MS.length) {
2990
+ return { outcome: "retryable", statusCode };
2991
+ }
2922
2992
  await this.sleep(EVIDENCE_RETRY_DELAYS_MS[attempt]);
2923
2993
  }
2924
2994
  }
2925
2995
  }
2996
+ /**
2997
+ * #2972: report the merchant's REAL settlement transaction hash for an
2998
+ * erc7710 x402 payment out of band — the remedy #2970's guidance could not
2999
+ * name, because no hosted tool accepted a hash. An agent reaches this after
3000
+ * `haven_settle_mcp_tool` / `haven_complete_mcp_tool` answered
3001
+ * `DELIVERED_UNSETTLED` or `SETTLEMENT_PENDING`, or after
3002
+ * `haven_get_payment_status` reports `awaiting_settlement_evidence` — in
3003
+ * every one of those cases the agent may be holding the merchant's own
3004
+ * `PAYMENT-RESPONSE.transaction` while Haven has nothing.
3005
+ *
3006
+ * Reuses `reportEvidence` — same backend seam
3007
+ * (`POST /machine-payments/evidence` → `observeErc7710Settlement`,
3008
+ * fail-closed — see `settlement-observed.ts`), same three-outcome contract.
3009
+ * `resourceUrl` and `merchantStatus` are omitted: this call has no fresh
3010
+ * merchant HTTP exchange to read either from, and both are optional at the
3011
+ * backend (see the parameter doc on `reportEvidence`).
3012
+ *
3013
+ * The zero hash is refused HERE, client-side, before any network call —
3014
+ * never posted. `isZeroSettlementTxHash` is the same recognizer the #2970
3015
+ * gate uses, so a caller cannot "fix" a missing hash by reporting the demo
3016
+ * merchant's own marker and getting a different verdict than the settle
3017
+ * path already gave it.
3018
+ */
3019
+ async reportSettlementEvidence(paymentId, settlementTxHash) {
3020
+ if (isZeroSettlementTxHash(settlementTxHash)) {
3021
+ throw new HavenZeroSettlementHashError(paymentId);
3022
+ }
3023
+ return this.reportEvidence({
3024
+ paymentId,
3025
+ rail: "x402",
3026
+ txHash: settlementTxHash
3027
+ });
3028
+ }
2926
3029
  };
3030
+ function isZeroSettlementTxHash(hash) {
3031
+ return typeof hash === "string" && /^0x0+$/i.test(hash);
3032
+ }
2927
3033
  function parseMerchantSettlement(header) {
2928
3034
  if (!header) return {};
2929
3035
  const parsed = parseProtocolReceiptHeader(header);
@@ -3425,7 +3531,7 @@ var HavenClient = class {
3425
3531
  const query = params.size > 0 ? `?${params.toString()}` : "";
3426
3532
  const raw = await this.get(`/catalog${query}`);
3427
3533
  let entries = raw.entries.map(mapCatalogEntry);
3428
- if (options.verified === "verified") entries = entries.filter((e) => e.source === "ingestion");
3534
+ if (options.verified === "verified") entries = entries.filter((e) => e.verifiedPayable === true);
3429
3535
  if (options.verified === "operator") entries = entries.filter((e) => e.source === "operator");
3430
3536
  return entries;
3431
3537
  }
@@ -3562,9 +3668,16 @@ var HavenClient = class {
3562
3668
  const request = snapshotX402Request(url, initialInit);
3563
3669
  const response = await this.merchantTransport.fetch(url, initialInit);
3564
3670
  if (response.status !== 402) {
3671
+ let body;
3672
+ try {
3673
+ body = await response.clone().json();
3674
+ } catch {
3675
+ body = void 0;
3676
+ }
3565
3677
  throw new X402UnexpectedStatusError(
3566
3678
  `Expected an x402 quote response with HTTP 402, got HTTP ${response.status}.`,
3567
- response.status || 400
3679
+ response.status || 400,
3680
+ body
3568
3681
  );
3569
3682
  }
3570
3683
  if (response.headers.get("MACHINE-PAYMENT-CHALLENGE")) {
@@ -3853,6 +3966,7 @@ var HavenClient = class {
3853
3966
  } catch {
3854
3967
  body = text;
3855
3968
  }
3969
+ let evidenceOutcome;
3856
3970
  if (!surfaced.ok) {
3857
3971
  if (!input.noFundingLeg && fundingTxHash) {
3858
3972
  await this.merchantCompletion.recordRetryRejected({
@@ -3872,9 +3986,10 @@ var HavenClient = class {
3872
3986
  });
3873
3987
  }
3874
3988
  } else {
3875
- const evidenceTxHash = input.noFundingLeg ? settlement.settlementTxHash ?? void 0 : fundingTxHash ?? void 0;
3989
+ const rawEvidenceTxHash = input.noFundingLeg ? settlement.settlementTxHash ?? void 0 : fundingTxHash ?? void 0;
3990
+ const evidenceTxHash = rawEvidenceTxHash && !isZeroSettlementTxHash(rawEvidenceTxHash) ? rawEvidenceTxHash : void 0;
3876
3991
  if (evidenceTxHash) {
3877
- await this.merchantCompletion.reportEvidence({
3992
+ evidenceOutcome = await this.merchantCompletion.reportEvidence({
3878
3993
  paymentId: evidenceContext.paymentId,
3879
3994
  rail: "x402",
3880
3995
  txHash: evidenceTxHash,
@@ -3892,7 +4007,8 @@ var HavenClient = class {
3892
4007
  status: surfaced.status,
3893
4008
  ok: surfaced.ok,
3894
4009
  body,
3895
- settlementTxHash: settlement.settlementTxHash ?? void 0
4010
+ settlementTxHash: settlement.settlementTxHash ?? void 0,
4011
+ evidenceOutcome
3896
4012
  };
3897
4013
  }
3898
4014
  /**
@@ -3907,6 +4023,19 @@ var HavenClient = class {
3907
4023
  async reportX402MerchantOutcome(input) {
3908
4024
  return await this.merchantCompletion.reportMerchantOutcome(input);
3909
4025
  }
4026
+ /**
4027
+ * #2972: report the merchant's real settlement transaction hash for an
4028
+ * erc7710 x402 payment — the remedy for `DELIVERED_UNSETTLED` /
4029
+ * `SETTLEMENT_PENDING` / `awaiting_settlement_evidence` when the agent
4030
+ * holds the hash (`PAYMENT-RESPONSE.transaction`, or a prior settle/
4031
+ * complete result's `settlement_tx_hash`) and Haven does not. See
4032
+ * `MerchantCompletion.reportSettlementEvidence` for the fail-closed
4033
+ * verification this posts into (`observeErc7710Settlement`) and the
4034
+ * client-side zero-hash refusal.
4035
+ */
4036
+ async reportSettlementEvidence(paymentId, settlementTxHash) {
4037
+ return await this.merchantCompletion.reportSettlementEvidence(paymentId, settlementTxHash);
4038
+ }
3910
4039
  /**
3911
4040
  * GET /x402/:id/merchant-call-context — the settle-leg twin of #1263's
3912
4041
  * sign-context fetch (#1307). Re-serves the stored merchant MCP-tool call
@@ -4063,13 +4192,13 @@ var toolDescriptions = {
4063
4192
  summary: "Pay an inspected x402 quote. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",
4064
4193
  selectionGuidance: "Do not use this for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
4065
4194
  behavior: "Signs the payment locally 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.",
4066
- nextActionGuidance: "Preserve the returned resume_state \u2014 it 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 (or fund_account_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."
4195
+ nextActionGuidance: "Preserve the returned resume_state \u2014 it 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_account_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."
4067
4196
  },
4068
4197
  payX402OneShot: {
4069
4198
  summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
4070
4199
  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.",
4071
4200
  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.",
4072
- 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 (or fund_account_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."
4201
+ 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_account_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."
4073
4202
  },
4074
4203
  resumeX402: {
4075
4204
  summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
@@ -4081,8 +4210,8 @@ var toolDescriptions = {
4081
4210
  // MACHINE-PAYMENT-CHALLENGE was never produced by anything besides the now
4082
4211
  // deleted `/demo/mpp/*` route. Use the x402 fragments above instead.
4083
4212
  getPaymentStatus: {
4084
- summary: "Fetch structured Haven payment status, including phase and nextAction taxonomy for agent recovery.",
4085
- behavior: "Accepts a payment intent id and returns the full state taxonomy (phase, nextAction, rail, amount, merchant, resource url, idempotency key, message).",
4213
+ summary: "Fetch structured Haven payment status for agent recovery.",
4214
+ behavior: "State: phase, nextAction, rail, amount, merchant, resource, idempotency, message; parties: treasury/delegate/delegateAccount/merchant. awaiting_settlement_evidence: poll once, else unverified.",
4086
4215
  nextActionGuidance: ""
4087
4216
  },
4088
4217
  getResumeState: {
@@ -4093,7 +4222,7 @@ var toolDescriptions = {
4093
4222
  getAgent: {
4094
4223
  summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness, and per-token remaining allowance (atomic + human-readable). The recommended first call in a new session to confirm who you are and whether Haven will let you spend right now.",
4095
4224
  selectionGuidance: "Use this as the one-shot orientation/bootstrap at the start of a session, or whenever you need to confirm identity together with whether the agent can spend right now. For a detailed per-token breakdown (configured vs spent vs reset window) use haven_get_allowances.",
4096
- behavior: `Reads identity plus the live spend-authority snapshot in one shot \u2014 the agent's active on-chain budget delegation. spend_authority_readiness (readiness is a deprecated alias, same value) is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. It covers hosted identity + on-chain spend authority ONLY \u2014 the hosted server cannot see the LOCAL signer, so "ready" does not mean the signer can start; verify the signer with a signer tool call or connect --doctor. An over-budget payment is declined before any money moves: there is no approval queue, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields: id, name, status, accountAddress (safeAddress: deprecated alias, same value), delegateAddress, chainId.`,
4225
+ behavior: `Reads identity plus the live spend-authority snapshot in one shot \u2014 the agent's active on-chain budget delegation. spend_authority_readiness (readiness is a deprecated alias, same value) is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. It covers hosted identity + on-chain spend authority ONLY \u2014 the hosted server cannot see the LOCAL signer, so "ready" does not mean the signer can start; verify the signer with a signer tool call or connect --doctor. An over-budget payment is declined before any money moves: there is no approval queue, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields: id, name, status, accountAddress, delegateAddress, chainId.`,
4097
4226
  nextActionGuidance: ""
4098
4227
  },
4099
4228
  getAllowances: {
@@ -4122,7 +4251,7 @@ var toolDescriptions = {
4122
4251
  },
4123
4252
  discoverTools: {
4124
4253
  summary: "Step 1 of a purchase: discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use next.",
4125
- selectionGuidance: "Use this when the user asks what the agent can buy, pay for, or which paid services exist \u2014 or when you need a resource URL for a service the user described. Use verified=verified to show only self-submitted directory entries that passed domain-ownership proof and a live quote probe \u2014 never treat those badges as proof of merchant honesty, quality, or reliability. Do NOT use for balance, budget, or spend-limit questions \u2014 use haven_get_allowances. Do NOT use to pay \u2014 each returned entry names the pay tool to use next.",
4254
+ selectionGuidance: "Use this when the user asks what the agent can buy, pay for, or which paid services exist \u2014 or when you need a resource URL for a service the user described. Use verified=verified for entries Haven watched pass a live quote probe (operator-curated or self-submitted) \u2014 domain_verified is the only ownership claim; never treat these badges as proof of merchant honesty, quality, or reliability. Do NOT use for balance, budget, or spend-limit questions \u2014 use haven_get_allowances. Do NOT use to pay \u2014 each returned entry names the pay tool to use next.",
4126
4255
  behavior: "Use each entry's suggested_tool field first \u2014 it names the exact next call. Read-only lookup against Haven's curated catalog; entries are periodically re-verified against the live merchant and degraded entries are flagged. Use category for a case-insensitive category filter (for example, VPN or vpn), or search for a product name, category, or description term. Returns name, description, price, rail, resource URL, tool_name, tool_arguments, suggested_tool, and the provenance badges source/domain_verified/verified_payable. The catalog price (price_display/price_atomic, marked price_is_indicative) is a last-verified hint, NOT authoritative \u2014 the real price comes from the merchant's live 402 at pay time. Never creates a payment, signature, or approval.",
4127
4256
  nextActionGuidance: `Pick an entry and pay it with the tool named in suggested_tool, passing the entry's resource_url, tool_name, and tool_arguments for MCP merchants. Confirm the price from the live pay-tool result (not the catalog), and pass the user's cap as max_amount_human in whole tokens ("no more than 1 USDC" \u2192 max_amount_human: "1") \u2014 never convert it to atomic units by hand.`
4128
4257
  },
@@ -4143,6 +4272,11 @@ var toolDescriptions = {
4143
4272
  selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints \u2014 use haven_pay_x402 instead. Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances.",
4144
4273
  behavior: "Sends the requested amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves and before the agent is asked to sign \u2014 it is never queued for a human to approve later. The agent's signing key signs the account's typed data; Haven never receives the key.",
4145
4274
  nextActionGuidance: "On a decline, report the reason to the user and ask them to grant or raise the budget in Haven \u2014 there is nothing to poll and no approval will arrive. After a successful send, poll haven_get_payment_status until nextAction=none."
4275
+ },
4276
+ reportSettlementEvidence: {
4277
+ summary: "Report an erc7710 payment's real settlement transaction hash so Haven can verify it on-chain and confirm the payment.",
4278
+ behavior: "Pass payment_id and settlement_tx_hash (0x + 64 hex chars) \u2014 from PAYMENT-RESPONSE or a prior settlement_tx_hash. Haven verifies on-chain before confirming; a zero, mismatched, or reverted hash is refused. Your own payments only.",
4279
+ nextActionGuidance: "code DELIVERED_UNSETTLED: did not verify, do not retry \u2014 poll haven_get_payment_status. code SETTLEMENT_PENDING (retryable:true): not mined or RPC unreachable \u2014 report the same hash again shortly."
4146
4280
  }
4147
4281
  };
4148
4282
 
@@ -4745,15 +4879,30 @@ present and surface \`message\` or \`error\` verbatim. Common cases:
4745
4879
  Round the cap, or send an exact atomic \`max_amount\`.
4746
4880
  - \`PAYMENT_WINDOW_EXPIRED\`: re-run the quote/prepare tool with the same
4747
4881
  \`idempotency_key\`, then sign the fresh payload.
4882
+ - \`MERCHANT_NOT_READY\`: the merchant refused the quote with its own
4883
+ "cannot settle right now" signal (a 503 \`merchant_not_ready\` with a
4884
+ \`reason_code\`) instead of a 402. No payment was created. Tell the user;
4885
+ retry later (the message carries \`retry_after_s\` when the merchant gave
4886
+ one) \u2014 this is not a wrong or broken endpoint.
4748
4887
  - \`MERCHANT_REJECTED_AFTER_FUNDING\`: the merchant refused the paid retry.
4749
- Stop-and-sweep \u2014 stop retrying the merchant and use
4750
- \`mcp__haven__haven_sweep_delegate\` to recover stranded delegate funds.
4751
- - \`MERCHANT_UNRESPONSIVE_AFTER_FUNDING\`: funding confirmed on-chain, but the
4752
- merchant never answered the paid retry. This is NOT proof of rejection \u2014 the
4753
- merchant may still settle late. Verify-then-sweep, never a blind sweep:
4754
- check \`mcp__haven__haven_get_payment_status\`, retry
4888
+ On eip3009 (\`rail\` not \`erc7710\`): Stop-and-sweep \u2014 stop retrying the
4889
+ merchant and use \`mcp__haven__haven_sweep_delegate\` to recover stranded
4890
+ delegate funds. On erc7710 there is no funding leg and nothing to sweep:
4891
+ follow the message \u2014 it says whether the merchant declined to settle
4892
+ (re-quote later) or whether to check \`haven_get_payment_status\` after
4893
+ the payment window first.
4894
+ - \`MERCHANT_UNRESPONSIVE_AFTER_FUNDING\`: the merchant never answered the paid
4895
+ retry. This is NOT proof of rejection \u2014 the merchant may still settle late.
4896
+ On eip3009 (\`rail\` not \`erc7710\`), funding confirmed on-chain: Verify-then-sweep,
4897
+ never a blind sweep \u2014 check \`mcp__haven__haven_get_payment_status\`, retry
4755
4898
  \`mcp__haven__haven_complete_mcp_tool\` ONCE, and only sweep with
4756
- \`mcp__haven__haven_sweep_delegate\` if no settlement appears.
4899
+ \`mcp__haven__haven_sweep_delegate\` if no settlement appears. On erc7710
4900
+ there is no funding leg and nothing to sweep, and
4901
+ \`mcp__haven__haven_complete_mcp_tool\` has no erc7710 branch (it refuses a
4902
+ submitted intent) \u2014 do not retry it: the merchant may still redeem the
4903
+ settlement authorization within the payment window, so check
4904
+ \`mcp__haven__haven_get_payment_status\` after that window and re-quote only
4905
+ if it shows no settlement.
4757
4906
  - Budget exceeded: tell the user how much remains (from
4758
4907
  \`mcp__haven__haven_get_allowances\`) and that they can raise the budget in
4759
4908
  Haven.
@@ -4878,7 +5027,6 @@ exports.AgentPaymentFailureCode = AgentPaymentFailureCode;
4878
5027
  exports.AgentPaymentFailureCodeDescriptions = AgentPaymentFailureCodeDescriptions;
4879
5028
  exports.AgentPaymentFailureCodeSchema = AgentPaymentFailureCodeSchema;
4880
5029
  exports.AgentPaymentNextAction = AgentPaymentNextAction;
4881
- exports.AgentPaymentNextActionAccountAlias = AgentPaymentNextActionAccountAlias;
4882
5030
  exports.AgentPaymentNextActionDescriptions = AgentPaymentNextActionDescriptions;
4883
5031
  exports.AgentPaymentNextActionSchema = AgentPaymentNextActionSchema;
4884
5032
  exports.AgentPaymentPhase = AgentPaymentPhase;
@@ -4904,6 +5052,7 @@ exports.HavenPaymentStateError = HavenPaymentStateError;
4904
5052
  exports.HavenSigningError = HavenSigningError;
4905
5053
  exports.HavenTimeoutError = HavenTimeoutError;
4906
5054
  exports.HavenUnsupportedSignerVersionError = HavenUnsupportedSignerVersionError;
5055
+ exports.HavenZeroSettlementHashError = HavenZeroSettlementHashError;
4907
5056
  exports.MERCHANT_DISCOVERY_PATHS = MERCHANT_DISCOVERY_PATHS;
4908
5057
  exports.MerchantTimeoutError = MerchantTimeoutError;
4909
5058
  exports.RECEIPT_VERSION = RECEIPT_VERSION;
@@ -4925,12 +5074,10 @@ exports.X402_PAYMENT_HEADER_NAMES_SENT = X402_PAYMENT_HEADER_NAMES_SENT;
4925
5074
  exports.X402_PAYMENT_REQUIRED_HEADER_NAME = X402_PAYMENT_REQUIRED_HEADER_NAME;
4926
5075
  exports.X402_PAYMENT_RESPONSE_HEADER_NAME = X402_PAYMENT_RESPONSE_HEADER_NAME;
4927
5076
  exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
4928
- exports.accountAddressTwins = accountAddressTwins;
4929
5077
  exports.addressFromKey = addressFromKey;
4930
5078
  exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
4931
5079
  exports.buildSweepTypedData = buildSweepTypedData;
4932
5080
  exports.buildX402ExpectedMessage = buildX402ExpectedMessage;
4933
- exports.canonicalAgentPaymentNextAction = canonicalAgentPaymentNextAction;
4934
5081
  exports.compareNodeVersions = compareNodeVersions;
4935
5082
  exports.composeDescription = composeDescription;
4936
5083
  exports.connectorRerunCommand = connectorRerunCommand;
@@ -4944,14 +5091,12 @@ exports.encodePaymentProof = encodePaymentProof;
4944
5091
  exports.havenTools = havenTools;
4945
5092
  exports.isConnectorChannel = isConnectorChannel;
4946
5093
  exports.isErc7710Option = isErc7710Option;
4947
- exports.isFundAccountOrRaiseAllowance = isFundAccountOrRaiseAllowance;
4948
5094
  exports.isSupportedNodeVersion = isSupportedNodeVersion;
4949
5095
  exports.isSweepableChain = isSweepableChain;
5096
+ exports.isZeroSettlementTxHash = isZeroSettlementTxHash;
4950
5097
  exports.normalizePaymentRequired = normalizePaymentRequired;
4951
5098
  exports.parsePaymentRequired = parsePaymentRequired;
4952
5099
  exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
4953
- exports.readAccountAddress = readAccountAddress;
4954
- exports.readAccountId = readAccountId;
4955
5100
  exports.readX402ReceiptPayer = readX402ReceiptPayer;
4956
5101
  exports.resolveConnectorChannel = resolveConnectorChannel;
4957
5102
  exports.resolveTokenFromAddress = resolveTokenFromAddress;