@haven_ai/sdk 0.1.13-alpha.0 → 0.1.15-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];
@@ -890,6 +921,7 @@ var HavenClient = class {
890
921
  }
891
922
  return {
892
923
  paymentId: raw.payment_id,
924
+ idempotencyKey,
893
925
  status: "pending_signature",
894
926
  expiresAt: raw.expires_at,
895
927
  signData: raw.sign_data,
@@ -1041,6 +1073,30 @@ var HavenClient = class {
1041
1073
  transfers
1042
1074
  };
1043
1075
  }
1076
+ /**
1077
+ * Hosted (keyless) split-signer sweep — step 1 of 2.
1078
+ *
1079
+ * Asks the backend to build a gasless EIP-3009 sweep authorization for the
1080
+ * delegate's stranded USDC. Returns `nothing_stranded` when the delegate is
1081
+ * empty, otherwise an `authorization` + Haven `expected_auth` to hand to the
1082
+ * edge signer's `haven_sign_sweep_delegate`. No key is required on this client.
1083
+ */
1084
+ async prepareSweep() {
1085
+ return this.post("/machine-payments/sweep/prepare", {});
1086
+ }
1087
+ /**
1088
+ * Hosted (keyless) split-signer sweep — step 2 of 2.
1089
+ *
1090
+ * Relays the delegate-signed authorization. The Haven relayer submits the
1091
+ * on-chain `transferWithAuthorization` and pays gas; this client never holds
1092
+ * the key.
1093
+ */
1094
+ async submitSweep(authorization, signature) {
1095
+ return this.post("/machine-payments/sweep/submit", {
1096
+ authorization,
1097
+ signature
1098
+ });
1099
+ }
1044
1100
  /**
1045
1101
  * Get configured and on-chain allowances for the authenticated agent.
1046
1102
  */
@@ -1196,7 +1252,8 @@ var HavenClient = class {
1196
1252
  throw new HavenApiError("quoteX402 only supports standard x402 Payment Required responses.", 400);
1197
1253
  }
1198
1254
  const paymentRequired = await parsePaymentRequiredResponse(response);
1199
- return this.buildX402Quote(paymentRequired, request, options.idempotencyKey);
1255
+ const mcpTransport = await this.detectX402McpTransport(url, paymentRequired, response);
1256
+ return this.buildX402Quote(paymentRequired, request, options.idempotencyKey, mcpTransport);
1200
1257
  }
1201
1258
  /**
1202
1259
  * Pay a previously inspected x402 quote and retry the exact captured request.
@@ -1401,12 +1458,11 @@ var HavenClient = class {
1401
1458
  * a transport/HTTP error, a missing session id, or a JSON-RPC error in the
1402
1459
  * handshake response — so the caller can fall back to plain x402.
1403
1460
  */
