@haven_ai/sdk 0.1.22-alpha.0 → 0.1.23-alpha.1

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
@@ -104,7 +104,23 @@ var AgentPaymentFailureCode = {
104
104
  * mechanical: re-send merchant_url, tool_name, arguments, and
105
105
  * mcp_transport explicitly (the version-skew path).
106
106
  */
107
- MerchantCallContextUnavailable: "MERCHANT_CALL_CONTEXT_UNAVAILABLE"
107
+ MerchantCallContextUnavailable: "MERCHANT_CALL_CONTEXT_UNAVAILABLE",
108
+ /**
109
+ * #1351: the caller supplied BOTH the atomic `max_amount` and the
110
+ * human-denominated `max_amount_human` cap for one purchase. Haven refuses
111
+ * to guess which the user meant — the two differ by a factor of 10^decimals,
112
+ * so picking wrong is exactly the silent-overspend this cap exists to
113
+ * prevent. Rejected before any merchant probe, funding intent, or signature.
114
+ */
115
+ AmbiguousMaxAmount: "AMBIGUOUS_MAX_AMOUNT",
116
+ /**
117
+ * #1351: a human-denominated cap was supplied, but it cannot be converted to
118
+ * atomic units against THIS quote — either the quote's asset has no known
119
+ * decimals on its network, or the cap carries more fraction digits than the
120
+ * asset can represent (truncating it would silently change the user's cap).
121
+ * The fallback is the exact atomic `max_amount`.
122
+ */
123
+ MaxAmountUnconvertible: "MAX_AMOUNT_UNCONVERTIBLE"
108
124
  };
