@haven_ai/sdk 0.2.1-alpha.0 → 0.4.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,12 +114,12 @@ 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
@@ -146,19 +146,6 @@ var AgentPaymentNextAction = {
146
146
  */
147
147
  AwaitingSettlementEvidence: "awaiting_settlement_evidence"
148
148
  };
149
- var AgentPaymentNextActionAccountAlias = {
150
- /** Account-vocabulary twin of `fund_safe_or_raise_allowance`; same meaning. */
151
- FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance"
152
- };
153
- function canonicalAgentPaymentNextAction(value) {
154
- if (value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance) {
155
- return AgentPaymentNextAction.FundSafeOrRaiseAllowance;
156
- }
157
- return value;
158
- }
159
- function isFundAccountOrRaiseAllowance(value) {
160
- return value === AgentPaymentNextAction.FundSafeOrRaiseAllowance || value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance;
161
- }
162
149
  var AgentPaymentFailureCode = {
163
150
  /** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
164
151
  PriceExceedsMax: "PRICE_EXCEEDS_MAX",
@@ -253,7 +240,7 @@ var AgentPaymentNextActionDescriptions = {
253
240
  [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
254
241
  [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
255
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.",
256
- [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.",
257
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.",
258
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.",
259
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."
@@ -721,7 +708,13 @@ function normalizePaymentOption(value) {
721
708
  mimeType: candidate.mimeType,
722
709
  asset: candidate.asset,
723
710
  payTo: candidate.payTo,
724
- maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
711
+ // Keep the merchant offer intact for v2 `accepted` matching. Only the
712
+ // signing conversion may cap its authorization lifetime (#3117).
713
+ // Retain legacy fallback behavior for missing or unusable timeouts.
714
+ // Integer, because the authorize body types it `integer` and the child's
715
+ // timestamp caveat is built from it — the old clamp floored, so keeping a
716
+ // fractional value here would 400 a merchant that used to work (#3117).
717
+ maxTimeoutSeconds: typeof candidate.maxTimeoutSeconds === "number" && Number.isFinite(candidate.maxTimeoutSeconds) && candidate.maxTimeoutSeconds >= 1 ? Math.floor(candidate.maxTimeoutSeconds) : clampAuthorizationWindow(candidate.maxTimeoutSeconds),
725
718
  extra: candidate.extra
726
719
  };
727
720
  }
@@ -872,6 +865,13 @@ function selectPaymentOption(accepts) {
872
865
  return null;
873
866
  }
874
867
  var ERC7710_ASSET_TRANSFER_METHOD = "erc7710";
868
+ function isEip3009ConstructibleOption(option) {
869
+ const rawMethod = option.extra?.assetTransferMethod;
870
+ if (rawMethod !== void 0 && rawMethod !== "eip3009") return false;
871
+ const rawFlow = option.extra?.paymentFlow;
872
+ if (rawFlow !== void 0 && rawFlow !== "authorization") return false;
873
+ return true;
874
+ }
875
875
  function x402AssetTransferMethod(option) {
876
876
  const raw = option.extra?.assetTransferMethod;
877
877
  return typeof raw === "string" ? raw : null;
@@ -892,7 +892,8 @@ function selectStandardPaymentOption(accepts) {
892
892
  if (!accepts || accepts.length === 0) return null;
893
893
  for (const opt of accepts) {
894
894
  if (opt === null || typeof opt !== "object") continue;
895
- if (!isErc7710Option(opt) && isPayableStandardOption(opt)) return opt;
895
+ if (!isEip3009ConstructibleOption(opt)) continue;
896
+ if (isPayableStandardOption(opt)) return opt;
896
897
  }
897
898
  return null;
898
899
  }
@@ -901,6 +902,7 @@ function selectErc7710PaymentOption(accepts) {
901
902
  for (const opt of accepts) {
902
903
  if (opt === null || typeof opt !== "object") continue;
903
904
  if (isErc7710Option(opt) && isPayableStandardOption(opt)) return opt;
905
+ if (!isEip3009ConstructibleOption(opt)) continue;
904
906
  }
905
907
  return null;
906
908
  }
@@ -962,6 +964,11 @@ function toStandardPaymentRequirements(paymentRequired, option) {
962
964
  if (option.scheme !== "exact") {
963
965
  throw new Error(`Unsupported x402 scheme: ${option.scheme}`);
964
966
  }
967
+ if (!isEip3009ConstructibleOption(option)) {
968
+ throw new Error(
969
+ "Unsupported x402 payment requirements: this SDK constructs EIP-3009 authorization payments only. The merchant advertised a different extra.assetTransferMethod or an unrecognized extra.paymentFlow."
970
+ );
971
+ }
965
972
  return {
966
973
  scheme: "exact",
967
974
  network,
@@ -971,14 +978,11 @@ function toStandardPaymentRequirements(paymentRequired, option) {
971
978
  mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
972
979
  payTo: option.payTo,
973
980
  asset: option.asset,
974
- // Second enforcement point (#715): the parse path clamps too, but this is
975
- // the last stop before the x402 library turns the timeout into
976
- // `validBefore` — options constructed without parsing are bounded here.
977
- // The forward margin (#1256) is added ONLY here, at signing: the parse
978
- // path keeps recording the merchant's advertised timeout unchanged, and
979
- // the library's `validBefore = now + this value` then carries enough
980
- // slack to satisfy the facilitator's `validBefore ≥ now + maxTimeout`
981
- // verify rule after our funding leg confirms.
981
+ // Signing-only policy (#715/#3117): parsed and directly supplied options
982
+ // share this cap before the library computes validBefore. Keep the offer
983
+ // echoed in `accepted` unchanged. The forward margin (#1256) adds time
984
+ // for funding/retry, but does not guarantee a merchant timeout above the
985
+ // total bounded lifetime can pass facilitator verification.
982
986
  maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds) + X402_SETTLEMENT_FORWARD_MARGIN_SECONDS,
983
987
  extra: option.extra
984
988
  };
@@ -1226,10 +1230,7 @@ function mapPaymentStatusResult(raw) {
1226
1230
  rail: raw.rail,
1227
1231
  status: raw.status,
1228
1232
  phase: raw.phase,
1229
- // #2908: the account-vocabulary alias collapses onto the canonical value
1230
- // so every `=== AgentPaymentNextAction.X` downstream keeps working when
1231
- // the server flips its emit at #2914.
1232
- nextAction: canonicalAgentPaymentNextAction(raw.next_action),
1233
+ nextAction: raw.next_action,
1233
1234
  amount: raw.amount,
1234
1235
  token: raw.token,
1235
1236
  resourceUrl: raw.resource_url,
@@ -1365,7 +1366,7 @@ function messageForState(label, status, paymentId, nextAction) {
1365
1366
  function paymentStateFromRaw(label, raw) {
1366
1367
  if (!raw.payment_id || !raw.status) return null;
1367
1368
  const phase = raw.phase ?? phaseForStatus(raw.status);
1368
- const nextAction = canonicalAgentPaymentNextAction(raw.next_action) ?? nextActionForStatus(raw.status);
1369
+ const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
1369
1370
  if (!phase || !nextAction) return null;
1370
1371
  const amount = raw.amount ?? raw.requested ?? "";
1371
1372
  const token = raw.token ?? "";
@@ -1436,6 +1437,51 @@ function throwPaymentStateError(label, raw) {
1436
1437
  throw new HavenApiError(message, statusCode, raw);
1437
1438
  }
1438
1439
 
1440
+ // src/x402-retry-target.ts
1441
+ function resolveX402RetryTarget(input) {
1442
+ const requestUrl = input.requestUrl?.trim() || void 0;
1443
+ if (requestUrl) {
1444
+ return {
1445
+ url: requestUrl,
1446
+ source: "request",
1447
+ resourceUrlDiffersFromRequest: requestUrl !== input.resourceUrl
1448
+ };
1449
+ }
1450
+ return { url: input.resourceUrl, source: "resource" };
1451
+ }
1452
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "[::1]"]);
1453
+ var RESERVED_SUFFIXES = [".test", ".localhost", ".invalid", ".example"];
1454
+ var IPV4_LOOPBACK = /^127(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
1455
+ function isSecureX402RetryTarget(url) {
1456
+ let parsed;
1457
+ try {
1458
+ parsed = new URL(url);
1459
+ } catch {
1460
+ return false;
1461
+ }
1462
+ if (parsed.protocol === "https:") return true;
1463
+ if (parsed.protocol !== "http:") return false;
1464
+ const host = parsed.hostname.toLowerCase();
1465
+ if (LOOPBACK_HOSTS.has(host) || IPV4_LOOPBACK.test(host)) return true;
1466
+ return RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix));
1467
+ }
1468
+ var INSECURE_RETRY_TARGET_CODE = "INSECURE_RETRY_TARGET";
1469
+ var HavenInsecureRetryTargetError = class extends HavenError {
1470
+ constructor(url) {
1471
+ super(
1472
+ `Refusing to send a payment header to ${url}: the paid x402 retry must go to an https URL (or a loopback / reserved test host). The merchant's challenge declared this resource URL; retry with the https URL you quoted (pass it as \`url\`), and report a merchant whose challenge downgrades the scheme.`,
1473
+ INSECURE_RETRY_TARGET_CODE,
1474
+ 400
1475
+ );
1476
+ this.url = url;
1477
+ this.name = "HavenInsecureRetryTargetError";
1478
+ }
1479
+ url;
1480
+ };
1481
+ function assertSecureX402RetryTarget(url) {
1482
+ if (!isSecureX402RetryTarget(url)) throw new HavenInsecureRetryTargetError(url);
1483
+ }
1484
+
1439
1485
  // src/mcp-merchant-transport.ts
1440
1486
  var DEFAULT_MERCHANT_TIMEOUT = 3e5;
1441
1487
  var MCP_NOTIFICATION_TIMEOUT = 1e4;
@@ -1593,6 +1639,7 @@ var McpMerchantTransport = class {
1593
1639
  * overwrite, which is worse than the duplicate this change removes.
1594
1640
  */
1595
1641
  async deliverPayment(url, init, paymentHeader) {
1642
+ assertSecureX402RetryTarget(url);
1596
1643
  const headers = new Headers(init?.headers);
1597
1644
  const send = x402PaymentHeaderNamesFor(paymentHeader);
1598
1645
  for (const name of X402_PAYMENT_HEADER_NAMES) {
@@ -1688,20 +1735,6 @@ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
1688
1735
  return { verified: true, recoveredSigner: recovered };
1689
1736
  }
1690
1737
 
1691
- // src/account-naming.ts
1692
- function readAccountAddress(raw) {
1693
- return raw.account_address ?? raw.safe_address ?? void 0;
1694
- }
1695
- function readAccountId(raw) {
1696
- return raw.account_id ?? raw.safe_id ?? void 0;
1697
- }
1698
- function accountAddressTwins(address) {
1699
- return { accountAddress: address, safeAddress: address };
1700
- }
1701
- function readX402ReceiptPayer(raw) {
1702
- return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account ?? raw.safe_address ?? raw.sign_data?.components?.safe;
1703
- }
1704
-
1705
1738
  // src/account-reads.ts
1706
1739
  function safeBigInt(value) {
1707
1740
  try {
@@ -1721,6 +1754,10 @@ function deriveReadiness(status, allowances) {
1721
1754
  if (status !== "active") return "revoked";
1722
1755
  return allowances.some((allowance) => safeBigInt(allowance.remainingAtomic) > 0n) ? "ready" : "needs_approval";
1723
1756
  }
1757
+ function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
1758
+ const token = resolveTokenFromAddress(tokenAddress);
1759
+ return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
1760
+ }
1724
1761
  var AccountReads = class {
1725
1762
  transport;
1726
1763
  getPaymentStatus;
@@ -1742,12 +1779,12 @@ var AccountReads = class {
1742
1779
  async getAgentSummary() {
1743
1780
  const [agent, allowanceSummary] = await Promise.all([this.getAgent(), this.getAllowances()]);
1744
1781
  const allowances = allowanceSummary.allowances.map((allowance) => {
1745
- const token = resolveTokenFromAddress(allowance.tokenAddress);
1746
- const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(allowance.onchain.remaining), token.decimals)} ${allowance.tokenSymbol}` : `${allowance.onchain.remaining} ${allowance.tokenSymbol} (atomic; unknown decimals)`;
1747
1782
  return {
1783
+ id: allowance.id,
1748
1784
  tokenSymbol: allowance.tokenSymbol,
1785
+ tokenAddress: allowance.tokenAddress,
1749
1786
  remainingAtomic: allowance.onchain.remaining,
1750
- remainingDisplay,
1787
+ remainingDisplay: allowance.remainingDisplay,
1751
1788
  configuredAmount: allowance.configuredAmount,
1752
1789
  resetPeriodMin: allowance.resetPeriodMin,
1753
1790
  isResetPending: allowance.onchain.isResetPending
@@ -1760,10 +1797,13 @@ var AccountReads = class {
1760
1797
  const raw = await this.transport.get("/machine-payments/allowances");
1761
1798
  return {
1762
1799
  agentId: raw.agent_id,
1763
- // #2908: one mapper, both names, same value — `readAccountAddress`
1764
- // prefers the server's `account_address` twin and falls back to
1765
- // `safe_address` for a pre-#2907 server.
1766
- ...accountAddressTwins(readAccountAddress(raw)),
1800
+ // `account_address` is required on the wire contract, so the declared
1801
+ // type stays `string`; a server that omits it is off-contract and the
1802
+ // cast is the one place that case is allowed through as `undefined`
1803
+ // rather than a fabricated `''` (a present-but-blank address downstream
1804
+ // — the hosted MCP output spreads this object, and the sweep uses it as
1805
+ // a destination).
1806
+ accountAddress: raw.account_address,
1767
1807
  delegateAddress: raw.delegate_address,
1768
1808
  chainId: raw.chain_id,
1769
1809
  allowances: raw.allowances.map((allowance) => ({
@@ -1772,6 +1812,7 @@ var AccountReads = class {
1772
1812
  tokenSymbol: allowance.token_symbol,
1773
1813
  configuredAmount: allowance.configured_amount,
1774
1814
  resetPeriodMin: allowance.reset_period_min,
1815
+ remainingDisplay: formatRemainingDisplay(allowance.token_address, allowance.token_symbol, allowance.onchain.remaining),
1775
1816
  onchain: {
1776
1817
  amount: allowance.onchain.amount,
1777
1818
  spent: allowance.onchain.spent,
@@ -1786,6 +1827,33 @@ var AccountReads = class {
1786
1827
  }))
1787
1828
  };
1788
1829
  }
1830
+ /**
1831
+ * #3126 — the sufficiency signal behind {@link HavenBalanceCoverage}.
1832
+ *
1833
+ * Deliberately NOT a balance read: the endpoint answers whether the
1834
+ * account HOLDS at least the checked amount, as `covered
1835
+ * true/false/null`, and never returns the balance itself. The camelCase
1836
+ * mapping is permissive (raw fields flow through; the server owns the
1837
+ * wire shape, pinned by the backend's `expectMatchesSpec` assertion), so
1838
+ * an older server that has not deployed the endpoint surfaces its 404 as
1839
+ * a thrown error rather than a fabricated answer.
1840
+ */
1841
+ async checkFunds(input) {
1842
+ const query = `token=${encodeURIComponent(input.token)}&amount_atomic=${encodeURIComponent(input.amountAtomic)}`;
1843
+ const raw = await this.transport.get(
1844
+ `/machine-payments/balance-coverage?${query}`
1845
+ );
1846
+ return {
1847
+ covered: raw.covered,
1848
+ ...raw.coverage_error !== void 0 ? { coverageError: raw.coverage_error } : {},
1849
+ chainId: raw.chain_id,
1850
+ tokenAddress: raw.token_address,
1851
+ tokenSymbol: raw.token_symbol,
1852
+ checkedAmountAtomic: raw.checked_amount_atomic,
1853
+ budgetRemainingAtomic: raw.budget_remaining_atomic,
1854
+ ...raw.budget_remaining_is_from_chain !== void 0 ? { budgetRemainingIsFromChain: raw.budget_remaining_is_from_chain } : {}
1855
+ };
1856
+ }
1789
1857
  async getPostPurchaseAllowanceSummary(paymentId) {
1790
1858
  const unavailable = (detail, payment2 = null) => ({
1791
1859
  payment: payment2,
@@ -1837,10 +1905,23 @@ var AccountReads = class {
1837
1905
  return unavailable(error instanceof Error ? error.message : String(error));
1838
1906
  }
1839
1907
  }
1908
+ /** The first page's receipts as a bare array — the pre-#3128 shape, kept for callers that never page. */
1840
1909
  async listReceipts(options = {}) {
1841
- const query = options.limit ? `?limit=${encodeURIComponent(String(options.limit))}` : "";
1910
+ return (await this.listReceiptsPage(options)).receipts;
1911
+ }
1912
+ /** #3128: one page with `total`, `hasMore` and `nextCursor` — see {@link HavenPaymentReceiptsPage}. */
1913
+ async listReceiptsPage(options = {}) {
1914
+ const params = new URLSearchParams();
1915
+ if (options.limit) params.set("limit", String(options.limit));
1916
+ if (options.cursor) params.set("cursor", options.cursor);
1917
+ const query = params.size > 0 ? `?${params.toString()}` : "";
1842
1918
  const raw = await this.transport.get(`/machine-payments/receipts${query}`);
1843
- return raw.receipts.map(mapPaymentReceipt);
1919
+ return {
1920
+ receipts: raw.receipts.map(mapPaymentReceipt),
1921
+ total: typeof raw.total === "number" ? raw.total : null,
1922
+ hasMore: typeof raw.has_more === "boolean" ? raw.has_more : null,
1923
+ nextCursor: typeof raw.next_cursor === "string" ? raw.next_cursor : null
1924
+ };
1844
1925
  }
1845
1926
  async getReceipt(paymentId) {
1846
1927
  const { receipt } = await this.transport.get(`/payments/${paymentId}/receipt`);
@@ -1852,10 +1933,10 @@ var AccountReads = class {
1852
1933
  id: raw.id,
1853
1934
  name: raw.name,
1854
1935
  status: raw.status,
1855
- // #2908: both camelCase names off whichever snake_case name the server
1856
- // sent (new first). The hosted MCP's `haven_get_agent` spreads this
1857
- // object, so this is also the hosted output's dual-emit point.
1858
- ...accountAddressTwins(readAccountAddress(raw)),
1936
+ // See the comment on `getAllowances` above: `account_address` is
1937
+ // required on the wire contract, so an omission here is off-contract
1938
+ // and comes through as `undefined` rather than a fabricated `''`.
1939
+ accountAddress: raw.account_address,
1859
1940
  delegateAddress: raw.delegate_address,
1860
1941
  chainId: raw.chain_id,
1861
1942
  executionRail: raw.execution_rail === "delegation" ? "delegation" : "legacy"
@@ -2012,8 +2093,11 @@ function requestInitFromSnapshot(request) {
2012
2093
  }
2013
2094
  function noCompatiblePaymentOptionError(accepts) {
2014
2095
  const erc7710Only = selectErc7710PaymentOption(accepts) !== null;
2096
+ const unsupportedOnly = !erc7710Only && accepts.some(
2097
+ (opt) => opt !== null && typeof opt === "object" && !isErc7710Option(opt) && (x402AssetTransferMethod(opt) !== null || opt.extra?.paymentFlow !== void 0)
2098
+ );
2015
2099
  return new HavenApiError(
2016
- "No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC." + (erc7710Only ? " The only Haven-compatible option this merchant advertises is tagged extra.assetTransferMethod: 'erc7710' (direct settlement), which this EIP-3009 payment path cannot settle \u2014 the limitation is the settlement scheme, not the asset. Paying this merchant requires a delegation-rail erc7710 flow (settleX402Erc7710, or the hosted MCP purchase tools)." : ""),
2100
+ "No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC." + (erc7710Only ? " The only Haven-compatible option this merchant advertises is tagged extra.assetTransferMethod: 'erc7710' (direct settlement), which this EIP-3009 payment path cannot settle \u2014 the limitation is the settlement scheme, not the asset. Paying this merchant requires a delegation-rail erc7710 flow (settleX402Erc7710, or the hosted MCP purchase tools)." : unsupportedOnly ? " Every option this merchant advertises asks for a transfer method or payment flow Haven cannot construct (for example extra.assetTransferMethod: 'permit2', or an unrecognized extra.paymentFlow). No payment intent was created and no funds moved." : ""),
2017
2101
  400
2018
2102
  );
2019
2103
  }
@@ -2033,6 +2117,11 @@ function buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport)
2033
2117
  request,
2034
2118
  ...mcpTransport ? { mcpTransport } : {},
2035
2119
  resourceUrl: paymentRequired.resource.url,
2120
+ // #3097: the merchant's declaration vs the URL the caller quoted. The
2121
+ // paid retry goes to the caller's URL; a quote that says the two disagree
2122
+ // is how an agent sees a challenge that downgrades the scheme or moves
2123
+ // the host before it pays.
2124
+ resourceUrlDiffersFromRequest: paymentRequired.resource.url !== request.url,
2036
2125
  description: paymentRequired.resource.description ?? option.description ?? null,
2037
2126
  mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
2038
2127
  amountAtomic: x402AuthorizationAmount(option),
@@ -2187,6 +2276,13 @@ function assertCanResumeX402(status, paymentRequired, option) {
2187
2276
  );
2188
2277
  }
2189
2278
  }
2279
+
2280
+ // src/account-naming.ts
2281
+ function readX402ReceiptPayer(raw) {
2282
+ return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account;
2283
+ }
2284
+
2285
+ // src/x402-funding-leg.ts
2190
2286
  var X402FundingLeg = class {
2191
2287
  delegateKey;
2192
2288
  delegateAddress;
@@ -3099,7 +3195,18 @@ function mapCatalogEntry(entry) {
3099
3195
  verifiedAt: entry.verified_at,
3100
3196
  source: entry.source,
3101
3197
  domainVerified: entry.domain_verified,
3102
- verifiedPayable: entry.verified_payable
3198
+ verifiedPayable: entry.verified_payable,
3199
+ // #3078: absent (older backend) or null (unresolved join) both mean "no
3200
+ // merchant known" — the public field is then absent, never null.
3201
+ ...entry.merchant ? {
3202
+ merchant: {
3203
+ id: entry.merchant.id,
3204
+ slug: entry.merchant.slug,
3205
+ name: entry.merchant.name,
3206
+ listingStatus: entry.merchant.listing_status,
3207
+ isTestMerchant: entry.merchant.is_test_merchant
3208
+ }
3209
+ } : {}
3103
3210
  };
3104
3211
  }
3105
3212
  var HavenClient = class {
@@ -3481,6 +3588,49 @@ var HavenClient = class {
3481
3588
  async getAllowances() {
3482
3589
  return this.accountReads.getAllowances();
3483
3590
  }
3591
+ /**
3592
+ * #3126 — is the checked amount of the token actually HELD on the
3593
+ * agent's own account?
3594
+ *
3595
+ * This is the companion to {@link getAllowances}, not a variant of it:
3596
+ * allowances answer what the agent is PERMITTED to spend this period;
3597
+ * this answers whether the account HOLDS funds behind that permission,
3598
+ * as a sufficiency signal — `covered: true | false | null` — never as a
3599
+ * balance. `covered: null` means the chain read failed: treat it as
3600
+ * unverifiable, not as absence (`coverageError` says why). The account's
3601
+ * balance itself is deliberately not returned.
3602
+ */
3603
+ async checkFunds(input) {
3604
+ return this.accountReads.checkFunds(input);
3605
+ }
3606
+ /**
3607
+ * `POST /machine-payments/budget-precheck` (#3054): ask Haven to decide —
3608
+ * server-side — whether `amountAtomic` of `token` fits the agent's
3609
+ * remaining delegation budget, the same compare the guided prepare used to
3610
+ * run locally over its allowances read.
3611
+ *
3612
+ * On insufficiency Haven refuses (403, `delegation_budget_exceeded`) and
3613
+ * the refusal reaches the `payment_refusals` ledger with
3614
+ * `source: 'hosted_prepare'` — the point of the endpoint. This method
3615
+ * surfaces that decision as a thrown {@link HavenApiError}; it does NOT
3616
+ * swallow it, because swallowing would turn a decided refusal into the
3617
+ * degrade-to-warning path and the ledger row would still land while the
3618
+ * purchase proceeded.
3619
+ *
3620
+ * camelCase body like the route family; the response mirrors the wire
3621
+ * (`sufficient`, `remaining_atomic`). `resourceUrl` is the merchant
3622
+ * resource being bought — the ledger dedupe window's discriminating
3623
+ * column — never this request's own URL.
3624
+ */
3625
+ async precheckBudget(input) {
3626
+ return this.post("/machine-payments/budget-precheck", {
3627
+ chainId: input.chainId,
3628
+ token: input.token,
3629
+ amountAtomic: input.amountAtomic,
3630
+ ...input.merchantTo !== void 0 ? { merchantTo: input.merchantTo } : {},
3631
+ ...input.resourceUrl !== void 0 ? { resourceUrl: input.resourceUrl } : {}
3632
+ });
3633
+ }
3484
3634
  /**
3485
3635
  * Post-purchase allowance/budget summary for a settled payment (#1310).
3486
3636
  *
@@ -3603,6 +3753,10 @@ var HavenClient = class {
3603
3753
  async listReceipts(options = {}) {
3604
3754
  return this.accountReads.listReceipts(options);
3605
3755
  }
3756
+ /** #3128: one page of receipts with `total`, `hasMore` and `nextCursor`. */
3757
+ async listReceiptsPage(options = {}) {
3758
+ return this.accountReads.listReceiptsPage(options);
3759
+ }
3606
3760
  /**
3607
3761
  * Fetch the verifiable receipt bundle for a settled payment and verify it
3608
3762
  * locally. The server's own verification is ignored — the receipt is verified
@@ -4212,13 +4366,13 @@ var toolDescriptions = {
4212
4366
  summary: "Pay an inspected x402 quote. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",
4213
4367
  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.",
4214
4368
  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.",
4215
- 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."
4369
+ 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."
4216
4370
  },
4217
4371
  payX402OneShot: {
4218
4372
  summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
4219
4373
  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.",
4220
4374
  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.",
4221
- 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."
4375
+ 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."
4222
4376
  },
4223
4377
  resumeX402: {
4224
4378
  summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
@@ -4240,21 +4394,29 @@ var toolDescriptions = {
4240
4394
  nextActionGuidance: ""
4241
4395
  },
4242
4396
  getAgent: {
4243
- 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.",
4244
- 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.",
4245
- 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.`,
4397
+ summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness, per-token remaining allowance (atomic + human-readable). The recommended first call in a new session.",
4398
+ selectionGuidance: "Use this as the session bootstrap, or to confirm identity together with whether the agent can spend right now. For per-token detail (configured vs spent vs reset window) use haven_get_allowances.",
4399
+ behavior: 'Reads identity plus the live spend-authority snapshot \u2014 the 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 \u2014 ask the owner to grant or raise the budget in Haven. allowances[] carries id, tokenAddress, remainingAtomic, remainingDisplay per token. Identity fields: id, name, status, accountAddress, delegateAddress, chainId.',
4246
4400
  nextActionGuidance: ""
4247
4401
  },
4248
4402
  getAllowances: {
4249
4403
  summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
4250
- selectionGuidance: "Use this when the user asks about allowance, budget, spend limit, remaining amount, remaining allowance, remaining budget, daily limit, reset period, what can I spend, or what the agent can still spend.",
4251
- behavior: "Returns the per-token spend authority for the account: the active budget delegation (remaining = the period budget, which re-arms natively at the period boundary). An over-budget payment is declined before any money moves; nothing queues. Configured amounts from Haven are returned alongside.",
4404
+ selectionGuidance: "Use this when the user asks about allowance, budget, spend limit, remaining amount, remaining allowance, remaining budget, daily limit, reset period, what can I spend, or what the agent can still spend. For whether the account actually HOLDS funds behind the budget use haven_check_funds.",
4405
+ behavior: "Returns the per-token spend authority for the account: the active budget delegation (remaining = the period budget, which re-arms natively at the period boundary), each with id, onchain.remaining, remainingDisplay. An over-budget payment is declined before any money moves; nothing queues. Configured amounts from Haven are returned alongside.",
4252
4406
  nextActionGuidance: ""
4253
4407
  },
4408
+ // #3126 — the sufficiency signal, deliberately NOT a balance tool. The
4409
+ // constrained actor reads a boolean, never the treasury total.
4410
+ checkFunds: {
4411
+ summary: "Check whether the agent's account actually holds at least the given amount of a token \u2014 funds held, not spend permitted.",
4412
+ selectionGuidance: "Use this before attempting a payment when it matters whether the money is really there: allowance answers say what you are PERMITTED to spend, never whether the account HOLDS it. For allowance, budget, spend-limit, remaining-budget, reset-period, or what-can-I-spend questions use the allowance lookup tool instead.",
4413
+ behavior: "Returns covered: true (the account holds at least the checked amount), false (a live chain read reports less \u2014 the budget is backed by an empty account; stop and tell the user funds are missing), or null (the chain read failed \u2014 unverifiable, never treat it as absence; coverageError says why). The account balance itself is deliberately not returned: this is a sufficiency signal, not a balance read. budget_remaining_atomic is the permitted figure from the allowance lookup (the SDK spells it budgetRemainingAtomic), named so it can never be confused with holdings.",
4414
+ nextActionGuidance: "On covered=false, do not attempt the payment \u2014 tell the user the account is short and let them fund it; on covered=null, retry the check shortly or proceed knowing the payment may fail on-chain."
4415
+ },
4254
4416
  listReceipts: {
4255
- summary: "List recent machine-payment receipts and evidence for bookkeeping.",
4256
- selectionGuidance: "Use this for transaction history, receipts, payment evidence, or bookkeeping; use the allowance tool instead for remaining allowance, budget, spend-limit, or what-can-I-spend questions.",
4257
- behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
4417
+ summary: "List machine-payment receipts, newest first, by page.",
4418
+ selectionGuidance: "For transaction history or payment evidence; use the allowance tool instead for remaining allowance or what-can-I-spend questions.",
4419
+ behavior: "Page: { receipts, total, hasMore, nextCursor }; total 0 = none exist (no indexing delay); hasMore = cut at limit, send nextCursor as cursor. parties.treasuryAccount is Haven's authoritative payer. protocolReceiptPayload is the merchant's PAYMENT-RESPONSE, relayed verbatim: merchant-controlled, unverified, not Haven's record; payer may differ from payerAddress. Proof header values are omitted.",
4258
4420
  nextActionGuidance: ""
4259
4421
  },
4260
4422
  verifyReceipt: {
@@ -4270,10 +4432,10 @@ var toolDescriptions = {
4270
4432
  nextActionGuidance: "On a decline, report the reason to the user and ask them to raise the budget in Haven \u2014 there is no approval queue to wait on. This tool retries the merchant itself while it runs, so do not wait on a signal mid-call. If the process crashes after payment, a later haven_get_payment_status call may report nextAction=retry_original_x402_request \u2014 resume via haven_resume_x402_payment instead of paying again."
4271
4433
  },
4272
4434
  discoverTools: {
4273
- 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.",
4274
- 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.",
4275
- 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.",
4276
- 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.`
4435
+ summary: "Step 1 of a purchase: discover payable services from Haven's curated merchant catalog \u2014 names, prices, the next call.",
4436
+ 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 next tool to call.",
4437
+ behavior: "Use each entry's suggested_tool + suggested_arguments first; an entry without them says why. 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, the hint, 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.",
4438
+ nextActionGuidance: `Call suggested_tool with suggested_arguments VERBATIM (no hint: read suggested_tool_omitted_reason). Confirm the price from the live quote or pay result, not the catalog; if the next tool takes a cap, pass the user's cap as max_amount_human in whole tokens ("no more than 1 USDC" \u2192 max_amount_human: "1"), never atomic units by hand.`
4277
4439
  },
4278
4440
  submitCatalogEntry: {
4279
4441
  summary: "Submit a merchant's payable (x402/MCP) endpoint to Haven's Verified Payable Directory for verification and listing.",
@@ -4283,13 +4445,13 @@ var toolDescriptions = {
4283
4445
  },
4284
4446
  sweep_delegate: {
4285
4447
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating account.",
4286
- 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.",
4448
+ 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, or haven_check_funds for whether the account holds funds.",
4287
4449
  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 account (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. Each transfer carries confirmation: "confirmed" (a receipt was seen \u2014 the funds are in the account) or "unconfirmed" (broadcast but not confirmed within 90 seconds \u2014 still in the mempool, may still land). The top-level unconfirmed flag is true when any transfer is unconfirmed.`,
4288
4450
  nextActionGuidance: 'If transfers is non-empty, confirm the amounts with the user. Report a transfer as recovered ONLY when its confirmation is "confirmed". For an "unconfirmed" transfer, tell the user it was submitted but not yet confirmed, give them its txHash and explorerUrl to check, and do not re-run the sweep immediately \u2014 a re-run after it lands will simply find nothing stranded.'
4289
4451
  },
4290
4452
  send: {
4291
4453
  summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
4292
- 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.",
4454
+ 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. Do NOT use to check whether funds are held before sending \u2014 use haven_check_funds.",
4293
4455
  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.",
4294
4456
  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."
4295
4457
  },
@@ -4668,8 +4830,10 @@ the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
4668
4830
  That namespacing is Claude-family; other runtimes name the servers by their
4669
4831
  own config keys (Codex: \`haven\`, \`haven_signer\`). Tool results carry the
4670
4832
  exact next step (\`next_action\`, \`next_tool\`, \`next_arguments\`, plus the
4671
- runtime-neutral \`next_tool_server\` + \`next_tool_name\` \u2014 the bare tool name
4672
- on that logical server, whatever your runtime calls it).
4833
+ runtime-neutral \`next_tool_server\` + \`next_tool_name\` + \`next_tool_server_role\`
4834
+ \u2014 the bare tool name on that logical server, whatever your runtime calls it).
4835
+ When no tool follows, \`next_tool\` is absent and \`next_tool_omitted_reason\`
4836
+ says why; that is a complete answer.
4673
4837
  Follow those fields first; the prose below is fallback and orientation, not
4674
4838
  the source of truth.
4675
4839
 
@@ -4756,6 +4920,13 @@ spending:
4756
4920
  local signer; the signer is verified by calling any signer tool.
4757
4921
  - \`mcp__haven__haven_get_allowances\` \u2014 detailed per-token breakdown
4758
4922
  (configured, spent, reset window) when you need more than the summary.
4923
+ - \`mcp__haven__haven_check_funds\` \u2014 whether the account actually HOLDS at
4924
+ least a given amount of a token. Allowance answers above say what you are
4925
+ permitted to spend; this one says whether the money is really there,
4926
+ answered as \`covered\` true/false/null \u2014 never as a balance. On
4927
+ \`covered: false\`, stop and tell the user the account is short; on
4928
+ \`covered: null\` (the chain read failed), treat it as unverifiable rather
4929
+ than as absence.
4759
4930
 
4760
4931
  Budgets reset on a period the user chose. If a payment exceeds the remaining
4761
4932
  budget it is declined before any money moves \u2014 tell the user; they can raise
@@ -4884,8 +5055,10 @@ check on in-flight payments. Do not poll in a tight loop.
4884
5055
  ## Failure handling
4885
5056
 
4886
5057
  Haven tool failures are shaped like \`{ success: false, code, message, ... }\`
4887
- or older \`{ error, status, details? }\` responses. Branch on \`code\` when
4888
- present and surface \`message\` or \`error\` verbatim. Common cases:
5058
+ or older \`{ error, status, details? }\` responses. A failure carries the same
5059
+ \`next_action\` / \`next_tool\` / \`next_arguments\` / \`next_tool_omitted_reason\`
5060
+ fields a success does; follow them first, then branch on \`code\` and surface
5061
+ \`message\` or \`error\` verbatim. Common cases:
4889
5062
 
4890
5063
  - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
4891
5064
  Suggest the user add funds in the Haven dashboard.
@@ -4946,6 +5119,64 @@ for that credential.
4946
5119
  var SKILL_FOLDER_NAME = "haven-pay";
4947
5120
  var HAVEN_SKILL_BODY_MD = HAVEN_SKILL_MD.replace(/^---\n[\s\S]*?\n---\n+/, "");
4948
5121
 
5122
+ // src/next-step.ts
5123
+ var NEXT_TOOL_SERVER_NAMES = {
5124
+ hosted: "haven",
5125
+ signer: "haven-signer"
5126
+ };
5127
+ var NEXT_TOOL_SERVER_ROLES = {
5128
+ haven: "hosted",
5129
+ "haven-signer": "signer"
5130
+ };
5131
+ function renderNextTool(role, name) {
5132
+ return `mcp__${NEXT_TOOL_SERVER_NAMES[role]}__${name}`;
5133
+ }
5134
+ function parseNextTool(literal) {
5135
+ const m = /^mcp__([a-z0-9-]+)__([a-z0-9_]+)$/.exec(literal);
5136
+ if (!m) return null;
5137
+ const role = NEXT_TOOL_SERVER_ROLES[m[1]];
5138
+ return { server: m[1], name: m[2], ...role ? { role } : {} };
5139
+ }
5140
+ var DEFAULT_NEXT_TOOL_BY_ACTION = {
5141
+ check_status_later: "haven_get_payment_status",
5142
+ sweep_stranded_funds: "haven_sweep_delegate"
5143
+ // `retry_original_x402_request` is NOT here: its only live emitter
5144
+ // (state-direct-recovery.ts, erc7710) names no tool on purpose — the retry
5145
+ // is the agent's own HTTP call — so a default would contradict the site.
5146
+ };
5147
+ function defaultNextToolFor(action) {
5148
+ return DEFAULT_NEXT_TOOL_BY_ACTION[action];
5149
+ }
5150
+ function createNextStepBuilder(targets) {
5151
+ return function nextStep(input) {
5152
+ const i = input;
5153
+ const base = {
5154
+ next_action: input.nextAction,
5155
+ safe_to_continue: input.safeToContinue,
5156
+ reason: input.reason
5157
+ };
5158
+ if (i.nextTool === null) {
5159
+ return { ...base, next_tool_omitted_reason: i.nextToolOmittedReason ?? "no next tool" };
5160
+ }
5161
+ const target = targets[i.nextTool];
5162
+ if (!target) {
5163
+ return { ...base, next_tool_omitted_reason: `${i.nextTool} is not a registered next-step target` };
5164
+ }
5165
+ const problem = target.validate(i.nextArguments);
5166
+ if (problem) {
5167
+ return { ...base, next_tool_omitted_reason: `next_arguments do not parse under ${i.nextTool}: ${problem}` };
5168
+ }
5169
+ return {
5170
+ ...base,
5171
+ next_tool: renderNextTool(target.role, i.nextTool),
5172
+ next_tool_server: NEXT_TOOL_SERVER_NAMES[target.role],
5173
+ next_tool_name: i.nextTool,
5174
+ next_tool_server_role: target.role,
5175
+ next_arguments: i.nextArguments
5176
+ };
5177
+ };
5178
+ }
5179
+
4949
5180
  // src/node-version.ts
4950
5181
  var HAVEN_MINIMUM_NODE_VERSION = "22.0.0";
4951
5182
  function parseNodeVersion(value) {
@@ -5047,7 +5278,6 @@ exports.AgentPaymentFailureCode = AgentPaymentFailureCode;
5047
5278
  exports.AgentPaymentFailureCodeDescriptions = AgentPaymentFailureCodeDescriptions;
5048
5279
  exports.AgentPaymentFailureCodeSchema = AgentPaymentFailureCodeSchema;
5049
5280
  exports.AgentPaymentNextAction = AgentPaymentNextAction;
5050
- exports.AgentPaymentNextActionAccountAlias = AgentPaymentNextActionAccountAlias;
5051
5281
  exports.AgentPaymentNextActionDescriptions = AgentPaymentNextActionDescriptions;
5052
5282
  exports.AgentPaymentNextActionSchema = AgentPaymentNextActionSchema;
5053
5283
  exports.AgentPaymentPhase = AgentPaymentPhase;
@@ -5059,6 +5289,7 @@ exports.AgentPaymentRailSchema = AgentPaymentRailSchema;
5059
5289
  exports.AgentPaymentWarningCode = AgentPaymentWarningCode;
5060
5290
  exports.CONNECTOR_PACKAGE_NAME = CONNECTOR_PACKAGE_NAME;
5061
5291
  exports.DEFAULT_CONFIRMATION_TIMEOUT_MS = DEFAULT_CONFIRMATION_TIMEOUT_MS;
5292
+ exports.DEFAULT_NEXT_TOOL_BY_ACTION = DEFAULT_NEXT_TOOL_BY_ACTION;
5062
5293
  exports.DISCOVERY_MAX_BYTES = DISCOVERY_MAX_BYTES;
5063
5294
  exports.ERC7710_ASSET_TRANSFER_METHOD = ERC7710_ASSET_TRANSFER_METHOD;
5064
5295
  exports.HAVEN_AGENT_RUNBOOK_MD = HAVEN_AGENT_RUNBOOK_MD;
@@ -5069,13 +5300,17 @@ exports.HAVEN_SKILL_MD = HAVEN_SKILL_MD;
5069
5300
  exports.HavenApiError = HavenApiError;
5070
5301
  exports.HavenClient = HavenClient;
5071
5302
  exports.HavenError = HavenError;
5303
+ exports.HavenInsecureRetryTargetError = HavenInsecureRetryTargetError;
5072
5304
  exports.HavenPaymentStateError = HavenPaymentStateError;
5073
5305
  exports.HavenSigningError = HavenSigningError;
5074
5306
  exports.HavenTimeoutError = HavenTimeoutError;
5075
5307
  exports.HavenUnsupportedSignerVersionError = HavenUnsupportedSignerVersionError;
5076
5308
  exports.HavenZeroSettlementHashError = HavenZeroSettlementHashError;
5309
+ exports.INSECURE_RETRY_TARGET_CODE = INSECURE_RETRY_TARGET_CODE;
5077
5310
  exports.MERCHANT_DISCOVERY_PATHS = MERCHANT_DISCOVERY_PATHS;
5078
5311
  exports.MerchantTimeoutError = MerchantTimeoutError;
5312
+ exports.NEXT_TOOL_SERVER_NAMES = NEXT_TOOL_SERVER_NAMES;
5313
+ exports.NEXT_TOOL_SERVER_ROLES = NEXT_TOOL_SERVER_ROLES;
5079
5314
  exports.RECEIPT_VERSION = RECEIPT_VERSION;
5080
5315
  exports.SIGNER_UPDATE_FALLBACK = SIGNER_UPDATE_FALLBACK;
5081
5316
  exports.SKILL_FOLDER_NAME = SKILL_FOLDER_NAME;
@@ -5095,18 +5330,19 @@ exports.X402_PAYMENT_HEADER_NAMES_SENT = X402_PAYMENT_HEADER_NAMES_SENT;
5095
5330
  exports.X402_PAYMENT_REQUIRED_HEADER_NAME = X402_PAYMENT_REQUIRED_HEADER_NAME;
5096
5331
  exports.X402_PAYMENT_RESPONSE_HEADER_NAME = X402_PAYMENT_RESPONSE_HEADER_NAME;
5097
5332
  exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
5098
- exports.accountAddressTwins = accountAddressTwins;
5099
5333
  exports.addressFromKey = addressFromKey;
5334
+ exports.assertSecureX402RetryTarget = assertSecureX402RetryTarget;
5100
5335
  exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
5101
5336
  exports.buildSweepTypedData = buildSweepTypedData;
5102
5337
  exports.buildX402ExpectedMessage = buildX402ExpectedMessage;
5103
- exports.canonicalAgentPaymentNextAction = canonicalAgentPaymentNextAction;
5104
5338
  exports.compareNodeVersions = compareNodeVersions;
5105
5339
  exports.composeDescription = composeDescription;
5106
5340
  exports.connectorRerunCommand = connectorRerunCommand;
5107
5341
  exports.connectorSpec = connectorSpec;
5342
+ exports.createNextStepBuilder = createNextStepBuilder;
5108
5343
  exports.decodeBase64Json = decodeBase64Json;
5109
5344
  exports.decodeBase64Utf8 = decodeBase64Utf8;
5345
+ exports.defaultNextToolFor = defaultNextToolFor;
5110
5346
  exports.discoverMerchantMcpUrl = discoverMerchantMcpUrl;
5111
5347
  exports.encodeBase64Json = encodeBase64Json;
5112
5348
  exports.encodeBase64Utf8 = encodeBase64Utf8;
@@ -5114,18 +5350,19 @@ exports.encodePaymentProof = encodePaymentProof;
5114
5350
  exports.havenTools = havenTools;
5115
5351
  exports.isConnectorChannel = isConnectorChannel;
5116
5352
  exports.isErc7710Option = isErc7710Option;
5117
- exports.isFundAccountOrRaiseAllowance = isFundAccountOrRaiseAllowance;
5353
+ exports.isSecureX402RetryTarget = isSecureX402RetryTarget;
5118
5354
  exports.isSupportedNodeVersion = isSupportedNodeVersion;
5119
5355
  exports.isSweepableChain = isSweepableChain;
5120
5356
  exports.isZeroSettlementTxHash = isZeroSettlementTxHash;
5121
5357
  exports.normalizePaymentRequired = normalizePaymentRequired;
5358
+ exports.parseNextTool = parseNextTool;
5122
5359
  exports.parsePaymentRequired = parsePaymentRequired;
5123
5360
  exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
5124
- exports.readAccountAddress = readAccountAddress;
5125
- exports.readAccountId = readAccountId;
5126
5361
  exports.readX402ReceiptPayer = readX402ReceiptPayer;
5362
+ exports.renderNextTool = renderNextTool;
5127
5363
  exports.resolveConnectorChannel = resolveConnectorChannel;
5128
5364
  exports.resolveTokenFromAddress = resolveTokenFromAddress;
5365
+ exports.resolveX402RetryTarget = resolveX402RetryTarget;
5129
5366
  exports.sameUrl = sameUrl;
5130
5367
  exports.selectErc7710PaymentOption = selectErc7710PaymentOption;
5131
5368
  exports.selectPaymentOption = selectPaymentOption;