1404
- async mcpInitialize(url, init) {
1461
+ async mcpInitialize(url, init, wallet = this.x402PayerAddress()) {
1405
1462
  try {
1406
1463
  const headers = new Headers(init?.headers);
1407
1464
  headers.set("Content-Type", "application/json");
1408
1465
  headers.set("Accept", MCP_ACCEPT);
1409
- const wallet = this.x402PayerAddress();
1410
1466
  if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1411
1467
  const response = await globalThis.fetch(url, {
1412
1468
  method: "POST",
@@ -1427,7 +1483,7 @@ var HavenClient = class {
1427
1483
  if (!sessionId) return void 0;
1428
1484
  const message = await this.readMcpMessage(response);
1429
1485
  if (message && "error" in message) return void 0;
1430
- await this.mcpNotifyInitialized(url, init, sessionId);
1486
+ await this.mcpNotifyInitialized(url, init, sessionId, wallet);
1431
1487
  return sessionId;
1432
1488
  } catch {
1433
1489
  return void 0;
@@ -1438,13 +1494,12 @@ var HavenClient = class {
1438
1494
  * lifecycle handshake. Best-effort: the session is already established, so a
1439
1495
  * failed notification must not abort the payment.
1440
1496
  */
1441
- async mcpNotifyInitialized(url, init, sessionId) {
1497
+ async mcpNotifyInitialized(url, init, sessionId, wallet = this.x402PayerAddress()) {
1442
1498
  try {
1443
1499
  const headers = new Headers(init?.headers);
1444
1500
  headers.set("Content-Type", "application/json");
1445
1501
  headers.set("Accept", MCP_ACCEPT);
1446
1502
  headers.set("mcp-session-id", sessionId);
1447
- const wallet = this.x402PayerAddress();
1448
1503
  if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1449
1504
  await globalThis.fetch(url, {
1450
1505
  method: "POST",
@@ -1612,6 +1667,133 @@ var HavenClient = class {
1612
1667
  });
1613
1668
  return retryResponse;
1614
1669
  }
1670
+ /**
1671
+ * Deliver an already-signed x402 payment header to the merchant and return
1672
+ * the merchant's response. Used by the hosted MCP server to complete the
1673
+ * merchant leg of an MCP tool payment after the edge signer has built the
1674
+ * `X-PAYMENT` header.
1675
+ *
1676
+ * Custody note: this never needs the delegate key. It relays a signed,
1677
+ * amount/merchant/nonce-bound EIP-3009 authorization the edge signer already
1678
+ * produced — the hosted server cannot mint or reuse signing authority.
1679
+ *
1680
+ * When the URL is MCP-shaped (`/mcp` path) or the quote-time transport context
1681
+ * says the merchant was Bazaar-discoverable, runs a fresh `initialize`
1682
+ * handshake (the quote-time session is gone once funding confirms; the x402
1683
+ * challenge is stateless w.r.t. the MCP session, so a fresh session is
1684
+ * accepted), threads the session + wallet headers, sets `X-PAYMENT`, and
1685
+ * collapses an SSE JSON-RPC response to its `result`.
1686
+ */
1687
+ async completeX402MerchantCall(input) {
1688
+ const evidenceContext = await this.resolveX402MerchantCompletionContext({
1689
+ paymentId: input.paymentId,
1690
+ url: input.url
1691
+ });
1692
+ const shouldHandshakeMcp = isMcpUrl(input.url) || input.mcpTransport?.handshakeRequired === true;
1693
+ const x402Wallet = shouldHandshakeMcp ? await this.resolveX402WalletForMerchantCall() : this.x402PayerAddress();
1694
+ let mcpSessionId;
1695
+ if (shouldHandshakeMcp) {
1696
+ mcpSessionId = await this.mcpInitialize(input.url, input.init, x402Wallet);
1697
+ }
1698
+ let requestInit = this.withX402Wallet(input.init, x402Wallet) ?? {};
1699
+ if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
1700
+ const headers = new Headers(requestInit.headers);
1701
+ headers.set("X-PAYMENT", input.paymentHeader);
1702
+ requestInit = { ...requestInit, headers };
1703
+ const response = await globalThis.fetch(input.url, requestInit);
1704
+ const surfaced = mcpSessionId ? await this.surfaceMcpResult(response) : response;
1705
+ const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
1706
+ const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
1707
+ const text = await surfaced.text();
1708
+ let body;
1709
+ try {
1710
+ body = text ? JSON.parse(text) : null;
1711
+ } catch {
1712
+ body = text;
1713
+ }
1714
+ if (!surfaced.ok) {
1715
+ await this.recordMerchantRetryRejected({
1716
+ rail: "x402",
1717
+ paymentId: evidenceContext.paymentId,
1718
+ txHash: evidenceContext.txHash,
1719
+ resourceUrl: evidenceContext.resourceUrl,
1720
+ merchant: {
1721
+ merchant_status: surfaced.status,
1722
+ merchant_status_text: surfaced.statusText,
1723
+ merchant_headers: Object.fromEntries(surfaced.headers.entries()),
1724
+ merchant_body: text
1725
+ },
1726
+ details: {
1727
+ merchant_to: evidenceContext.merchantAddress
1728
+ }
1729
+ });
1730
+ } else {
1731
+ await this.reportMachinePaymentEvidence({
1732
+ paymentId: evidenceContext.paymentId,
1733
+ rail: "x402",
1734
+ txHash: evidenceContext.txHash,
1735
+ resourceUrl: evidenceContext.resourceUrl,
1736
+ merchantStatus: surfaced.status,
1737
+ paymentProofHeaderName: "X-PAYMENT",
1738
+ paymentProofHeader: input.paymentHeader,
1739
+ protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1740
+ protocolReceiptHeader
1741
+ });
1742
+ }
1743
+ return {
1744
+ status: surfaced.status,
1745
+ ok: surfaced.ok,
1746
+ body,
1747
+ settlementTxHash: settlement.settlementTxHash ?? void 0
1748
+ };
1749
+ }
1750
+ async resolveX402MerchantCompletionContext(input) {
1751
+ const status = await this.getPaymentStatus(input.paymentId);
1752
+ if (status.rail !== "x402") {
1753
+ throw new HavenPaymentStateError(
1754
+ `Payment ${status.paymentId} is ${status.rail}, not x402.`,
1755
+ 409,
1756
+ status
1757
+ );
1758
+ }
1759
+ const readyForMerchantCompletion = status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request || status.kind === "payment_intent" && status.status === "confirmed" && status.phase === AgentPaymentPhase.PaymentConfirmed && status.nextAction === AgentPaymentNextAction.None;
1760
+ if (!readyForMerchantCompletion) {
1761
+ throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
1762
+ }
1763
+ if (!status.txHash) {
1764
+ throw new HavenApiError(
1765
+ `x402 payment ${status.paymentId} is ready for merchant completion but has no Haven transaction hash.`,
1766
+ 502,
1767
+ status,
1768
+ status.paymentId
1769
+ );
1770
+ }
1771
+ const approvedResourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
1772
+ if (approvedResourceUrl && approvedResourceUrl !== input.url) {
1773
+ throw new HavenApiError(
1774
+ "x402 merchant completion does not match the approved resource URL.",
1775
+ 409,
1776
+ { status, url: input.url },
1777
+ status.paymentId
1778
+ );
1779
+ }
1780
+ return {
1781
+ paymentId: status.paymentId,
1782
+ txHash: status.txHash,
1783
+ resourceUrl: approvedResourceUrl ?? input.url,
1784
+ merchantAddress: status.merchantAddress ?? status.x402?.merchantAddress ?? null
1785
+ };
1786
+ }
1787
+ async resolveX402WalletForMerchantCall() {
1788
+ const localWallet = this.x402PayerAddress();
1789
+ if (localWallet) return localWallet;
1790
+ try {
1791
+ const agent = await this.getAgent();
1792
+ return agent.delegateAddress ?? void 0;
1793
+ } catch {
1794
+ return void 0;
1795
+ }
1796
+ }
1615
1797
  async authorizeMachinePayment(challenge, options = {}) {
1616
1798
  if (!this.delegateKey) {
1617
1799
  throw new HavenSigningError(
@@ -2209,7 +2391,7 @@ var HavenClient = class {
2209
2391
  headers
2210
2392
  };
2211
2393
  }
2212
- buildX402Quote(paymentRequired, request, idempotencyKey) {
2394
+ buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport) {
2213
2395
  const option = selectStandardPaymentOption(paymentRequired.accepts);
2214
2396
  if (!option) {
2215
2397
  throw new HavenApiError(
@@ -2224,6 +2406,7 @@ var HavenClient = class {
2224
2406
  paymentRequired,
2225
2407
  accepted: option,
2226
2408
  request,
2409
+ ...mcpTransport ? { mcpTransport } : {},
2227
2410
  resourceUrl: paymentRequired.resource.url,
2228
2411
  description: paymentRequired.resource.description ?? option.description ?? null,
2229
2412
  mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
@@ -2237,6 +2420,18 @@ var HavenClient = class {
2237
2420
  maxTimeoutSeconds: option.maxTimeoutSeconds
2238
2421
  };
2239
2422
  }
2423
+ async detectX402McpTransport(url, paymentRequired, response) {
2424
+ if (isMcpUrl(url)) {
2425
+ return { handshakeRequired: true, source: "path" };
2426
+ }
2427
+ if (paymentRequired.extensions?.bazaar != null) {
2428
+ return { handshakeRequired: true, source: "bazaar" };
2429
+ }
2430
+ if (await responseHasBazaarExtension(response)) {
2431
+ return { handshakeRequired: true, source: "bazaar" };
2432
+ }
2433
+ return void 0;
2434
+ }
2240
2435
  buildX402ResumeState(input) {
2241
2436
  const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
2242
2437
  return {
@@ -2789,8 +2984,8 @@ var toolDescriptions = {
2789
2984
  discoverTools: {
2790
2985
  summary: "Discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use.",
2791
2986
  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.",
2792
- 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.",
2793
- 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)."
2987
+ 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.",
2988
+ 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."
2794
2989
  },
2795
2990
  sweep_delegate: {
2796
2991
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
@@ -3057,6 +3252,9 @@ the Haven MCP tools. Every payment is checked against the agent's on-chain
3057
3252
  budget before money moves; payments above the remaining budget wait for the
3058
3253
  user's approval in Haven.
3059
3254
 
3255
+ Hosted tools run in the \`mcp__haven__\` namespace. Local signing tools run in
3256
+ the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
3257
+
3060
3258
  ## When to use this skill
3061
3259
 
3062
3260
  - The user asks to send money, pay someone, tip, donate, or transfer tokens.
@@ -3082,6 +3280,33 @@ normal, not an error.
3082
3280
  the local Haven signer; follow the tool results \u2014 they tell you the next
3083
3281
  action at every step. Retry the original request only when the result says
3084
3282
  \`retry_original_x402_request\`.
3283
+ - **Paid MCP tool call:** \`mcp__haven__haven_pay_mcp_tool\` with the merchant
3284
+ URL, tool name, and arguments, then finish in two calls (fast path):
3285
+ \`mcp__haven-signer__haven_sign_x402\` on the local signer (pass
3286
+ \`payload_hash\`, \`x402_expected\` as the nested \`x402.expected\` object, and
3287
+ \`payment_required\`) returns \`{ signature, payment_header }\`; then
3288
+ \`mcp__haven__haven_settle_mcp_tool\` (pass \`payment_id\`, \`signature\`,
3289
+ \`payment_header\`, \`merchant_url\`, \`tool_name\`, \`arguments\`,
3290
+ \`mcp_transport\`) funds and settles in one step and returns the tool result.
3291
+ If it returns \`settled: false\`, funding is queued for the user's approval \u2014
3292
+ tell them and check status later, do not re-pay. Step-by-step alternative:
3293
+ \`mcp__haven-signer__haven_sign\` \u2192 \`mcp__haven__haven_submit\` \u2192
3294
+ \`mcp__haven-signer__haven_x402_sign_header\` \u2192
3295
+ \`mcp__haven__haven_complete_mcp_tool\`. Pass \`payment_required\`,
3296
+ \`arguments\`, and \`mcp_transport\` verbatim from the
3297
+ \`mcp__haven__haven_pay_mcp_tool\` result. The returned \`expires_at\` is the
3298
+ signing window; if a tool returns \`PAYMENT_WINDOW_EXPIRED\`, re-run
3299
+ \`mcp__haven__haven_pay_mcp_tool\` with the same
3300
+ \`idempotency_key\`. Do not call the merchant yourself \u2014 Haven completes the
3301
+ merchant leg for you.
3302
+ - **Prices:** show the user the live price from the pay-tool result, never a
3303
+ catalog price. \`haven_discover_tools\` prices are indicative
3304
+ (\`price_is_indicative\`) and can be stale. The pay-tool result's \`amount\` /
3305
+ \`amount_atomic\` is the amount Haven authorizes for the call \u2014 a ceiling the
3306
+ merchant settles at or below \u2014 so present it as the most the user will pay.
3307
+ Pass \`max_amount\` (atomic units) to \`haven_pay_mcp_tool\` /
3308
+ \`haven_pay_x402_quote\` to reject a quote whose authorized amount is above the
3309
+ user's cap, before any funds move.
3085
3310
  - **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
3086
3311
  queued or in-flight payments. Do not poll in a tight loop.
3087
3312
 
@@ -3090,18 +3315,26 @@ normal, not an error.
3090
3315
  - A result with \`pending_approval\` means the payment exceeded the remaining
3091
3316
  budget and is waiting for the user in Haven. Tell the user, then check
3092
3317
  status later.
3093
- - Never ask the user for private keys and never try to sign anything
3094
- yourself \u2014 Haven signs. If a tool reports a missing or invalid credential,
3095
- tell the user to re-run the Haven setup command.
3318
+ - Never ask the user for private keys. Signing happens only in the local Haven
3319
+ signer; the hosted Haven tools never receive the signing key. If a tool
3320
+ reports a missing or invalid credential, tell the user to re-run the Haven
3321
+ setup command.
3096
3322
 
3097
3323
  ## Failure handling
3098
3324
 
3099
- Haven errors are shaped \`{ error, status, details? }\` and written for
3100
- humans \u2014 surface the message verbatim. Common cases:
3325
+ Haven tool failures are shaped like \`{ success: false, code, message, ... }\`
3326
+ or older \`{ error, status, details? }\` responses. Branch on \`code\` when
3327
+ present and surface \`message\` or \`error\` verbatim. Common cases:
3101
3328
 
3102
3329
  - \`pending_approval\`: queued for the user's approval (see above).
3103
3330
  - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
3104
3331
  Suggest the user add funds in the Haven dashboard.
3332
+ - \`PRICE_EXCEEDS_MAX\`: the live merchant price exceeded your \`max_amount\`.
3333
+ No funds moved; ask the user before retrying with a higher cap.
3334
+ - \`PAYMENT_WINDOW_EXPIRED\`: re-run \`mcp__haven__haven_pay_mcp_tool\` with the same
3335
+ \`idempotency_key\`, then sign the fresh \`payload_hash\`.
3336
+ - \`MERCHANT_REJECTED_AFTER_FUNDING\`: stop retrying the merchant and use
3337
+ \`mcp__haven__haven_sweep_delegate\` to recover stranded delegate funds.
3105
3338
  - Budget exceeded: tell the user how much remains (from
3106
3339
  \`haven_get_allowances\`) and that they can raise the budget in Haven.
3107
3340
 
@@ -3113,6 +3346,98 @@ for that credential.
3113
3346
  `;
3114
3347
  var SKILL_FOLDER_NAME = "haven-pay";
3115
3348
 
3116
- 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 };
3349
+ // src/sweep.ts
3350
+ var SWEEP_BASE_CHAIN_ID = 8453;
3351
+ var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3352
+ var USDC_EIP712_DOMAIN_BY_CHAIN = {
3353
+ [SWEEP_BASE_CHAIN_ID]: {
3354
+ name: "USD Coin",
3355
+ version: "2",
3356
+ chainId: SWEEP_BASE_CHAIN_ID,
3357
+ verifyingContract: SWEEP_BASE_USDC_ADDRESS
3358
+ }
3359
+ };
3360
+ var USDC_ADDRESS_BY_CHAIN = {
3361
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3362
+ };
3363
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3364
+ TransferWithAuthorization: [
3365
+ { name: "from", type: "address" },
3366
+ { name: "to", type: "address" },
3367
+ { name: "value", type: "uint256" },
3368
+ { name: "validAfter", type: "uint256" },
3369
+ { name: "validBefore", type: "uint256" },
3370
+ { name: "nonce", type: "bytes32" }
3371
+ ]
3372
+ };
3373
+ function sweepUsdcAddress(chainId) {
3374
+ const address = USDC_ADDRESS_BY_CHAIN[chainId];
3375
+ if (!address) {
3376
+ throw new HavenSigningError(
3377
+ `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3378
+ );
3379
+ }
3380
+ return address;
3381
+ }
3382
+ function sweepUsdcDomain(chainId) {
3383
+ const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3384
+ if (!domain) {
3385
+ throw new HavenSigningError(
3386
+ `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3387
+ );
3388
+ }
3389
+ return domain;
3390
+ }
3391
+ function sameAddress2(a, b) {
3392
+ return a.toLowerCase() === b.toLowerCase();
3393
+ }
3394
+ function buildSweepTypedData(auth) {
3395
+ const domain = sweepUsdcDomain(auth.chainId);
3396
+ const expectedToken = sweepUsdcAddress(auth.chainId);
3397
+ if (!sameAddress2(auth.token, expectedToken)) {
3398
+ throw new HavenSigningError(
3399
+ `Sweep token ${auth.token} is not the canonical USDC contract for chain ${auth.chainId}.`
3400
+ );
3401
+ }
3402
+ if (!/^0x[0-9a-fA-F]{64}$/.test(auth.nonce)) {
3403
+ throw new HavenSigningError("Sweep nonce must be a 0x-prefixed 32-byte hex string.");
3404
+ }
3405
+ return {
3406
+ domain,
3407
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
3408
+ primaryType: "TransferWithAuthorization",
3409
+ message: {
3410
+ from: auth.from,
3411
+ to: auth.to,
3412
+ value: BigInt(auth.value),
3413
+ validAfter: BigInt(auth.validAfter),
3414
+ validBefore: BigInt(auth.validBefore),
3415
+ nonce: auth.nonce
3416
+ }
3417
+ };
3418
+ }
3419
+ function buildSweepAuthorizationMessage(auth) {
3420
+ return `Haven sweep authorization v1
3421
+ ${stableStringify2({
3422
+ version: 1,
3423
+ kind: "haven.sweep.authorization",
3424
+ from: auth.from.toLowerCase(),
3425
+ to: auth.to.toLowerCase(),
3426
+ value: auth.value,
3427
+ validAfter: auth.validAfter,
3428
+ validBefore: auth.validBefore,
3429
+ nonce: auth.nonce.toLowerCase(),
3430
+ token: auth.token.toLowerCase(),
3431
+ chainId: auth.chainId
3432
+ })}`;
3433
+ }
3434
+ function stableStringify2(value) {
3435
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
3436
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
3437
+ const object = value;
3438
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
3439
+ }
3440
+
3441
+ 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 };
3117
3442
  //# sourceMappingURL=index.js.map
3118
3443
  //# sourceMappingURL=index.js.map