109
125
  var AgentPaymentRail = {
110
126
  /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
@@ -159,7 +175,9 @@ var AgentPaymentFailureCodeDescriptions = {
159
175
  [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.",
160
176
  [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.",
161
177
  [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.",
162
- [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."
178
+ [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.",
179
+ [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.",
180
+ [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."
163
181
  };
164
182
  var AgentPaymentWarningCode = {
165
183
  /** No max_amount cap was supplied — the live quoted price was accepted as-is. */
@@ -685,73 +703,6 @@ function stableStringify(value) {
685
703
  const object = value;
686
704
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
687
705
  }
688
- function normalizeChallenge(value) {
689
- const candidate = value;
690
- if (!candidate || typeof candidate !== "object" || candidate.rail !== "mpp_demo" || typeof candidate.version !== "string" || typeof candidate.challengeId !== "string" || typeof candidate.resource !== "string" || typeof candidate.description !== "string" || // TODO: relax these checks when non-demo machine payment rails are added.
691
- candidate.network?.chainId !== 8453 || candidate.network?.name !== "base" || candidate.asset?.symbol !== "USDC" || typeof candidate.asset?.address !== "string" || candidate.asset.decimals !== 6 || typeof candidate.amount?.display !== "string" || typeof candidate.amount?.atomic !== "string" || typeof candidate.recipient !== "string" || typeof candidate.expiresAt !== "string") {
692
- return null;
693
- }
694
- return {
695
- rail: candidate.rail,
696
- version: candidate.version,
697
- challengeId: candidate.challengeId,
698
- resource: candidate.resource,
699
- description: candidate.description,
700
- network: candidate.network,
701
- asset: candidate.asset,
702
- amount: candidate.amount,
703
- recipient: candidate.recipient,
704
- expiresAt: candidate.expiresAt,
705
- metadata: candidate.metadata
706
- };
707
- }
708
- function parseMachinePaymentChallenge(response) {
709
- const header = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
710
- if (!header) {
711
- throw new Error("No MACHINE-PAYMENT-CHALLENGE header found in 402 response.");
712
- }
713
- const parsed = normalizeChallenge(
714
- decodeBase64Json(header, "MACHINE-PAYMENT-CHALLENGE header")
715
- );
716
- if (!parsed) throw new Error("Invalid machine payment challenge");
717
- return parsed;
718
- }
719
- async function parseMachinePaymentChallengeResponse(response) {
720
- try {
721
- return parseMachinePaymentChallenge(response);
722
- } catch (headerErr) {
723
- try {
724
- const body = await response.clone().json();
725
- const parsed = normalizeChallenge(body.challenge);
726
- if (parsed) return parsed;
727
- } catch {
728
- }
729
- throw headerErr;
730
- }
731
- }
732
- function buildMachinePaymentIdempotencyKey(challenge) {
733
- const material = [
734
- challenge.rail,
735
- challenge.challengeId,
736
- challenge.resource,
737
- challenge.recipient.toLowerCase(),
738
- challenge.asset.address.toLowerCase(),
739
- challenge.amount.atomic,
740
- challenge.network.chainId
741
- ].join("|");
742
- return `${challenge.rail}:${crypto.createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
743
- }
744
- function encodeMachinePaymentProof(receipt) {
745
- return encodeBase64Json({
746
- rail: receipt.rail,
747
- challengeId: receipt.challengeId,
748
- paymentId: receipt.paymentId,
749
- txHash: receipt.txHash,
750
- settledVia: "haven",
751
- payer: receipt.payer,
752
- chainId: receipt.chainId
753
- });
754
- }
755
706
  function createJsonRpcProvider(url) {
756
707
  return new ethers.ethers.JsonRpcProvider(url);
757
708
  }
@@ -872,9 +823,6 @@ function messageForState(label, status, paymentId, nextAction) {
872
823
  function sameAddress(a, b) {
873
824
  return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
874
825
  }
875
- function isMppRail(rail) {
876
- return rail === "mpp" || Boolean(rail?.startsWith("mpp_"));
877
- }
878
826
  function decimalFromUsdcAtomic(value) {
879
827
  const amount = BigInt(value);
880
828
  const whole = amount / 1000000n;
@@ -976,7 +924,6 @@ var HavenClient = class {
976
924
  chainRpcs;
977
925
  inFlightX402 = /* @__PURE__ */ new Map();
978
926
  x402ReceiptCache = /* @__PURE__ */ new Map();
979
- inFlightMachinePayments = /* @__PURE__ */ new Map();
980
927
  /**
981
928
  * Setup-time headers configured via `HavenClientConfig.defaultHeaders`.
982
929
  * Read-only after construction — use `withRequestContext` for per-call
@@ -1094,8 +1041,7 @@ var HavenClient = class {
1094
1041
  400
1095
1042
  );
1096
1043
  }
1097
- const agent = await this.getAgent();
1098
- const fundingTo = agent.delegateAddress;
1044
+ const fundingTo = options.delegateAddress ?? (await this.getAgent()).delegateAddress;
1099
1045
  if (!fundingTo) {
1100
1046
  throw new HavenApiError("Authenticated agent has no delegate address registered.", 502);
1101
1047
  }
@@ -1104,13 +1050,25 @@ var HavenClient = class {
1104
1050
  url: paymentRequired.resource.url,
1105
1051
  payTo: fundingTo,
1106
1052
  merchantPayTo: option.payTo,
1053
+ // #1360: this path ALWAYS means the EIP-3009 funding leg (payTo is the
1054
+ // agent's own delegate EOA). Saying so explicitly turns a stale/rotated
1055
+ // delegate address into the backend's LOUD shape-mismatch 400 instead
1056
+ // of a silent reroute to the erc7710 settlement branch (the #1358
1057
+ // review's open-budget misroute). Legacy-rail backends ignore the field.
1058
+ settlementScheme: "eip3009",
1107
1059
  amount: x402AuthorizationAmount(option),
1108
1060
  asset: option.asset,
1109
1061
  network: option.network,
1110
1062
  description: paymentRequired.resource.description,
1111
1063
  idempotencyKey,
1112
1064
  // #1307: persisted so the settle leg can rehydrate it by payment_id.
1113
- ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
1065
+ ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {},
1066
+ // #1355: persisted so the SIGN leg can rehydrate it by payment_id — the
1067
+ // local signer's context fetch then carries the 402 PaymentRequired and
1068
+ // the agent passes only payment_id. Bounded: the backend rejects >64KB,
1069
+ // so an oversized blob is omitted here (signer falls back to the
1070
+ // caller-supplied copy) rather than failing the intent.
1071
+ ...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {}
1114
1072
  });
1115
1073
  if (raw.status !== "pending_signature") {
1116
1074
  this.throwPaymentStateError("x402 payment", raw);
@@ -1242,6 +1200,17 @@ var HavenClient = class {
1242
1200
  * Get the agent identity tied to this API key.
1243
1201
  */
1244
1202
  async getAgent() {
1203
+ if (this.agentInFlight) return this.agentInFlight;
1204
+ const request = this.fetchAgent();
1205
+ this.agentInFlight = request;
1206
+ request.finally(() => {
1207
+ this.agentInFlight = null;
1208
+ }).catch(() => {
1209
+ });
1210
+ return request;
1211
+ }
1212
+ agentInFlight = null;
1213
+ async fetchAgent() {
1245
1214
  const raw = await this.get("/machine-payments/agent");
1246
1215
  return {
1247
1216
  id: raw.id,
@@ -1440,6 +1409,7 @@ var HavenClient = class {
1440
1409
  */
1441
1410
  async getPostPurchaseAllowanceSummary(paymentId) {
1442
1411
  const unavailable = (detail) => ({
1412
+ payment: null,
1443
1413
  allowance: null,
1444
1414
  warnings: [
1445
1415
  {
@@ -1448,27 +1418,38 @@ var HavenClient = class {
1448
1418
  }
1449
1419
  ]
1450
1420
  });
1421
+ const [statusResult, agentResult, allowanceResult] = await Promise.allSettled([
1422
+ this.getPaymentStatus(paymentId),
1423
+ this.getAgent(),
1424
+ this.getAllowances()
1425
+ ]);
1426
+ if (statusResult.status === "rejected") {
1427
+ return unavailable(statusResult.reason instanceof Error ? statusResult.reason.message : String(statusResult.reason));
1428
+ }
1429
+ const status = statusResult.value;
1430
+ if (agentResult.status === "rejected") {
1431
+ return { ...unavailable(agentResult.reason instanceof Error ? agentResult.reason.message : String(agentResult.reason)), payment: status };
1432
+ }
1433
+ if (allowanceResult.status === "rejected") {
1434
+ return { ...unavailable(allowanceResult.reason instanceof Error ? allowanceResult.reason.message : String(allowanceResult.reason)), payment: status };
1435
+ }
1451
1436
  try {
1452
- const [status, agent, allowanceSummary] = await Promise.all([
1453
- this.getPaymentStatus(paymentId),
1454
- this.getAgent(),
1455
- this.getAllowances()
1456
- ]);
1457
1437
  const tokenAddress = status.asset ?? status.x402?.asset ?? null;
1458
1438
  if (!tokenAddress) {
1459
- return unavailable("the settled payment does not carry a resolvable token address");
1439
+ return { ...unavailable("the settled payment does not carry a resolvable token address"), payment: status };
1460
1440
  }
1461
- const rail = agent.executionRail;
1441
+ const rail = agentResult.value.executionRail;
1462
1442
  const source = rail === "delegation" ? "active_delegations" : "allowance_module";
1463
- const match = allowanceSummary.allowances.find(
1443
+ const match = allowanceResult.value.allowances.find(
1464
1444
  (a) => a.tokenAddress.toLowerCase() === tokenAddress.toLowerCase()
1465
1445
  );
1466
1446
  if (!match) {
1467
- return unavailable("no allowance/budget row matches the settled token");
1447
+ return { ...unavailable("no allowance/budget row matches the settled token"), payment: status };
1468
1448
  }
1469
1449
  const token = resolveTokenFromAddress(match.tokenAddress);
1470
1450
  const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(match.onchain.remaining), token.decimals)} ${match.tokenSymbol}` : void 0;
1471
1451
  return {
1452
+ payment: status,
1472
1453
  allowance: {
1473
1454
  rail,
1474
1455
  remaining_atomic: match.onchain.remaining,
@@ -1516,6 +1497,7 @@ var HavenClient = class {
1516
1497
  async discoverTools(options = {}) {
1517
1498
  const params = new URLSearchParams();
1518
1499
  if (options.category) params.set("category", options.category);
1500
+ if (options.search !== void 0) params.set("search", options.search);
1519
1501
  if (options.rail) params.set("rail", options.rail);
1520
1502
  const query = params.size > 0 ? `?${params.toString()}` : "";
1521
1503
  const raw = await this.get(`/catalog${query}`);
@@ -1555,10 +1537,11 @@ var HavenClient = class {
1555
1537
  return { receipt, verification: verifyPaymentReceipt(receipt) };
1556
1538
  }
1557
1539
  /**
1558
- * Rehydrate the x402/MPP resume-state bundle for a payment id.
1540
+ * Rehydrate the x402 resume-state bundle for a payment id (#1328: the MPP
1541
+ * resume-state variant retired along with the rest of the mpp_demo surface).
1559
1542
  *
1560
1543
  * The server returns stored protocol context only. The client still signs the
1561
- * merchant proof locally when resumeX402Payment() or resumeMppPayment() runs.
1544
+ * merchant proof locally when resumeX402Payment() runs.
1562
1545
  */
1563
1546
  async getResumeState(paymentId) {
1564
1547
  return this.get(`/payments/${paymentId}/resume_state`);
@@ -1707,7 +1690,11 @@ var HavenClient = class {
1707
1690
  asset: option.asset,
1708
1691
  network: option.network,
1709
1692
  description: paymentRequired.resource.description,
1710
- idempotencyKey
1693
+ idempotencyKey,
1694
+ // #1360: same explicit funding-leg declaration as createX402Intent —
1695
+ // this local-key path derives payTo from the key (never stale), but the
1696
+ // declaration keeps both writers of the 3009 shape loud-by-default.
1697
+ settlementScheme: "eip3009"
1711
1698
  });
1712
1699
  if (raw.success && raw.tx_hash) {
1713
1700
  const receipt2 = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
@@ -1825,22 +1812,11 @@ var HavenClient = class {
1825
1812
  if (response.status !== 402) {
1826
1813
  return mcpSessionId ? this.surfaceMcpResult(response) : response;
1827
1814
  }
1828
- const machineChallengeHeader = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
1829
- if (machineChallengeHeader) {
1830
- const challenge = await parseMachinePaymentChallengeResponse(response);
1831
- return this.fetchWithMachinePayment(url, requestInit, challenge);
1832
- }
1833
1815
  let paymentRequired;
1834
1816
  try {
1835
1817
  paymentRequired = await parsePaymentRequiredResponse(response);
1836
1818
  } catch {
1837
- let challenge;
1838
- try {
1839
- challenge = await parseMachinePaymentChallengeResponse(response);
1840
- } catch {
1841
- return response;
1842
- }
1843
- return this.fetchWithMachinePayment(url, requestInit, challenge);
1819
+ return response;
1844
1820
  }
1845
1821
  if (!mcpSessionId && await responseHasBazaarExtension(response)) {
1846
1822
  mcpSessionId = await this.mcpInitialize(url, init);
@@ -1983,49 +1959,6 @@ var HavenClient = class {
1983
1959
  headers
1984
1960
  });
1985
1961
  }
1986
- /**
1987
- * Probe a paid MPP endpoint or inspect an existing challenge without creating
1988
- * a Haven payment or approval request.
1989
- */
1990
- async quoteMpp(challengeOrUrl, init, options = {}) {
1991
- if (typeof challengeOrUrl !== "string") {
1992
- const request2 = this.snapshotX402Request(challengeOrUrl.resource, init);
1993
- return this.buildMppQuote(challengeOrUrl, request2, options.idempotencyKey);
1994
- }
1995
- const request = this.snapshotX402Request(challengeOrUrl, init);
1996
- const response = await this.merchantFetch(challengeOrUrl, init);
1997
- if (response.status !== 402) {
1998
- throw new HavenApiError(
1999
- `Expected an MPP quote response with HTTP 402, got HTTP ${response.status}.`,
2000
- response.status || 400
2001
- );
2002
- }
2003
- const challenge = await parseMachinePaymentChallengeResponse(response);
2004
- return this.buildMppQuote(challenge, request, options.idempotencyKey);
2005
- }
2006
- /**
2007
- * Pay a previously inspected MPP quote and retry the exact captured request.
2008
- */
2009
- async payMppChallenge(quote, options = {}) {
2010
- const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
2011
- try {
2012
- const receipt = await this.authorizeMachinePayment(quote.challenge, { idempotencyKey });
2013
- return this.retryMppRequest(
2014
- quote.request.url,
2015
- this.requestInitFromSnapshot(quote.request),
2016
- quote.challenge,
2017
- receipt
2018
- );
2019
- } catch (err) {
2020
- this.attachResumeState(err, {
2021
- rail: "mpp",
2022
- challenge: quote.challenge,
2023
- idempotencyKey,
2024
- request: quote.request
2025
- });
2026
- throw err;
2027
- }
2028
- }
2029
1962
  async retryX402Request(url, initialInit, paymentRequired, receipt) {
2030
1963
  if (!receipt.accepted) {
2031
1964
  throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
@@ -2290,151 +2223,13 @@ var HavenClient = class {
2290
2223
  return void 0;
2291
2224
  }
2292
2225
  }
2293
- async authorizeMachinePayment(challenge, options = {}) {
2294
- if (!this.delegateKey) {
2295
- throw new HavenSigningError(
2296
- "delegateKey is required for machine payments. Pass it in the HavenClient config."
2297
- );
2298
- }
2299
- if (challenge.rail !== "mpp_demo") {
2300
- throw new HavenApiError(`Unsupported machine payment rail: ${challenge.rail}`, 400);
2301
- }
2302
- const idempotencyKey = options.idempotencyKey ?? buildMachinePaymentIdempotencyKey(challenge);
2303
- const inFlight = this.inFlightMachinePayments.get(idempotencyKey);
2304
- if (inFlight) return inFlight;
2305
- const promise = this.authorizeMppDemoPayment(challenge, idempotencyKey);
2306
- this.inFlightMachinePayments.set(idempotencyKey, promise);
2307
- try {
2308
- return await promise;
2309
- } catch (err) {
2310
- this.attachResumeState(err, {
2311
- rail: "mpp",
2312
- challenge,
2313
- idempotencyKey
2314
- });
2315
- throw err;
2316
- } finally {
2317
- this.inFlightMachinePayments.delete(idempotencyKey);
2318
- }
2319
- }
2320
- async authorizeMppDemoPayment(challenge, idempotencyKey) {
2321
- const raw = await this.post(
2322
- "/machine-payments/authorize",
2323
- { challenge, idempotencyKey }
2324
- );
2325
- if (raw.success && raw.tx_hash) {
2326
- return this.mapMachinePaymentReceipt(challenge, raw, raw.tx_hash);
2327
- }
2328
- this.throwIfNonSignableAuthorizationState("Machine payment", raw);
2329
- if (!raw.sign_data?.hash) {
2330
- throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
2331
- }
2332
- const sig = await this.signForData(raw.sign_data);
2333
- const execResult = await this.post(
2334
- `/payments/${raw.payment_id}/sign`,
2335
- { signature: sig }
2336
- );
2337
- if (execResult.status !== "confirmed" || !execResult.tx_hash) {
2338
- this.throwPaymentStateError("Machine payment", execResult);
2339
- }
2340
- return this.mapMachinePaymentReceipt(challenge, raw, execResult.tx_hash, execResult);
2341
- }
2342
- async resumeAuthorizedMpp(input) {
2343
- if (!this.delegateKey) {
2344
- throw new HavenSigningError(
2345
- "delegateKey is required for machine payments. Pass it in the HavenClient config."
2346
- );
2347
- }
2348
- const status = await this.getPaymentStatus(input.paymentId);
2349
- this.assertCanResumeMpp(status, input.challenge);
2350
- return this.mapMachinePaymentReceiptFromStatus(input.challenge, status);
2351
- }
2352
- async resumeMppPayment(input) {
2353
- const inputInit = "init" in input ? input.init : void 0;
2354
- const initialInit = inputInit ?? (input.request ? this.requestInitFromSnapshot(input.request) : void 0);
2355
- let challenge = input.challenge;
2356
- const url = input.url ?? input.request?.url;
2357
- if (!challenge) {
2358
- if (!url) {
2359
- throw new HavenApiError("MPP resume requires the original URL or a captured request snapshot.", 400);
2360
- }
2361
- const response = await this.merchantFetch(url, initialInit);
2362
- if (response.status !== 402) {
2363
- throw new HavenApiError("Expected the original MPP request to return HTTP 402 before resuming.", 400);
2364
- }
2365
- challenge = await parseMachinePaymentChallengeResponse(response);
2366
- }
2367
- const receipt = await this.resumeAuthorizedMpp({
2368
- paymentId: input.paymentId,
2369
- challenge,
2370
- idempotencyKey: input.idempotencyKey
2371
- });
2372
- return this.retryMppRequest(url ?? challenge.resource, initialInit, challenge, receipt);
2373
- }
2374
- async fetchWithMachinePayment(url, initialInit, challenge) {
2375
- const request = this.snapshotX402Request(url, initialInit);
2376
- const idempotencyKey = buildMachinePaymentIdempotencyKey(challenge);
2377
- let receipt;
2378
- try {
2379
- receipt = await this.authorizeMachinePayment(challenge, { idempotencyKey });
2380
- } catch (err) {
2381
- this.attachResumeState(err, {
2382
- rail: "mpp",
2383
- challenge,
2384
- idempotencyKey,
2385
- request
2386
- });
2387
- throw err;
2388
- }
2389
- return this.retryMppRequest(url, initialInit, challenge, receipt);
2390
- }
2391
- async retryMppRequest(url, initialInit, challenge, receipt) {
2392
- const retryHeaders = new Headers(initialInit?.headers);
2393
- retryHeaders.set("MACHINE-PAYMENT-PROOF", receipt.proofHeader);
2394
- const retryResponse = await this.merchantFetch(url, {
2395
- ...initialInit,
2396
- headers: retryHeaders
2397
- });
2398
- if (!retryResponse.ok) {
2399
- const merchant = await captureMerchantResponse(retryResponse);
2400
- await this.recordMerchantRetryRejected({
2401
- rail: receipt.rail,
2402
- paymentId: receipt.paymentId,
2403
- txHash: receipt.txHash,
2404
- resourceUrl: receipt.resourceUrl,
2405
- merchant,
2406
- details: {
2407
- challenge_id: receipt.challengeId
2408
- }
2409
- });
2410
- throw new HavenApiError(
2411
- "Machine payment retry failed after Haven sent the payment.",
2412
- merchant.merchant_status,
2413
- {
2414
- marker: "machine_payment_retry_rejected_after_payment",
2415
- payment_id: receipt.paymentId,
2416
- tx_hash: receipt.txHash,
2417
- resource_url: receipt.resourceUrl,
2418
- rail: receipt.rail,
2419
- ...merchant
2420
- }
2421
- );
2422
- }
2423
- await this.reportMachinePaymentEvidence({
2424
- paymentId: receipt.paymentId,
2425
- rail: receipt.rail,
2426
- txHash: receipt.txHash,
2427
- resourceUrl: receipt.resourceUrl,
2428
- merchantStatus: retryResponse.status,
2429
- challengePayload: challenge,
2430
- paymentProofHeaderName: "MACHINE-PAYMENT-PROOF",
2431
- paymentProofHeader: receipt.proofHeader,
2432
- protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
2433
- protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
2434
- });
2435
- await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
2436
- return retryResponse;
2437
- }
2226
+ // #1328: authorizeMachinePayment / authorizeMppDemoPayment / resumeAuthorizedMpp
2227
+ // / resumeMppPayment / fetchWithMachinePayment / retryMppRequest (the
2228
+ // MACHINE-PAYMENT-CHALLENGE / mpp_demo client surface) are retired — the
2229
+ // backend's POST /machine-payments/authorize refuses unconditionally now,
2230
+ // and MACHINE-PAYMENT-CHALLENGE was never produced by any other Haven
2231
+ // surface. Use the x402 flow (authorizeX402 / fetch / quoteX402 / payX402Quote)
2232
+ // for agent-to-merchant payments.
2438
2233
  assertCanResumeX402(status, paymentRequired, option) {
2439
2234
  if (status.rail !== "x402") {
2440
2235
  throw new HavenPaymentStateError(
@@ -2498,68 +2293,6 @@ var HavenClient = class {
2498
2293
  );
2499
2294
  }
2500
2295
  }
2501
- assertCanResumeMpp(status, challenge) {
2502
- if (!isMppRail(status.rail)) {
2503
- throw new HavenPaymentStateError(
2504
- `Payment ${status.paymentId} is ${status.rail}, not MPP.`,
2505
- 409,
2506
- status
2507
- );
2508
- }
2509
- if (status.nextAction !== AgentPaymentNextAction.RetryOriginalX402Request) {
2510
- throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
2511
- }
2512
- if (!status.txHash) {
2513
- throw new HavenApiError(
2514
- `MPP payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
2515
- 502,
2516
- status,
2517
- status.paymentId
2518
- );
2519
- }
2520
- if (status.resourceUrl && status.resourceUrl !== challenge.resource) {
2521
- throw new HavenApiError(
2522
- "MPP resume request does not match the approved resource URL.",
2523
- 409,
2524
- { status, challenge },
2525
- status.paymentId
2526
- );
2527
- }
2528
- if (status.merchantAddress && !sameAddress(status.merchantAddress, challenge.recipient)) {
2529
- throw new HavenApiError(
2530
- "MPP resume request does not match the approved merchant.",
2531
- 409,
2532
- { status, challenge },
2533
- status.paymentId
2534
- );
2535
- }
2536
- if (status.chainId && status.chainId !== challenge.network.chainId) {
2537
- throw new HavenApiError(
2538
- "MPP resume request does not match the approved network.",
2539
- 409,
2540
- { status, challenge },
2541
- status.paymentId
2542
- );
2543
- }
2544
- if (status.token && status.token !== challenge.asset.symbol) {
2545
- throw new HavenApiError(
2546
- "MPP resume request does not match the approved token.",
2547
- 409,
2548
- { status, challenge },
2549
- status.paymentId
2550
- );
2551
- }
2552
- const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
2553
- const requestedAmount = normalizeDecimal(challenge.amount.display);
2554
- if (approvedAmount && approvedAmount !== requestedAmount) {
2555
- throw new HavenApiError(
2556
- "MPP resume request does not match the approved amount.",
2557
- 409,
2558
- { status, challenge },
2559
- status.paymentId
2560
- );
2561
- }
2562
- }
2563
2296
  mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
2564
2297
  const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
2565
2298
  const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
@@ -2666,53 +2399,6 @@ var HavenClient = class {
2666
2399
  this.x402ReceiptCache.set(idempotencyKey, { expiresAt, receipt });
2667
2400
  }
2668
2401
  }
2669
- mapMachinePaymentReceipt(challenge, raw, txHash, execResult) {
2670
- const receiptWithoutHeader = {
2671
- success: true,
2672
- rail: challenge.rail,
2673
- paymentId: raw.payment_id,
2674
- challengeId: challenge.challengeId,
2675
- txHash,
2676
- token: execResult?.token ?? raw.token ?? challenge.asset.symbol,
2677
- amount: execResult?.amount ?? raw.amount ?? challenge.amount.display,
2678
- to: execResult?.to ?? raw.to ?? challenge.recipient,
2679
- resourceUrl: raw.resource_url ?? challenge.resource,
2680
- explorerUrl: execResult?.explorer_url ?? raw.explorer_url ?? buildExplorerUrl(execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId, txHash),
2681
- payer: raw.payer ?? raw.safe_address,
2682
- chainId: execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId
2683
- };
2684
- return {
2685
- ...receiptWithoutHeader,
2686
- proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
2687
- };
2688
- }
2689
- mapMachinePaymentReceiptFromStatus(challenge, status) {
2690
- if (!status.txHash) {
2691
- throw new HavenApiError(
2692
- `MPP payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
2693
- 502,
2694
- status,
2695
- status.paymentId
2696
- );
2697
- }
2698
- const receiptWithoutHeader = {
2699
- success: true,
2700
- rail: challenge.rail,
2701
- paymentId: status.paymentId,
2702
- challengeId: challenge.challengeId,
2703
- txHash: status.txHash,
2704
- token: status.token || challenge.asset.symbol,
2705
- amount: status.amount || challenge.amount.display,
2706
- to: status.merchantAddress ?? challenge.recipient,
2707
- resourceUrl: status.resourceUrl ?? challenge.resource,
2708
- explorerUrl: explorerUrlOrEmpty(status.chainId || challenge.network.chainId, status.txHash),
2709
- chainId: status.chainId || challenge.network.chainId
2710
- };
2711
- return {
2712
- ...receiptWithoutHeader,
2713
- proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
2714
- };
2715
- }
2716
2402
  async recordMerchantRetryRejected(input) {
2717
2403
  try {
2718
2404
  await this.post("/machine-payments/reconciliation-events", {
@@ -2910,6 +2596,11 @@ var HavenClient = class {
2910
2596
  amountAtomic: x402AuthorizationAmount(option),
2911
2597
  amount: decimalFromUsdcAtomic(x402AuthorizationAmount(option)),
2912
2598
  token: token?.symbol ?? "USDC",
2599
+ // #1351: null when the asset is unrecognised on this network — the
2600
+ // `token` fallback above is a LABEL, not evidence of 6 decimals, and a
2601
+ // human-denominated cap must fail closed rather than convert against a
2602
+ // guess. Same resolution as `token`, so the two never disagree.
2603
+ decimals: token?.decimals ?? null,
2913
2604
  asset: option.asset,
2914
2605
  network: option.network,
2915
2606
  chainId: chainIdOrNull(option.network),
@@ -2950,63 +2641,18 @@ var HavenClient = class {
2950
2641
  merchantAddress: input.accepted.payTo
2951
2642
  };
2952
2643
  }
2953
- buildMppQuote(challenge, request, idempotencyKey) {
2954
- return {
2955
- rail: "mpp",
2956
- paymentRail: challenge.rail,
2957
- idempotencyKey: idempotencyKey ?? buildMachinePaymentIdempotencyKey(challenge),
2958
- challenge,
2959
- request,
2960
- resourceUrl: challenge.resource,
2961
- description: challenge.description ?? null,
2962
- amountAtomic: challenge.amount.atomic,
2963
- amount: challenge.amount.display,
2964
- token: challenge.asset.symbol,
2965
- asset: challenge.asset.address,
2966
- network: challenge.network.name,
2967
- chainId: challenge.network.chainId,
2968
- merchantAddress: challenge.recipient,
2969
- expiresAt: challenge.expiresAt
2970
- };
2971
- }
2972
- buildMppResumeState(input) {
2973
- const quote = this.buildMppQuote(
2974
- input.challenge,
2975
- input.request ?? this.snapshotX402Request(input.challenge.resource),
2976
- input.idempotencyKey
2977
- );
2978
- return {
2979
- rail: "mpp",
2980
- paymentRail: quote.paymentRail,
2981
- paymentId: input.paymentId,
2982
- idempotencyKey: quote.idempotencyKey,
2983
- challenge: input.challenge,
2984
- url: input.request?.url ?? input.challenge.resource,
2985
- request: input.request,
2986
- resourceUrl: quote.resourceUrl,
2987
- description: quote.description,
2988
- amountAtomic: quote.amountAtomic,
2989
- amount: quote.amount,
2990
- token: quote.token,
2991
- asset: quote.asset,
2992
- network: quote.network,
2993
- chainId: quote.chainId,
2994
- merchantAddress: quote.merchantAddress,
2995
- expiresAt: quote.expiresAt
2996
- };
2997
- }
2644
+ // #1328: attachResumeState's 'mpp' branch (buildMppQuote / buildMppResumeState
2645
+ // / attachMppResumeState) is retired along with the rest of the MPP-demo
2646
+ // client surface — every remaining caller passes rail: 'x402' only, so this
2647
+ // is now a direct alias for attachX402ResumeState rather than a dispatcher.
2998
2648
  attachResumeState(err, input) {
2999
- if (input.rail === "x402") {
3000
- this.attachX402ResumeState(
3001
- err,
3002
- input.paymentRequired,
3003
- input.accepted,
3004
- input.idempotencyKey,
3005
- input.request
3006
- );
3007
- return;
3008
- }
3009
- this.attachMppResumeState(err, input.challenge, input.idempotencyKey, input.request);
2649
+ this.attachX402ResumeState(
2650
+ err,
2651
+ input.paymentRequired,
2652
+ input.accepted,
2653
+ input.idempotencyKey,
2654
+ input.request
2655
+ );
3010
2656
  }
3011
2657
  attachX402ResumeState(err, paymentRequired, accepted, idempotencyKey, request) {
3012
2658
  if (!(err instanceof HavenPaymentStateError)) return;
@@ -3019,16 +2665,6 @@ var HavenClient = class {
3019
2665
  request
3020
2666
  });
3021
2667
  }
3022
- attachMppResumeState(err, challenge, idempotencyKey, request) {
3023
- if (!(err instanceof HavenPaymentStateError)) return;
3024
- if (!isMppRail(err.state.rail)) return;
3025
- err.resumeState = this.buildMppResumeState({
3026
- paymentId: err.state.paymentId,
3027
- challenge,
3028
- idempotencyKey,
3029
- request
3030
- });
3031
- }
3032
2668
  // ── Tool Execution (for agent frameworks) ────────────────────────
3033
2669
  /**
3034
2670
  * Execute a tool call by name and input.
@@ -3087,29 +2723,6 @@ var HavenClient = class {
3087
2723
  return this.toolError(err);
3088
2724
  }
3089
2725
  }
3090
- if (toolName === "authorize_machine_payment") {
3091
- const { challenge, idempotencyKey } = input;
3092
- try {
3093
- const receipt = await this.authorizeMachinePayment(challenge, { idempotencyKey });
3094
- return {
3095
- success: true,
3096
- payment_id: receipt.paymentId,
3097
- tx_hash: receipt.txHash,
3098
- token: receipt.token,
3099
- amount: receipt.amount,
3100
- to: receipt.to,
3101
- resource_url: receipt.resourceUrl,
3102
- explorer_url: receipt.explorerUrl,
3103
- proof_header: receipt.proofHeader,
3104
- rail: receipt.rail,
3105
- challenge_id: receipt.challengeId,
3106
- payer: receipt.payer,
3107
- chain_id: receipt.chainId
3108
- };
3109
- } catch (err) {
3110
- return this.toolError(err);
3111
- }
3112
- }
3113
2726
  if (toolName === "get_payment_status") {
3114
2727
  const { payment_id } = input;
3115
2728
  const result = await this.getPaymentStatus(payment_id);
@@ -3457,7 +3070,7 @@ var toolDescriptions = {
3457
3070
  payX402OneShot: {
3458
3071
  summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
3459
3072
  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.",
3460
- behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the EIP-3009 payment from the delegate wallet, asks Haven for a Safe AllowanceModule top-up if needed, then retries the original request with the X-PAYMENT header and returns the merchant response. If the resource returns an MPP machine-payment challenge instead of standard x402, the MPP payment path is used automatically. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
3073
+ behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the EIP-3009 payment from the delegate wallet, asks Haven for a Safe AllowanceModule top-up if needed, then retries the original request with the X-PAYMENT header and returns the merchant response. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
3461
3074
  nextActionGuidance: "If approval is needed, preserve the returned resume_state or paymentId and call the resume tool once nextAction=retry_original_x402_request. If the response carries phase=insufficient_funds and nextAction=fund_safe_or_raise_allowance, the payment cannot be retried until the originating Safe is funded or the agent allowance raised \u2014 stop and tell the user the shortfall reported on the response."
3462
3075
  },
3463
3076
  resumeX402: {
@@ -3465,29 +3078,17 @@ var toolDescriptions = {
3465
3078
  behavior: "Accepts either resume_state or payment_id, validates the original x402 details against the approved Haven funding, and retries the merchant request with the X-PAYMENT header. No new Haven approval is created.",
3466
3079
  nextActionGuidance: "Only use when get_payment_status returns nextAction=retry_original_x402_request; do not start a new merchant session."
3467
3080
  },
3468
- quoteMpp: {
3469
- summary: "Inspect a Haven MPP challenge or paid MPP URL without creating a Haven payment, signature, approval, or on-chain transaction.",
3470
- behavior: "Parses an MPP challenge envelope and returns a typed quote with rail tag, amount, asset, and merchant context. Pure read-only \u2014 Haven is not contacted.",
3471
- nextActionGuidance: "On success the returned quote is the input to haven_pay_mpp_challenge. Do not call the merchant again \u2014 Haven re-uses the captured request when paying."
3472
- },
3473
- payMpp: {
3474
- summary: "Pay an inspected MPP challenge. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",
3475
- 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.",
3476
- behavior: "Authorizes the payment through Haven within the on-chain allowance, signs the challenge proof, and returns the proof header for retrying the original paid resource.",
3477
- nextActionGuidance: "If approval is needed, preserve resume_state or payment_id and wait for nextAction=retry_original_x402_request before resuming."
3478
- },
3479
- resumeMpp: {
3480
- summary: "Resume an MPP payment after the Haven wallet owner approved the funding step.",
3481
- behavior: "Accepts either resume_state or payment_id and retries the original paid resource with the MPP proof header. No new Haven approval is created.",
3482
- nextActionGuidance: ""
3483
- },
3081
+ // #1328: quoteMpp / payMpp / resumeMpp (the mpp_demo challenge/quote/resume
3082
+ // fragments) are retired along with the client surface they described —
3083
+ // MACHINE-PAYMENT-CHALLENGE was never produced by anything besides the now
3084
+ // deleted `/demo/mpp/*` route. Use the x402 fragments above instead.
3484
3085
  getPaymentStatus: {
3485
3086
  summary: "Fetch structured Haven payment status, including phase and nextAction taxonomy for agent recovery.",
3486
3087
  behavior: "Accepts a payment intent or approval request id and returns the full state taxonomy (phase, nextAction, rail, amount, merchant, resource url, idempotency key, message).",
3487
3088
  nextActionGuidance: ""
3488
3089
  },
3489
3090
  getResumeState: {
3490
- summary: "Rehydrate stored x402 or MPP resume_state by payment_id.",
3091
+ summary: "Rehydrate stored x402 resume_state by payment_id.",
3491
3092
  behavior: "Returns the context that the agent originally received in a pending-approval response, reconstructed from Haven's database. This is context only; signing still happens locally when a resume tool is called.",
3492
3093
  nextActionGuidance: ""
3493
3094
  },
@@ -3524,18 +3125,18 @@ var toolDescriptions = {
3524
3125
  discoverTools: {
3525
3126
  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.",
3526
3127
  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. 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.",
3527
- 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. Returns name, description, price, rail, resource URL, tool_name, tool_arguments, and suggested_tool. 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.",
3528
- 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 max_amount when the user has a cap."
3128
+ 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, and suggested_tool. 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.",
3129
+ 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 (#1351).`
3529
3130
  },
3530
3131
  sweep_delegate: {
3531
3132
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
3532
- selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402 or haven_pay_mpp_challenge. Do NOT use to read balances only \u2014 use haven_get_allowances.",
3133
+ selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402. Do NOT use to read balances only \u2014 use haven_get_allowances.",
3533
3134
  behavior: "Reads the delegate EOA's on-chain USDC and ETH balances. For each non-zero balance, signs and submits a transfer from the delegate EOA to the originating Safe (hardcoded destination). The delegate key signs locally \u2014 Haven never sees it and the backend never constructs signed transactions (CASP/MiCA Red Line #2). Returns tx hashes and recovered amounts. Returns an empty transfers list when nothing is stranded.",
3534
3135
  nextActionGuidance: "If transfers is non-empty, confirm the amounts with the user. No further action required \u2014 funds are on their way back to the Safe."
3535
3136
  },
3536
3137
  send: {
3537
3138
  summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
3538
- 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 (use haven_pay_x402 instead) or MPP merchant payments (use haven_pay_mpp_challenge). Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances.",
3139
+ 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.",
3539
3140
  behavior: "Sends the requested amount through the Safe AllowanceModule. Amounts within the remaining on-chain allowance for the asset execute automatically; amounts that exceed the allowance are queued as pending_approval for the wallet owner to approve in Haven. The agent's signing key signs the AllowanceModule transfer hash; Haven never receives the key.",
3540
3141
  nextActionGuidance: "If pending_approval is returned, preserve the payment_id and wait for the wallet owner to approve in Haven. Poll haven_get_payment_status until nextAction=none."
3541
3142
  }
@@ -3651,22 +3252,11 @@ var resumeX402Schema = {
3651
3252
  },
3652
3253
  required: ["payment_id", "url", "payTo", "amount", "asset", "network"]
3653
3254
  };
3654
- var authorizeMachinePaymentSchema = {
3655
- type: "object",
3656
- properties: {
3657
- challenge: {
3658
- type: "object",
3659
- description: "Machine payment challenge returned by a Haven demo endpoint"
3660
- }
3661
- },
3662
- required: ["challenge"]
3663
- };
3664
3255
  var MAKE_PAYMENT_DESCRIPTION = "Request and sign a payment from the user-controlled Safe within approved on-chain limits. 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, validates the signed intent, and relays the Safe AllowanceModule transaction; it does not hold keys or control funds. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
3665
3256
  var GET_STATUS_DESCRIPTION = toolDescriptions.getPaymentStatus.summary + " Accepts payment intent IDs and approval request IDs. Returns the current status, phase, next_action, transaction hash if available, and payment details.";
3666
3257
  var GET_ALLOWANCES_DESCRIPTION = composeDescription(toolDescriptions.getAllowances);
3667
3258
  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 and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, preserve the original merchant/MCP session and x402 details, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not start a new merchant session or loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
3668
3259
  var RESUME_X402_DESCRIPTION = toolDescriptions.resumeX402.summary + " Use this only after get_payment_status returns next_action=retry_original_x402_request. It checks the approved payment, validates the original x402 details, and returns a merchant X-PAYMENT header without creating a new approval request or merchant session.";
3669
- var AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION = composeDescription(toolDescriptions.payMpp) + " In this SDK tool set, the allowance lookup tool is get_allowances. Currently scoped to the internal MPP demo rail. The agent signs the payment, Haven relays it within the on-chain allowance, and the tool returns a proof header for the retry request.";
3670
3260
  var SWEEP_DELEGATE_DESCRIPTION = composeDescription(toolDescriptions.sweep_delegate);
3671
3261
  var sweepDelegateSchema = {
3672
3262
  type: "object",
@@ -3700,11 +3290,6 @@ function claudeTools() {
3700
3290
  description: RESUME_X402_DESCRIPTION,
3701
3291
  input_schema: resumeX402Schema
3702
3292
  },
3703
- {
3704
- name: "authorize_machine_payment",
3705
- description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
3706
- input_schema: authorizeMachinePaymentSchema
3707
- },
3708
3293
  {
3709
3294
  name: "haven_sweep_delegate",
3710
3295
  description: SWEEP_DELEGATE_DESCRIPTION,
@@ -3754,14 +3339,6 @@ function openaiTools() {
3754
3339
  parameters: resumeX402Schema
3755
3340
  }
3756
3341
  },
3757
- {
3758
- type: "function",
3759
- function: {
3760
- name: "authorize_machine_payment",
3761
- description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
3762
- parameters: authorizeMachinePaymentSchema
3763
- }
3764
- },
3765
3342
  {
3766
3343
  type: "function",
3767
3344
  function: {
@@ -3837,10 +3414,13 @@ normal, not an error.
3837
3414
 
3838
3415
  1. \`mcp__haven__haven_discover_tools\` to find a payable service and its
3839
3416
  \`catalog_id\`.
3840
- 2. \`mcp__haven__haven_prepare_catalog_purchase\` with \`catalog_id\` and
3841
- \`max_amount\`. \`max_amount\` (atomic units) is REQUIRED on this tool, and
3842
- is best practice on every paid call below too \u2014 it caps what the LIVE
3843
- merchant quote may charge, checked before any funding intent is created.
3417
+ 2. \`mcp__haven__haven_prepare_catalog_purchase\` with \`catalog_id\` and a
3418
+ spending cap. A cap is REQUIRED on this tool and is best practice on every
3419
+ paid call below too \u2014 it caps what the LIVE merchant quote may charge,
3420
+ checked before any funding intent is created. Write it the way the user
3421
+ said it: \`max_amount_human\` is whole tokens, so "no more than 1 USDC" is
3422
+ \`max_amount_human: "1"\`. (\`max_amount\` is the atomic-unit form, where
3423
+ "1" means 0.000001 USDC \u2014 do not convert by hand, and never send both.)
3844
3424
  3. Then FOLLOW THE RESPONSE'S GUIDANCE FIELDS: \`next_action\`, \`next_tool\`,
3845
3425
  and \`next_arguments\` name the exact next call \u2014 act on those first; the
3846
3426
  prose in this section is fallback and debugging detail. If the catalog
@@ -3848,9 +3428,11 @@ normal, not an error.
3848
3428
  \`mcp__haven__haven_pay_mcp_tool\` (merchant URL, tool name, arguments) as
3849
3429
  the manual fallback.
3850
3430
 
3851
- **Signing:** \`mcp__haven-signer__haven_sign_x402\` with \`payment_id\` and
3852
- \`payment_required\` ONLY \u2014 the local signer fetches the exact signing bytes
3853
- itself, so never relay \`typed_data\` yourself. Fallback for an older signer
3431
+ **Signing:** \`mcp__haven-signer__haven_sign_x402\` with \`payment_id\` ONLY \u2014
3432
+ the local signer fetches the exact signing bytes AND \`payment_required\`
3433
+ itself, so never relay \`typed_data\` or the 402 blob yourself. If the signer
3434
+ reports its fetched context carried no \`payment_required\` (older backend),
3435
+ re-call with \`payment_required\` added verbatim. Fallback for an older signer
3854
3436
  or backend: re-run the quote/prepare tool with the SAME \`idempotency_key\`
3855
3437
  plus \`include_signing_payload=true\`, then pass \`payload_hash\`,
3856
3438
  \`x402_expected\` (the nested \`x402.expected\` object), and
@@ -3920,8 +3502,14 @@ present and surface \`message\` or \`error\` verbatim. Common cases:
3920
3502
  - \`pending_approval\`: queued for the user's approval (see above).
3921
3503
  - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
3922
3504
  Suggest the user add funds in the Haven dashboard.
3923
- - \`PRICE_EXCEEDS_MAX\`: the live merchant price exceeded your \`max_amount\`.
3924
- No funds moved; ask the user before retrying with a higher cap.
3505
+ - \`PRICE_EXCEEDS_MAX\`: the live merchant price exceeded your cap. No funds
3506
+ moved; ask the user before retrying with a higher one.
3507
+ - \`AMBIGUOUS_MAX_AMOUNT\`: you sent both \`max_amount\` and
3508
+ \`max_amount_human\`. Nothing was contacted or spent \u2014 re-send with exactly
3509
+ one (\`max_amount_human\` for a cap the user stated in tokens).
3510
+ - \`MAX_AMOUNT_UNCONVERTIBLE\`: \`max_amount_human\` does not fit this quote's
3511
+ asset \u2014 unknown decimals, or more decimal places than the asset supports.
3512
+ Round the cap, or send an exact atomic \`max_amount\`.
3925
3513
  - \`PAYMENT_WINDOW_EXPIRED\`: re-run the quote/prepare tool with the same
3926
3514
  \`idempotency_key\`, then sign the fresh payload.
3927
3515
  - \`MERCHANT_REJECTED_AFTER_FUNDING\`: the merchant refused the paid retry.
@@ -3940,10 +3528,12 @@ present and surface \`message\` or \`error\` verbatim. Common cases:
3940
3528
  ## Reporting after a purchase
3941
3529
 
3942
3530
  A settled \`mcp__haven__haven_settle_mcp_tool\` response carries
3943
- \`agent_summary\` and the remaining post-purchase allowance in \`allowance\` \u2014
3944
- report the amount paid and what is left from those fields directly. Do not
3945
- call \`haven_get_agent\` or \`haven_get_allowances\` again just to report a
3946
- purchase you already made.
3531
+ \`agent_summary.purchase_summary\` and the remaining post-purchase allowance
3532
+ in \`allowance\` \u2014 report the product, Haven-derived payment/transaction
3533
+ fields, and what is left from those fields directly. \`result\` is optional
3534
+ raw merchant evidence; never use it to decide whether the purchase was paid.
3535
+ Do not call \`haven_get_agent\` or \`haven_get_allowances\` again just to
3536
+ report a purchase you already made.
3947
3537
 
3948
3538
  ## Revoke
3949
3539
 
@@ -3954,7 +3544,7 @@ for that credential.
3954
3544
  var SKILL_FOLDER_NAME = "haven-pay";
3955
3545
 
3956
3546
  // src/node-version.ts
3957
- var HAVEN_MINIMUM_NODE_VERSION = "24.0.0";
3547
+ var HAVEN_MINIMUM_NODE_VERSION = "22.0.0";
3958
3548
  function parseNodeVersion(value) {
3959
3549
  const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
3960
3550
  if (!match) return [0, 0, 0];
@@ -4183,7 +3773,6 @@ exports.X402UnexpectedStatusError = X402UnexpectedStatusError;
4183
3773
  exports.X402_MAX_AUTHORIZATION_WINDOW_SECONDS = X402_MAX_AUTHORIZATION_WINDOW_SECONDS;
4184
3774
  exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
4185
3775
  exports.addressFromKey = addressFromKey;
4186
- exports.buildMachinePaymentIdempotencyKey = buildMachinePaymentIdempotencyKey;
4187
3776
  exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
4188
3777
  exports.buildSweepTypedData = buildSweepTypedData;
4189
3778
  exports.buildX402ExpectedMessage = buildX402ExpectedMessage;
@@ -4194,15 +3783,13 @@ exports.decodeBase64Utf8 = decodeBase64Utf8;
4194
3783
  exports.discoverMerchantMcpUrl = discoverMerchantMcpUrl;
4195
3784
  exports.encodeBase64Json = encodeBase64Json;
4196
3785
  exports.encodeBase64Utf8 = encodeBase64Utf8;
4197
- exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
4198
3786
  exports.encodePaymentProof = encodePaymentProof;
4199
3787
  exports.havenTools = havenTools;
4200
3788
  exports.isSupportedNodeVersion = isSupportedNodeVersion;
4201
3789
  exports.isSweepableChain = isSweepableChain;
4202
- exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
4203
- exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
4204
3790
  exports.parsePaymentRequired = parsePaymentRequired;
4205
3791
  exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
3792
+ exports.resolveTokenFromAddress = resolveTokenFromAddress;
4206
3793
  exports.sameUrl = sameUrl;
4207
3794
  exports.selectPaymentOption = selectPaymentOption;
4208
3795
  exports.selectStandardPaymentOption = selectStandardPaymentOption;