@haven_ai/sdk 0.1.14-alpha.0 → 0.1.16-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.js CHANGED
@@ -61,6 +61,11 @@ var AgentPaymentNextAction = {
61
61
  StopAndTellUser: "stop_and_tell_user",
62
62
  /** Ask again only if the user still wants the payment after expiry. */
63
63
  RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it",
64
+ /**
65
+ * The x402 funding/quote window expired. Re-quote the same logical merchant
66
+ * operation with the same idempotency key to stay double-charge-safe.
67
+ */
68
+ PaymentWindowExpired: "payment_window_expired",
64
69
  /**
65
70
  * Stop and tell the user that the originating Safe needs to be funded or
66
71
  * the agent's per-token allowance needs to be raised before the payment
@@ -74,6 +79,14 @@ var AgentPaymentNextAction = {
74
79
  */
75
80
  SweepStrandedFunds: "sweep_stranded_funds"
76
81
  };
82
+ var AgentPaymentFailureCode = {
83
+ /** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
84
+ PriceExceedsMax: "PRICE_EXCEEDS_MAX",
85
+ /** The x402 funding/quote window expired before the signer or hosted settle step could finish. */
86
+ PaymentWindowExpired: "PAYMENT_WINDOW_EXPIRED",
87
+ /** The Haven funding leg succeeded, but the merchant rejected the paid retry. */
88
+ MerchantRejectedAfterFunding: "MERCHANT_REJECTED_AFTER_FUNDING"
89
+ };
77
90
  var AgentPaymentRail = {
78
91
  /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
79
92
  Direct: "direct",
@@ -92,6 +105,7 @@ var AgentPaymentRail = {
92
105
  };
93
106
  var AGENT_PAYMENT_PHASE_VALUES = Object.values(AgentPaymentPhase);
94
107
  var AGENT_PAYMENT_NEXT_ACTION_VALUES = Object.values(AgentPaymentNextAction);
108
+ var AGENT_PAYMENT_FAILURE_CODE_VALUES = Object.values(AgentPaymentFailureCode);
95
109
  var AGENT_PAYMENT_RAIL_VALUES = Object.values(AgentPaymentRail);
96
110
  var AgentPaymentPhaseDescriptions = {
97
111
  [AgentPaymentPhase.AgentSignatureRequired]: "The agent must sign and submit the prepared payment before Haven can relay it.",
@@ -116,9 +130,15 @@ var AgentPaymentNextActionDescriptions = {
116
130
  [AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
117
131
  [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
118
132
  [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
133
+ [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.",
119
134
  [AgentPaymentNextAction.FundSafeOrRaiseAllowance]: "Stop and tell the user that the originating Safe needs to be funded or the agent allowance raised before the payment can succeed.",
120
135
  [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 Safe."
121
136
  };
137
+ var AgentPaymentFailureCodeDescriptions = {
138
+ [AgentPaymentFailureCode.PriceExceedsMax]: "The merchant-authoritative x402 amount exceeds the caller's max_amount cap. No funding transfer was created; ask the user before retrying with a larger cap.",
139
+ [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.",
140
+ [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."
141
+ };
122
142
  var AgentPaymentRailDescriptions = {
123
143
  [AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled Safe through an approved delegate allowance.",
124
144
  [AgentPaymentRail.X402]: "x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg.",
@@ -140,6 +160,12 @@ var AgentPaymentNextActionSchema = {
140
160
  description: "Stable next action an agent should take for a Haven payment state.",
141
161
  "x-enumDescriptions": AgentPaymentNextActionDescriptions
142
162
  };
163
+ var AgentPaymentFailureCodeSchema = {
164
+ type: "string",
165
+ enum: AGENT_PAYMENT_FAILURE_CODE_VALUES,
166
+ description: "Stable machine-readable failure codes for Haven agent payment recovery paths.",
167
+ "x-enumDescriptions": AgentPaymentFailureCodeDescriptions
168
+ };
143
169
  var AgentPaymentRailSchema = {
144
170
  type: "string",
145
171
  enum: AGENT_PAYMENT_RAIL_VALUES,
@@ -327,7 +353,8 @@ function normalizePaymentRequired(value) {
327
353
  x402Version: candidate.x402Version,
328
354
  resource,
329
355
  accepts,
330
- error: candidate.error
356
+ error: candidate.error,
357
+ ...candidate.extensions && typeof candidate.extensions === "object" ? { extensions: candidate.extensions } : {}
331
358
  };
332
359
  }
333
360
  var SUPPORTED_X402_NETWORKS = {
@@ -423,8 +450,7 @@ function x402AuthorizationAmount(option) {
423
450
  return amount;
424
451
  }
425
452
  function buildX402ExpectedMessage(context) {
426
- return `Haven x402 expected context v1
427
- ${stableStringify({
453
+ const payload = {
428
454
  version: 1,
429
455
  kind: "haven.x402.expected",
430
456
  paymentId: context.paymentId,
@@ -434,7 +460,12 @@ ${stableStringify({
434
460
  amount: context.amount,
435
461
  asset: context.asset.toLowerCase(),
436
462
  network: context.network
437
- })}`;
463
+ };
464
+ if (context.expiresAt) {
465
+ payload.expiresAt = context.expiresAt;
466
+ }
467
+ return `Haven x402 expected context v1
468
+ ${stableStringify(payload)}`;
438
469
  }
439
470
  function toStandardPaymentRequirements(paymentRequired, option) {
440
471
  const network = STANDARD_X402_NETWORKS[option.network];
@@ -498,6 +529,7 @@ function stableStringify(value) {
498
529
  const primitive = JSON.stringify(value);
499
530
  return primitive === void 0 ? "undefined" : primitive;
500
531
  }
532
+ if (value instanceof Date) return JSON.stringify(value.toISOString());
501
533
  if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
502
534
  const object = value;
503
535
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
@@ -599,11 +631,24 @@ var DEFAULT_REQUEST_TIMEOUT = 3e4;
599
631
  var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
600
632
  var DEFAULT_POLLING_INTERVAL = 3e3;
601
633
  function formatAtomicAmount(atomic, decimals) {
634
+ if (atomic < 0n) return "0.0";
602
635
  const s = atomic.toString().padStart(decimals + 1, "0");
603
636
  const intPart = s.slice(0, s.length - decimals) || "0";
604
637
  const fracPart = s.slice(s.length - decimals).replace(/0+$/, "") || "0";
605
638
  return `${intPart}.${fracPart}`;
606
639
  }
640
+ function safeBigInt(value) {
641
+ try {
642
+ return BigInt(value);
643
+ } catch {
644
+ return 0n;
645
+ }
646
+ }
647
+ function deriveReadiness(status, allowances) {
648
+ if (status !== "active") return "revoked";
649
+ const hasSpendable = allowances.some((a) => safeBigInt(a.remainingAtomic) > 0n);
650
+ return hasSpendable ? "ready" : "needs_approval";
651
+ }
607
652
  var MCP_PROTOCOL_VERSION = "2025-06-18";
608
653
  var MCP_ACCEPT = "application/json, text/event-stream";
609
654
  var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
@@ -890,6 +935,7 @@ var HavenClient = class {
890
935
  }
891
936
  return {
892
937
  paymentId: raw.payment_id,
938
+ idempotencyKey,
893
939
  status: "pending_signature",
894
940
  expiresAt: raw.expires_at,
895
941
  signData: raw.sign_data,
@@ -969,6 +1015,32 @@ var HavenClient = class {
969
1015
  chainId: raw.chain_id
970
1016
  };
971
1017
  }
1018
+ /**
1019
+ * One-shot "am I ready?" bootstrap: identity + live spend authority + a
1020
+ * readiness signal, in a single call. Folds {@link getAgent} and
1021
+ * {@link getAllowances} together and derives a {@link HavenAgentReadiness}
1022
+ * so an agent can answer "who am I and can I pay right now" at session start
1023
+ * without two round trips and manual assembly.
1024
+ */
1025
+ async getAgentSummary() {
1026
+ const [agent, allowanceSummary] = await Promise.all([
1027
+ this.getAgent(),
1028
+ this.getAllowances()
1029
+ ]);
1030
+ const allowances = allowanceSummary.allowances.map((a) => {
1031
+ const token = resolveTokenFromAddress(a.tokenAddress);
1032
+ const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(a.onchain.remaining), token.decimals)} ${a.tokenSymbol}` : `${a.onchain.remaining} ${a.tokenSymbol} (atomic; unknown decimals)`;
1033
+ return {
1034
+ tokenSymbol: a.tokenSymbol,
1035
+ remainingAtomic: a.onchain.remaining,
1036
+ remainingDisplay,
1037
+ configuredAmount: a.configuredAmount,
1038
+ resetPeriodMin: a.resetPeriodMin,
1039
+ isResetPending: a.onchain.isResetPending
1040
+ };
1041
+ });
1042
+ return { ...agent, readiness: deriveReadiness(agent.status, allowances), allowances };
1043
+ }
972
1044
  /**
973
1045
  * Sweep stranded USDC and ETH from the delegate EOA back to the originating Safe.
974
1046
  *
@@ -1041,6 +1113,30 @@ var HavenClient = class {
1041
1113
  transfers
1042
1114
  };
1043
1115
  }
1116
+ /**
1117
+ * Hosted (keyless) split-signer sweep — step 1 of 2.
1118
+ *
1119
+ * Asks the backend to build a gasless EIP-3009 sweep authorization for the
1120
+ * delegate's stranded USDC. Returns `nothing_stranded` when the delegate is
1121
+ * empty, otherwise an `authorization` + Haven `expected_auth` to hand to the
1122
+ * edge signer's `haven_sign_sweep_delegate`. No key is required on this client.
1123
+ */
1124
+ async prepareSweep() {
1125
+ return this.post("/machine-payments/sweep/prepare", {});
1126
+ }
1127
+ /**
1128
+ * Hosted (keyless) split-signer sweep — step 2 of 2.
1129
+ *
1130
+ * Relays the delegate-signed authorization. The Haven relayer submits the
1131
+ * on-chain `transferWithAuthorization` and pays gas; this client never holds
1132
+ * the key.
1133
+ */
1134
+ async submitSweep(authorization, signature) {
1135
+ return this.post("/machine-payments/sweep/submit", {
1136
+ authorization,
1137
+ signature
1138
+ });
1139
+ }
1044
1140
  /**
1045
1141
  * Get configured and on-chain allowances for the authenticated agent.
1046
1142
  */
@@ -1196,7 +1292,8 @@ var HavenClient = class {
1196
1292
  throw new HavenApiError("quoteX402 only supports standard x402 Payment Required responses.", 400);
1197
1293
  }
1198
1294
  const paymentRequired = await parsePaymentRequiredResponse(response);
1199
- return this.buildX402Quote(paymentRequired, request, options.idempotencyKey);
1295
+ const mcpTransport = await this.detectX402McpTransport(url, paymentRequired, response);
1296
+ return this.buildX402Quote(paymentRequired, request, options.idempotencyKey, mcpTransport);
1200
1297
  }
1201
1298
  /**
1202
1299
  * Pay a previously inspected x402 quote and retry the exact captured request.
@@ -1401,12 +1498,11 @@ var HavenClient = class {
1401
1498
  * a transport/HTTP error, a missing session id, or a JSON-RPC error in the
1402
1499
  * handshake response — so the caller can fall back to plain x402.
1403
1500
  */
1404
- async mcpInitialize(url, init) {
1501
+ async mcpInitialize(url, init, wallet = this.x402PayerAddress()) {
1405
1502
  try {
1406
1503
  const headers = new Headers(init?.headers);
1407
1504
  headers.set("Content-Type", "application/json");
1408
1505
  headers.set("Accept", MCP_ACCEPT);
1409
- const wallet = this.x402PayerAddress();
1410
1506
  if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1411
1507
  const response = await globalThis.fetch(url, {
1412
1508
  method: "POST",
@@ -1427,7 +1523,7 @@ var HavenClient = class {
1427
1523
  if (!sessionId) return void 0;
1428
1524
  const message = await this.readMcpMessage(response);
1429
1525
  if (message && "error" in message) return void 0;
1430
- await this.mcpNotifyInitialized(url, init, sessionId);
1526
+ await this.mcpNotifyInitialized(url, init, sessionId, wallet);
1431
1527
  return sessionId;
1432
1528
  } catch {
1433
1529
  return void 0;
@@ -1438,13 +1534,12 @@ var HavenClient = class {
1438
1534
  * lifecycle handshake. Best-effort: the session is already established, so a
1439
1535
  * failed notification must not abort the payment.
1440
1536
  */
1441
- async mcpNotifyInitialized(url, init, sessionId) {
1537
+ async mcpNotifyInitialized(url, init, sessionId, wallet = this.x402PayerAddress()) {
1442
1538
  try {
1443
1539
  const headers = new Headers(init?.headers);
1444
1540
  headers.set("Content-Type", "application/json");
1445
1541
  headers.set("Accept", MCP_ACCEPT);
1446
1542
  headers.set("mcp-session-id", sessionId);
1447
- const wallet = this.x402PayerAddress();
1448
1543
  if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1449
1544
  await globalThis.fetch(url, {
1450
1545
  method: "POST",
@@ -1622,30 +1717,48 @@ var HavenClient = class {
1622
1717
  * amount/merchant/nonce-bound EIP-3009 authorization the edge signer already
1623
1718
  * produced — the hosted server cannot mint or reuse signing authority.
1624
1719
  *
1625
- * When the URL is MCP-shaped (`/mcp` path), runs a fresh `initialize`
1720
+ * When the URL is MCP-shaped (`/mcp` path) or the quote-time transport context
1721
+ * says the merchant was Bazaar-discoverable, runs a fresh `initialize`
1626
1722
  * handshake (the quote-time session is gone once funding confirms; the x402
1627
1723
  * challenge is stateless w.r.t. the MCP session, so a fresh session is
1628
1724
  * accepted), threads the session + wallet headers, sets `X-PAYMENT`, and
1629
1725
  * collapses an SSE JSON-RPC response to its `result`.
1630
- *
1631
- * Limitation: detects MCP only by the `/mcp` path convention, not the
1632
- * Coinbase Bazaar `extensions.bazaar` 402 signal that `fetch()` also honors.
1633
- * A Bazaar-discoverable merchant on a non-`/mcp` URL would need the standard
1634
- * `fetch()` path. All current MCP-tool merchants use the `/mcp` convention.
1635
1726
  */
1727
+ /**
1728
+ * Wait for a payment's Safe→delegate funding tx to reach ≥1 on-chain
1729
+ * confirmation. The hosted x402 completion path MUST call this after funding
1730
+ * and before delivering the X-PAYMENT header, so the merchant's
1731
+ * balanceOf(delegate) / transferWithAuthorization verification sees the funded
1732
+ * balance — otherwise it rejects with "Payment verification failed". The
1733
+ * SDK's local path already does this (see authorizeStandardX402); the hosted
1734
+ * split flow regressed when the 5→3 collapse removed the incidental
1735
+ * inter-call latency that used to mask it. No-op when the funding tx hash or
1736
+ * a chain RPC (chainRpcs[chainId]) is unavailable.
1737
+ */
1738
+ async ensureFundingConfirmed(paymentId, fundingTxHash) {
1739
+ const status = await this.getPaymentStatus(paymentId);
1740
+ await this.waitForFundingTx(fundingTxHash ?? status.txHash ?? void 0, status.chainId);
1741
+ }
1636
1742
  async completeX402MerchantCall(input) {
1743
+ const evidenceContext = await this.resolveX402MerchantCompletionContext({
1744
+ paymentId: input.paymentId,
1745
+ url: input.url
1746
+ });
1747
+ const shouldHandshakeMcp = isMcpUrl(input.url) || input.mcpTransport?.handshakeRequired === true;
1748
+ const x402Wallet = shouldHandshakeMcp ? await this.resolveX402WalletForMerchantCall() : this.x402PayerAddress();
1637
1749
  let mcpSessionId;
1638
- if (isMcpUrl(input.url)) {
1639
- mcpSessionId = await this.mcpInitialize(input.url, input.init);
1750
+ if (shouldHandshakeMcp) {
1751
+ mcpSessionId = await this.mcpInitialize(input.url, input.init, x402Wallet);
1640
1752
  }
1641
- let requestInit = this.withX402Wallet(input.init, this.x402PayerAddress()) ?? {};
1753
+ let requestInit = this.withX402Wallet(input.init, x402Wallet) ?? {};
1642
1754
  if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
1643
1755
  const headers = new Headers(requestInit.headers);
1644
1756
  headers.set("X-PAYMENT", input.paymentHeader);
1645
1757
  requestInit = { ...requestInit, headers };
1646
1758
  const response = await globalThis.fetch(input.url, requestInit);
1647
1759
  const surfaced = mcpSessionId ? await this.surfaceMcpResult(response) : response;
1648
- const settlement = parseMerchantSettlement(surfaced.headers.get("PAYMENT-RESPONSE"));
1760
+ const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
1761
+ const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
1649
1762
  const text = await surfaced.text();
1650
1763
  let body;
1651
1764
  try {
@@ -1653,6 +1766,35 @@ var HavenClient = class {
1653
1766
  } catch {
1654
1767
  body = text;
1655
1768
  }
1769
+ if (!surfaced.ok) {
1770
+ await this.recordMerchantRetryRejected({
1771
+ rail: "x402",
1772
+ paymentId: evidenceContext.paymentId,
1773
+ txHash: evidenceContext.txHash,
1774
+ resourceUrl: evidenceContext.resourceUrl,
1775
+ merchant: {
1776
+ merchant_status: surfaced.status,
1777
+ merchant_status_text: surfaced.statusText,
1778
+ merchant_headers: Object.fromEntries(surfaced.headers.entries()),
1779
+ merchant_body: text
1780
+ },
1781
+ details: {
1782
+ merchant_to: evidenceContext.merchantAddress
1783
+ }
1784
+ });
1785
+ } else {
1786
+ await this.reportMachinePaymentEvidence({
1787
+ paymentId: evidenceContext.paymentId,
1788
+ rail: "x402",
1789
+ txHash: evidenceContext.txHash,
1790
+ resourceUrl: evidenceContext.resourceUrl,
1791
+ merchantStatus: surfaced.status,
1792
+ paymentProofHeaderName: "X-PAYMENT",
1793
+ paymentProofHeader: input.paymentHeader,
1794
+ protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1795
+ protocolReceiptHeader
1796
+ });
1797
+ }
1656
1798
  return {
1657
1799
  status: surfaced.status,
1658
1800
  ok: surfaced.ok,
@@ -1660,6 +1802,53 @@ var HavenClient = class {
1660
1802
  settlementTxHash: settlement.settlementTxHash ?? void 0
1661
1803
  };
1662
1804
  }
1805
+ async resolveX402MerchantCompletionContext(input) {
1806
+ const status = await this.getPaymentStatus(input.paymentId);
1807
+ if (status.rail !== "x402") {
1808
+ throw new HavenPaymentStateError(
1809
+ `Payment ${status.paymentId} is ${status.rail}, not x402.`,
1810
+ 409,
1811
+ status
1812
+ );
1813
+ }
1814
+ const readyForMerchantCompletion = status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request || status.kind === "payment_intent" && status.status === "confirmed" && status.phase === AgentPaymentPhase.PaymentConfirmed && status.nextAction === AgentPaymentNextAction.None;
1815
+ if (!readyForMerchantCompletion) {
1816
+ throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
1817
+ }
1818
+ if (!status.txHash) {
1819
+ throw new HavenApiError(
1820
+ `x402 payment ${status.paymentId} is ready for merchant completion but has no Haven transaction hash.`,
1821
+ 502,
1822
+ status,
1823
+ status.paymentId
1824
+ );
1825
+ }
1826
+ const approvedResourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
1827
+ if (approvedResourceUrl && approvedResourceUrl !== input.url) {
1828
+ throw new HavenApiError(
1829
+ "x402 merchant completion does not match the approved resource URL.",
1830
+ 409,
1831
+ { status, url: input.url },
1832
+ status.paymentId
1833
+ );
1834
+ }
1835
+ return {
1836
+ paymentId: status.paymentId,
1837
+ txHash: status.txHash,
1838
+ resourceUrl: approvedResourceUrl ?? input.url,
1839
+ merchantAddress: status.merchantAddress ?? status.x402?.merchantAddress ?? null
1840
+ };
1841
+ }
1842
+ async resolveX402WalletForMerchantCall() {
1843
+ const localWallet = this.x402PayerAddress();
1844
+ if (localWallet) return localWallet;
1845
+ try {
1846
+ const agent = await this.getAgent();
1847
+ return agent.delegateAddress ?? void 0;
1848
+ } catch {
1849
+ return void 0;
1850
+ }
1851
+ }
1663
1852
  async authorizeMachinePayment(challenge, options = {}) {
1664
1853
  if (!this.delegateKey) {
1665
1854
  throw new HavenSigningError(
@@ -2257,7 +2446,7 @@ var HavenClient = class {
2257
2446
  headers
2258
2447
  };
2259
2448
  }
2260
- buildX402Quote(paymentRequired, request, idempotencyKey) {
2449
+ buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport) {
2261
2450
  const option = selectStandardPaymentOption(paymentRequired.accepts);
2262
2451
  if (!option) {
2263
2452
  throw new HavenApiError(
@@ -2272,6 +2461,7 @@ var HavenClient = class {
2272
2461
  paymentRequired,
2273
2462
  accepted: option,
2274
2463
  request,
2464
+ ...mcpTransport ? { mcpTransport } : {},
2275
2465
  resourceUrl: paymentRequired.resource.url,
2276
2466
  description: paymentRequired.resource.description ?? option.description ?? null,
2277
2467
  mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
@@ -2285,6 +2475,18 @@ var HavenClient = class {
2285
2475
  maxTimeoutSeconds: option.maxTimeoutSeconds
2286
2476
  };
2287
2477
  }
2478
+ async detectX402McpTransport(url, paymentRequired, response) {
2479
+ if (isMcpUrl(url)) {
2480
+ return { handshakeRequired: true, source: "path" };
2481
+ }
2482
+ if (paymentRequired.extensions?.bazaar != null) {
2483
+ return { handshakeRequired: true, source: "bazaar" };
2484
+ }
2485
+ if (await responseHasBazaarExtension(response)) {
2486
+ return { handshakeRequired: true, source: "bazaar" };
2487
+ }
2488
+ return void 0;
2489
+ }
2288
2490
  buildX402ResumeState(input) {
2289
2491
  const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
2290
2492
  return {
@@ -2812,8 +3014,9 @@ var toolDescriptions = {
2812
3014
  nextActionGuidance: ""
2813
3015
  },
2814
3016
  getAgent: {
2815
- summary: "Return the authenticated agent identity, Haven wallet, delegate address, chain, and status.",
2816
- behavior: "Read-only identity lookup. Useful for verifying which on-chain Safe and delegate the credential is bound to.",
3017
+ summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, a readiness signal, and per-token remaining allowance (atomic + human-readable). The recommended first call in a new session to confirm who you are and whether you can pay right now.",
3018
+ 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.",
3019
+ behavior: 'Reads identity plus the on-chain AllowanceModule snapshot in one shot. readiness is "ready" when at least one token has remaining on-chain allowance, "needs_approval" when the agent is active but has no remaining allowance to auto-spend (payments will be queued for the wallet owner to approve in Haven), and "revoked" when the credential is not active. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields (id, name, status, safeAddress, delegateAddress, chainId) are unchanged from before.',
2817
3020
  nextActionGuidance: ""
2818
3021
  },
2819
3022
  getAllowances: {
@@ -2837,8 +3040,8 @@ var toolDescriptions = {
2837
3040
  discoverTools: {
2838
3041
  summary: "Discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use.",
2839
3042
  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.",
2840
- behavior: "Read-only lookup against Haven's curated catalog. Entries are periodically re-verified against the live merchant; degraded entries are flagged. Returns name, description, price, rail, resource URL, and a suggested_tool field naming the exact Haven pay tool for that entry. Never creates a payment, signature, or approval.",
2841
- nextActionGuidance: "Pick an entry, confirm the price with the user if it is non-trivial, and pay it with the tool named in suggested_tool, passing the entry's resource_url (and tool_name for MCP merchants)."
3043
+ behavior: "Read-only lookup against Haven's curated catalog. Entries are periodically re-verified against the live merchant; degraded entries are flagged. Returns name, description, price, rail, resource URL, and a suggested_tool field naming the exact Haven pay tool for that entry. 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.",
3044
+ nextActionGuidance: "Pick an entry and pay it with the tool named in suggested_tool, passing the entry's resource_url (and tool_name 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."
2842
3045
  },
2843
3046
  sweep_delegate: {
2844
3047
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
@@ -3105,18 +3308,37 @@ the Haven MCP tools. Every payment is checked against the agent's on-chain
3105
3308
  budget before money moves; payments above the remaining budget wait for the
3106
3309
  user's approval in Haven.
3107
3310
 
3311
+ Hosted tools run in the \`mcp__haven__\` namespace. Local signing tools run in
3312
+ the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
3313
+
3108
3314
  ## When to use this skill
3109
3315
 
3110
3316
  - The user asks to send money, pay someone, tip, donate, or transfer tokens.
3111
3317
  - A request returns HTTP 402 (x402): use the Haven pay tools to settle it,
3112
3318
  then retry the original request.
3113
3319
 
3114
- ## Identity and budget come from the tools \u2014 never assume them
3320
+ ## Identity and budget
3321
+
3322
+ Do not guess the wallet address, network, or budget.
3115
3323
 
3116
- Do not guess the wallet address, network, or budget. Read them live:
3324
+ For instant orientation at the start of a session, read the non-secret
3325
+ \`agent.json\` the connector wrote to your Haven credential directory (typically
3326
+ \`~/.haven/agents/<agent-id>/agent.json\` \u2014 if you don't know the agent id, list
3327
+ \`~/.haven/agents/\` to find the folder). It
3328
+ holds your agent id, Haven wallet address, network, and *configured* per-token
3329
+ budget, and contains no keys \u2014 the fastest way to answer "who am I and what may
3330
+ I spend" with no round trip. If that file is absent (some setups don't write
3331
+ it), use the tools below instead.
3117
3332
 
3118
- - \`haven_get_agent\` \u2014 agent identity, Haven wallet address, network.
3119
- - \`haven_get_allowances\` \u2014 current per-token budgets and what remains.
3333
+ Before any payment, confirm the *live remaining* budget with the tools \u2014
3334
+ \`agent.json\` shows the configured budget, not what is left after recent
3335
+ spending:
3336
+
3337
+ - \`haven_get_agent\` \u2014 the recommended first call: identity (wallet, network)
3338
+ plus a readiness signal (\`ready\` / \`needs_approval\` / \`revoked\`) and live
3339
+ remaining per-token allowance, in one shot.
3340
+ - \`haven_get_allowances\` \u2014 detailed per-token breakdown (configured, spent,
3341
+ reset window) when you need more than the summary.
3120
3342
 
3121
3343
  Budgets reset on a period the user chose. If a payment exceeds the remaining
3122
3344
  budget it is queued for the user to approve in the Haven dashboard \u2014 this is
@@ -3130,13 +3352,33 @@ normal, not an error.
3130
3352
  the local Haven signer; follow the tool results \u2014 they tell you the next
3131
3353
  action at every step. Retry the original request only when the result says
3132
3354
  \`retry_original_x402_request\`.
3133
- - **Paid MCP tool call:** \`haven_pay_mcp_tool\` with the merchant URL, tool
3134
- name, and arguments. Then follow the returned steps: \`haven_sign\` the
3135
- funding hash, \`haven_submit\` the signature, \`haven_x402_sign_header\` to
3136
- build the payment header, and finally \`haven_complete_mcp_tool\` to settle
3137
- with the merchant and get the tool result. Pass \`payment_required\` and
3138
- \`arguments\` through verbatim from the \`haven_pay_mcp_tool\` result. Do not
3139
- call the merchant yourself \u2014 Haven completes the merchant leg for you.
3355
+ - **Paid MCP tool call:** \`mcp__haven__haven_pay_mcp_tool\` with the merchant
3356
+ URL, tool name, and arguments, then finish in two calls (fast path):
3357
+ \`mcp__haven-signer__haven_sign_x402\` on the local signer (pass
3358
+ \`payload_hash\`, \`x402_expected\` as the nested \`x402.expected\` object, and
3359
+ \`payment_required\`) returns \`{ signature, payment_header }\`; then
3360
+ \`mcp__haven__haven_settle_mcp_tool\` (pass \`payment_id\`, \`signature\`,
3361
+ \`payment_header\`, \`merchant_url\`, \`tool_name\`, \`arguments\`,
3362
+ \`mcp_transport\`) funds and settles in one step and returns the tool result.
3363
+ If it returns \`settled: false\`, funding is queued for the user's approval \u2014
3364
+ tell them and check status later, do not re-pay. Step-by-step alternative:
3365
+ \`mcp__haven-signer__haven_sign\` \u2192 \`mcp__haven__haven_submit\` \u2192
3366
+ \`mcp__haven-signer__haven_x402_sign_header\` \u2192
3367
+ \`mcp__haven__haven_complete_mcp_tool\`. Pass \`payment_required\`,
3368
+ \`arguments\`, and \`mcp_transport\` verbatim from the
3369
+ \`mcp__haven__haven_pay_mcp_tool\` result. The returned \`expires_at\` is the
3370
+ signing window; if a tool returns \`PAYMENT_WINDOW_EXPIRED\`, re-run
3371
+ \`mcp__haven__haven_pay_mcp_tool\` with the same
3372
+ \`idempotency_key\`. Do not call the merchant yourself \u2014 Haven completes the
3373
+ merchant leg for you.
3374
+ - **Prices:** show the user the live price from the pay-tool result, never a
3375
+ catalog price. \`haven_discover_tools\` prices are indicative
3376
+ (\`price_is_indicative\`) and can be stale. The pay-tool result's \`amount\` /
3377
+ \`amount_atomic\` is the amount Haven authorizes for the call \u2014 a ceiling the
3378
+ merchant settles at or below \u2014 so present it as the most the user will pay.
3379
+ Pass \`max_amount\` (atomic units) to \`haven_pay_mcp_tool\` /
3380
+ \`haven_pay_x402_quote\` to reject a quote whose authorized amount is above the
3381
+ user's cap, before any funds move.
3140
3382
  - **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
3141
3383
  queued or in-flight payments. Do not poll in a tight loop.
3142
3384
 
@@ -3145,18 +3387,26 @@ normal, not an error.
3145
3387
  - A result with \`pending_approval\` means the payment exceeded the remaining
3146
3388
  budget and is waiting for the user in Haven. Tell the user, then check
3147
3389
  status later.
3148
- - Never ask the user for private keys and never try to sign anything
3149
- yourself \u2014 Haven signs. If a tool reports a missing or invalid credential,
3150
- tell the user to re-run the Haven setup command.
3390
+ - Never ask the user for private keys. Signing happens only in the local Haven
3391
+ signer; the hosted Haven tools never receive the signing key. If a tool
3392
+ reports a missing or invalid credential, tell the user to re-run the Haven
3393
+ setup command.
3151
3394
 
3152
3395
  ## Failure handling
3153
3396
 
3154
- Haven errors are shaped \`{ error, status, details? }\` and written for
3155
- humans \u2014 surface the message verbatim. Common cases:
3397
+ Haven tool failures are shaped like \`{ success: false, code, message, ... }\`
3398
+ or older \`{ error, status, details? }\` responses. Branch on \`code\` when
3399
+ present and surface \`message\` or \`error\` verbatim. Common cases:
3156
3400
 
3157
3401
  - \`pending_approval\`: queued for the user's approval (see above).
3158
3402
  - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
3159
3403
  Suggest the user add funds in the Haven dashboard.
3404
+ - \`PRICE_EXCEEDS_MAX\`: the live merchant price exceeded your \`max_amount\`.
3405
+ No funds moved; ask the user before retrying with a higher cap.
3406
+ - \`PAYMENT_WINDOW_EXPIRED\`: re-run \`mcp__haven__haven_pay_mcp_tool\` with the same
3407
+ \`idempotency_key\`, then sign the fresh \`payload_hash\`.
3408
+ - \`MERCHANT_REJECTED_AFTER_FUNDING\`: stop retrying the merchant and use
3409
+ \`mcp__haven__haven_sweep_delegate\` to recover stranded delegate funds.
3160
3410
  - Budget exceeded: tell the user how much remains (from
3161
3411
  \`haven_get_allowances\`) and that they can raise the budget in Haven.
3162
3412
 
@@ -3168,6 +3418,98 @@ for that credential.
3168
3418
  `;
3169
3419
  var SKILL_FOLDER_NAME = "haven-pay";
3170
3420
 
3171
- export { AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, SKILL_FOLDER_NAME, addressFromKey, buildMachinePaymentIdempotencyKey, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, toStandardPaymentRequirements, toolDescriptions, verifySignature, x402AuthorizationAmount };
3421
+ // src/sweep.ts
3422
+ var SWEEP_BASE_CHAIN_ID = 8453;
3423
+ var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3424
+ var USDC_EIP712_DOMAIN_BY_CHAIN = {
3425
+ [SWEEP_BASE_CHAIN_ID]: {
3426
+ name: "USD Coin",
3427
+ version: "2",
3428
+ chainId: SWEEP_BASE_CHAIN_ID,
3429
+ verifyingContract: SWEEP_BASE_USDC_ADDRESS
3430
+ }
3431
+ };
3432
+ var USDC_ADDRESS_BY_CHAIN = {
3433
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3434
+ };
3435
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3436
+ TransferWithAuthorization: [
3437
+ { name: "from", type: "address" },
3438
+ { name: "to", type: "address" },
3439
+ { name: "value", type: "uint256" },
3440
+ { name: "validAfter", type: "uint256" },
3441
+ { name: "validBefore", type: "uint256" },
3442
+ { name: "nonce", type: "bytes32" }
3443
+ ]
3444
+ };
3445
+ function sweepUsdcAddress(chainId) {
3446
+ const address = USDC_ADDRESS_BY_CHAIN[chainId];
3447
+ if (!address) {
3448
+ throw new HavenSigningError(
3449
+ `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3450
+ );
3451
+ }
3452
+ return address;
3453
+ }
3454
+ function sweepUsdcDomain(chainId) {
3455
+ const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3456
+ if (!domain) {
3457
+ throw new HavenSigningError(
3458
+ `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3459
+ );
3460
+ }
3461
+ return domain;
3462
+ }
3463
+ function sameAddress2(a, b) {
3464
+ return a.toLowerCase() === b.toLowerCase();
3465
+ }
3466
+ function buildSweepTypedData(auth) {
3467
+ const domain = sweepUsdcDomain(auth.chainId);
3468
+ const expectedToken = sweepUsdcAddress(auth.chainId);
3469
+ if (!sameAddress2(auth.token, expectedToken)) {
3470
+ throw new HavenSigningError(
3471
+ `Sweep token ${auth.token} is not the canonical USDC contract for chain ${auth.chainId}.`
3472
+ );
3473
+ }
3474
+ if (!/^0x[0-9a-fA-F]{64}$/.test(auth.nonce)) {
3475
+ throw new HavenSigningError("Sweep nonce must be a 0x-prefixed 32-byte hex string.");
3476
+ }
3477
+ return {
3478
+ domain,
3479
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
3480
+ primaryType: "TransferWithAuthorization",
3481
+ message: {
3482
+ from: auth.from,
3483
+ to: auth.to,
3484
+ value: BigInt(auth.value),
3485
+ validAfter: BigInt(auth.validAfter),
3486
+ validBefore: BigInt(auth.validBefore),
3487
+ nonce: auth.nonce
3488
+ }
3489
+ };
3490
+ }
3491
+ function buildSweepAuthorizationMessage(auth) {
3492
+ return `Haven sweep authorization v1
3493
+ ${stableStringify2({
3494
+ version: 1,
3495
+ kind: "haven.sweep.authorization",
3496
+ from: auth.from.toLowerCase(),
3497
+ to: auth.to.toLowerCase(),
3498
+ value: auth.value,
3499
+ validAfter: auth.validAfter,
3500
+ validBefore: auth.validBefore,
3501
+ nonce: auth.nonce.toLowerCase(),
3502
+ token: auth.token.toLowerCase(),
3503
+ chainId: auth.chainId
3504
+ })}`;
3505
+ }
3506
+ function stableStringify2(value) {
3507
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
3508
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
3509
+ const object = value;
3510
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
3511
+ }
3512
+
3513
+ export { AGENT_PAYMENT_FAILURE_CODE_VALUES, AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentFailureCode, AgentPaymentFailureCodeDescriptions, AgentPaymentFailureCodeSchema, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, verifySignature, x402AuthorizationAmount };
3172
3514
  //# sourceMappingURL=index.js.map
3173
3515
  //# sourceMappingURL=index.js.map