@haven_ai/sdk 0.1.9 → 0.1.10-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
@@ -35,7 +35,14 @@ var AgentPaymentPhase = {
35
35
  * is no approval that would fix this — the originating Safe needs more
36
36
  * funds or the agent's per-token allowance needs to be raised first.
37
37
  */
38
- InsufficientFunds: "insufficient_funds"
38
+ InsufficientFunds: "insufficient_funds",
39
+ /**
40
+ * Haven's funding leg (Safe → delegate) confirmed on-chain, but the
41
+ * merchant rejected the x402 retry. The delegate wallet may hold stranded
42
+ * USDC that was never settled to the merchant. The agent should stop, tell
43
+ * the user, and wait for the sweep flow to reclaim the funds.
44
+ */
45
+ FundedButUnsettled: "funded_but_unsettled"
39
46
  };
40
47
  var AgentPaymentNextAction = {
41
48
  /** Sign with the delegate key and submit the payment to Haven. */
@@ -59,7 +66,13 @@ var AgentPaymentNextAction = {
59
66
  * the agent's per-token allowance needs to be raised before the payment
60
67
  * can succeed. A user approval will not fix this state on its own.
61
68
  */
62
- FundSafeOrRaiseAllowance: "fund_safe_or_raise_allowance"
69
+ FundSafeOrRaiseAllowance: "fund_safe_or_raise_allowance",
70
+ /**
71
+ * The delegate wallet may hold funds that were sent from the Safe but never
72
+ * settled to the merchant. The wallet owner should initiate a sweep to
73
+ * return those funds to the originating Safe.
74
+ */
75
+ SweepStrandedFunds: "sweep_stranded_funds"
63
76
  };
64
77
  var AgentPaymentRail = {
65
78
  /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
@@ -91,7 +104,8 @@ var AgentPaymentPhaseDescriptions = {
91
104
  [AgentPaymentPhase.Rejected]: "The wallet owner rejected the request; the agent should stop and tell the user.",
92
105
  [AgentPaymentPhase.Expired]: "The payment or approval request expired before completion.",
93
106
  [AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure.",
94
- [AgentPaymentPhase.InsufficientFunds]: "Pre-flight check determined the delegate balance plus the remaining on-chain allowance cannot cover the requested amount, so no payment was created. The originating Safe must be funded or the agent allowance raised before retrying."
107
+ [AgentPaymentPhase.InsufficientFunds]: "Pre-flight check determined the delegate balance plus the remaining on-chain allowance cannot cover the requested amount, so no payment was created. The originating Safe must be funded or the agent allowance raised before retrying.",
108
+ [AgentPaymentPhase.FundedButUnsettled]: "Haven's funding leg confirmed on-chain but the merchant rejected the x402 retry. The delegate wallet may hold stranded funds. The agent should stop and wait for the wallet owner to sweep the stranded funds back to the Safe."
95
109
  };
96
110
  var AgentPaymentNextActionDescriptions = {
97
111
  [AgentPaymentNextAction.SignAndSubmitPayment]: "Sign with the delegate key and submit the payment to Haven.",
@@ -102,7 +116,8 @@ var AgentPaymentNextActionDescriptions = {
102
116
  [AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
103
117
  [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
104
118
  [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
105
- [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."
119
+ [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
+ [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."
106
121
  };
107
122
  var AgentPaymentRailDescriptions = {
108
123
  [AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled Safe through an approved delegate allowance.",
@@ -216,16 +231,52 @@ function verifySignature(hash, signature, expectedAddress) {
216
231
  return false;
217
232
  }
218
233
  }
219
- var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
220
- var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
221
- var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
234
+
235
+ // src/base64.ts
236
+ function normalizeBase64(value) {
237
+ const standard = value.replace(/-/g, "+").replace(/_/g, "/");
238
+ const remainder = standard.length % 4;
239
+ return remainder === 0 ? standard : standard + "=".repeat(4 - remainder);
240
+ }
241
+ function encodeBase64Utf8(value) {
242
+ if (typeof Buffer !== "undefined") {
243
+ return Buffer.from(value, "utf8").toString("base64");
244
+ }
245
+ const bytes = new TextEncoder().encode(value);
246
+ let binary = "";
247
+ for (let i = 0; i < bytes.length; i++) {
248
+ binary += String.fromCharCode(bytes[i]);
249
+ }
250
+ return btoa(binary);
251
+ }
252
+ function decodeBase64Utf8(value) {
253
+ const normalized = normalizeBase64(value);
254
+ if (typeof Buffer !== "undefined") {
255
+ return Buffer.from(normalized, "base64").toString("utf8");
256
+ }
257
+ const binary = atob(normalized);
258
+ const bytes = new Uint8Array(binary.length);
259
+ for (let i = 0; i < binary.length; i++) {
260
+ bytes[i] = binary.charCodeAt(i);
261
+ }
262
+ return new TextDecoder().decode(bytes);
263
+ }
264
+ function encodeBase64Json(value) {
265
+ return encodeBase64Utf8(JSON.stringify(value));
266
+ }
222
267
  function decodeBase64Json(value, label) {
223
268
  try {
224
- return JSON.parse(atob(value));
225
- } catch {
226
- throw new Error(`Failed to decode ${label}`);
269
+ return JSON.parse(decodeBase64Utf8(value));
270
+ } catch (err) {
271
+ if (label) throw new Error(`Failed to decode ${label}`);
272
+ throw err;
227
273
  }
228
274
  }
275
+
276
+ // src/x402.ts
277
+ var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
278
+ var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
279
+ var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
229
280
  function isPositiveDecimalAtomicAmount(value) {
230
281
  return DECIMAL_ATOMIC_AMOUNT_RE.test(value) && BigInt(value) > 0n;
231
282
  }
@@ -433,7 +484,7 @@ function encodePaymentProof(receipt) {
433
484
  chainId: receipt.chainId
434
485
  }
435
486
  };
436
- return btoa(JSON.stringify(payload));
487
+ return encodeBase64Json(payload);
437
488
  }
438
489
  function resolveTokenFromAddress(address, network) {
439
490
  const lower = address.toLowerCase();
@@ -451,13 +502,6 @@ function stableStringify(value) {
451
502
  const object = value;
452
503
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
453
504
  }
454
- function decodeBase64Json2(value, label) {
455
- try {
456
- return JSON.parse(atob(value));
457
- } catch {
458
- throw new Error(`Failed to decode ${label}`);
459
- }
460
- }
461
505
  function normalizeChallenge(value) {
462
506
  const candidate = value;
463
507
  if (!candidate || typeof candidate !== "object" || candidate.rail !== "mpp_demo" || typeof candidate.version !== "string" || typeof candidate.challengeId !== "string" || typeof candidate.resource !== "string" || typeof candidate.description !== "string" || // TODO: relax these checks when non-demo machine payment rails are added.
@@ -484,7 +528,7 @@ function parseMachinePaymentChallenge(response) {
484
528
  throw new Error("No MACHINE-PAYMENT-CHALLENGE header found in 402 response.");
485
529
  }
486
530
  const parsed = normalizeChallenge(
487
- decodeBase64Json2(header, "MACHINE-PAYMENT-CHALLENGE header")
531
+ decodeBase64Json(header, "MACHINE-PAYMENT-CHALLENGE header")
488
532
  );
489
533
  if (!parsed) throw new Error("Invalid machine payment challenge");
490
534
  return parsed;
@@ -515,7 +559,7 @@ function buildMachinePaymentIdempotencyKey(challenge) {
515
559
  return `${challenge.rail}:${createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
516
560
  }
517
561
  function encodeMachinePaymentProof(receipt) {
518
- return btoa(JSON.stringify({
562
+ return encodeBase64Json({
519
563
  rail: receipt.rail,
520
564
  challengeId: receipt.challengeId,
521
565
  paymentId: receipt.paymentId,
@@ -523,7 +567,16 @@ function encodeMachinePaymentProof(receipt) {
523
567
  settledVia: "haven",
524
568
  payer: receipt.payer,
525
569
  chainId: receipt.chainId
526
- }));
570
+ });
571
+ }
572
+ function createJsonRpcProvider(url) {
573
+ return new ethers.JsonRpcProvider(url);
574
+ }
575
+ function createWallet(privateKey, provider) {
576
+ return new ethers.Wallet(privateKey, provider);
577
+ }
578
+ function createErc20Contract(address, abi, signer) {
579
+ return new ethers.Contract(address, abi, signer);
527
580
  }
528
581
 
529
582
  // src/client.ts
@@ -532,6 +585,9 @@ var CHAIN_EXPLORER_TX = {
532
585
  100: "https://gnosisscan.io/tx",
533
586
  8453: "https://basescan.org/tx"
534
587
  };
588
+ var CHAIN_USDC = {
589
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
590
+ };
535
591
  function buildExplorerUrl(chainId, txHash) {
536
592
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
537
593
  return `${base}/${txHash}`;
@@ -542,6 +598,16 @@ function explorerUrlOrEmpty(chainId, txHash) {
542
598
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
543
599
  var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
544
600
  var DEFAULT_POLLING_INTERVAL = 3e3;
601
+ function formatAtomicAmount(atomic, decimals) {
602
+ const s = atomic.toString().padStart(decimals + 1, "0");
603
+ const intPart = s.slice(0, s.length - decimals) || "0";
604
+ const fracPart = s.slice(s.length - decimals).replace(/0+$/, "") || "0";
605
+ return `${intPart}.${fracPart}`;
606
+ }
607
+ var MCP_PROTOCOL_VERSION = "2025-06-18";
608
+ var MCP_ACCEPT = "application/json, text/event-stream";
609
+ var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
610
+ var MERCHANT_BODY_SNIPPET_LIMIT = 1e3;
545
611
  var PAYMENT_STATE_STATUS_CODES = {
546
612
  pending: 202,
547
613
  pending_approval: 202,
@@ -629,6 +695,47 @@ function parseMerchantSettlement(header) {
629
695
  const tx = typeof parsed?.transaction === "string" ? parsed.transaction : typeof parsed?.txHash === "string" ? parsed.txHash : typeof parsed?.tx_hash === "string" ? parsed.tx_hash : null;
630
696
  return { settlementTxHash: tx };
631
697
  }
698
+ function isMcpUrl(url) {
699
+ try {
700
+ return new URL(url).pathname.replace(/\/+$/, "").endsWith("/mcp");
701
+ } catch {
702
+ return /\/mcp(?:[/?#]|$)/.test(url);
703
+ }
704
+ }
705
+ async function responseHasBazaarExtension(response) {
706
+ try {
707
+ const body = await response.clone().json();
708
+ return body?.extensions?.bazaar != null;
709
+ } catch {
710
+ return false;
711
+ }
712
+ }
713
+ function parseSseJsonRpcMessages(text) {
714
+ const messages = [];
715
+ let dataLines = [];
716
+ const flush = () => {
717
+ if (dataLines.length === 0) return;
718
+ try {
719
+ messages.push(JSON.parse(dataLines.join("\n")));
720
+ } catch {
721
+ }
722
+ dataLines = [];
723
+ };
724
+ for (const line of text.split(/\r?\n/)) {
725
+ if (line === "") {
726
+ flush();
727
+ continue;
728
+ }
729
+ if (line.startsWith("data:")) {
730
+ dataLines.push(line.slice(5).replace(/^ /, ""));
731
+ }
732
+ }
733
+ flush();
734
+ return messages;
735
+ }
736
+ function selectJsonRpcResult(messages) {
737
+ return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
738
+ }
632
739
  var HavenClient = class {
633
740
  apiKey;
634
741
  delegateKey;
@@ -637,6 +744,7 @@ var HavenClient = class {
637
744
  requestTimeout;
638
745
  confirmationTimeout;
639
746
  pollingInterval;
747
+ chainRpcs;
640
748
  inFlightX402 = /* @__PURE__ */ new Map();
641
749
  x402ReceiptCache = /* @__PURE__ */ new Map();
642
750
  inFlightMachinePayments = /* @__PURE__ */ new Map();
@@ -653,6 +761,8 @@ var HavenClient = class {
653
761
  * the same time — see their own headers without stepping on each other.
654
762
  */
655
763
  requestContext = new AsyncLocalStorage();
764
+ /** Monotonic JSON-RPC id source for the MCP `initialize` handshake. */
765
+ mcpRequestId = 0;
656
766
  /** Delegate address derived from the private key (if provided) */
657
767
  delegateAddress;
658
768
  constructor(config) {
@@ -663,6 +773,7 @@ var HavenClient = class {
663
773
  this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
664
774
  this.confirmationTimeout = config.confirmationTimeout ?? DEFAULT_CONFIRMATION_TIMEOUT;
665
775
  this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
776
+ this.chainRpcs = config.chainRpcs ?? {};
666
777
  this.defaultHeaders = { ...config.defaultHeaders ?? {} };
667
778
  if (this.delegateKey) {
668
779
  this.delegateAddress = addressFromKey(this.delegateKey);
@@ -858,6 +969,78 @@ var HavenClient = class {
858
969
  chainId: raw.chain_id
859
970
  };
860
971
  }
972
+ /**
973
+ * Sweep stranded USDC and ETH from the delegate EOA back to the originating Safe.
974
+ *
975
+ * The delegate key held by this client signs and submits the transfer transactions
976
+ * directly — Haven's backend never handles the key or constructs signed txs
977
+ * (CASP/MiCA Red Line #2). Funds always go to the Safe linked to this agent.
978
+ *
979
+ * Requires `chainRpcs` to be set for the agent's chain in `HavenClientConfig`.
980
+ */
981
+ async sweepDelegate() {
982
+ if (!this.delegateKey) {
983
+ throw new HavenSigningError("delegateKey is required for sweepDelegate.");
984
+ }
985
+ const agent = await this.getAgent();
986
+ const { safeAddress, delegateAddress, chainId } = agent;
987
+ if (!delegateAddress) {
988
+ throw new HavenApiError("Agent has no delegate address.", 422);
989
+ }
990
+ const rpcUrl = this.chainRpcs[chainId];
991
+ if (!rpcUrl) {
992
+ throw new HavenApiError(
993
+ `chainRpcs[${chainId}] must be configured to sweep the delegate wallet.`,
994
+ 422
995
+ );
996
+ }
997
+ const provider = createJsonRpcProvider(rpcUrl);
998
+ const wallet = createWallet(this.delegateKey, provider);
999
+ const ERC20_TRANSFER_ABI = ["function balanceOf(address) view returns (uint256)", "function transfer(address to, uint256 amount) returns (bool)"];
1000
+ const transfers = [];
1001
+ const usdcAddress = CHAIN_USDC[chainId];
1002
+ if (usdcAddress) {
1003
+ const usdcContract = createErc20Contract(usdcAddress, ERC20_TRANSFER_ABI, wallet);
1004
+ const usdcBalance = await usdcContract.balanceOf(delegateAddress);
1005
+ if (usdcBalance > 0n) {
1006
+ const tx = await usdcContract.transfer(safeAddress, usdcBalance);
1007
+ const receipt = await tx.wait(1);
1008
+ const txHash = receipt?.hash ?? tx.hash;
1009
+ transfers.push({
1010
+ asset: "USDC",
1011
+ amount: formatAtomicAmount(usdcBalance, 6),
1012
+ amountAtomic: usdcBalance.toString(),
1013
+ txHash,
1014
+ explorerUrl: buildExplorerUrl(chainId, txHash)
1015
+ });
1016
+ }
1017
+ }
1018
+ const ethBalance = await provider.getBalance(delegateAddress);
1019
+ if (ethBalance > 0n) {
1020
+ const gasPrice = (await provider.getFeeData()).gasPrice ?? 1000000n;
1021
+ const gasLimit = 21000n;
1022
+ const gasCost = gasPrice * gasLimit;
1023
+ const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1024
+ if (ethToSend > 0n) {
1025
+ const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
1026
+ const receipt = await tx.wait(1);
1027
+ const txHash = receipt?.hash ?? tx.hash;
1028
+ transfers.push({
1029
+ asset: "ETH",
1030
+ amount: formatAtomicAmount(ethToSend, 18),
1031
+ amountAtomic: ethToSend.toString(),
1032
+ txHash,
1033
+ explorerUrl: buildExplorerUrl(chainId, txHash)
1034
+ });
1035
+ }
1036
+ }
1037
+ return {
1038
+ fromAddress: delegateAddress,
1039
+ toAddress: safeAddress,
1040
+ chainId,
1041
+ transfers
1042
+ };
1043
+ }
861
1044
  /**
862
1045
  * Get configured and on-chain allowances for the authenticated agent.
863
1046
  */
@@ -887,6 +1070,36 @@ var HavenClient = class {
887
1070
  }))
888
1071
  };
889
1072
  }
1073
+ /**
1074
+ * Discover payable services from Haven's curated merchant catalog.
1075
+ *
1076
+ * Read-only: returns catalog entries (price, rail, protocol) so an agent
1077
+ * can choose a service and pay it with the regular payment tools in the
1078
+ * same session. Never creates payments or signatures.
1079
+ */
1080
+ async discoverTools(options = {}) {
1081
+ const params = new URLSearchParams();
1082
+ if (options.category) params.set("category", options.category);
1083
+ if (options.rail) params.set("rail", options.rail);
1084
+ const query = params.size > 0 ? `?${params.toString()}` : "";
1085
+ const raw = await this.get(`/catalog${query}`);
1086
+ return raw.entries.map((entry) => ({
1087
+ id: entry.id,
1088
+ name: entry.name,
1089
+ description: entry.description,
1090
+ category: entry.category,
1091
+ resourceUrl: entry.resource_url,
1092
+ rail: entry.rail,
1093
+ protocol: entry.protocol,
1094
+ toolName: entry.tool_name,
1095
+ priceDisplay: entry.price_display,
1096
+ priceAtomic: entry.price_atomic,
1097
+ asset: entry.asset,
1098
+ network: entry.network,
1099
+ status: entry.status,
1100
+ verifiedAt: entry.verified_at
1101
+ }));
1102
+ }
890
1103
  /**
891
1104
  * List recent machine-payment receipts/evidence for bookkeeping.
892
1105
  */
@@ -1044,6 +1257,10 @@ var HavenClient = class {
1044
1257
  if (execResult.status !== "confirmed") {
1045
1258
  this.throwPaymentStateError("x402 payment", execResult);
1046
1259
  }
1260
+ await this.waitForFundingTx(
1261
+ execResult.tx_hash,
1262
+ execResult.chain_id ?? chainIdFromNetwork(option.network)
1263
+ );
1047
1264
  const receipt = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
1048
1265
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
1049
1266
  return receipt;
@@ -1110,16 +1327,33 @@ var HavenClient = class {
1110
1327
  * const data = await response.json()
1111
1328
  * ```
1112
1329
  *
1330
+ * **MCP-over-x402 auto-handshake (issue #315):** when the endpoint is
1331
+ * MCP-shaped — the URL path ends in `/mcp`, or the 402 body carries a
1332
+ * Coinbase Bazaar `extensions.bazaar` block — the SDK runs the MCP
1333
+ * `initialize` handshake, threads the resulting `mcp-session-id`,
1334
+ * `Accept: application/json, text/event-stream`, and `x402-wallet` headers
1335
+ * through every request, and collapses SSE responses to the JSON-RPC
1336
+ * `result`. The caller just passes `(url, { body })` and never sees the
1337
+ * protocol plumbing. A non-MCP server (handshake error / no session id)
1338
+ * falls back to standard x402 behaviour.
1339
+ *
1113
1340
  * Requires `delegateKey` to be set in the client config.
1114
1341
  */
1115
1342
  async fetch(url, init, options = {}) {
1116
- const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
1117
- const response = await globalThis.fetch(url, initialInit);
1118
- if (response.status !== 402) return response;
1343
+ let mcpSessionId;
1344
+ if (isMcpUrl(url)) {
1345
+ mcpSessionId = await this.mcpInitialize(url, init);
1346
+ }
1347
+ let requestInit = this.withX402Wallet(init, this.x402PayerAddress());
1348
+ if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
1349
+ const response = await globalThis.fetch(url, requestInit);
1350
+ if (response.status !== 402) {
1351
+ return mcpSessionId ? this.surfaceMcpResult(response) : response;
1352
+ }
1119
1353
  const machineChallengeHeader = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
1120
1354
  if (machineChallengeHeader) {
1121
1355
  const challenge = await parseMachinePaymentChallengeResponse(response);
1122
- return this.fetchWithMachinePayment(url, initialInit, challenge);
1356
+ return this.fetchWithMachinePayment(url, requestInit, challenge);
1123
1357
  }
1124
1358
  let paymentRequired;
1125
1359
  try {
@@ -1131,9 +1365,13 @@ var HavenClient = class {
1131
1365
  } catch {
1132
1366
  return response;
1133
1367
  }
1134
- return this.fetchWithMachinePayment(url, initialInit, challenge);
1368
+ return this.fetchWithMachinePayment(url, requestInit, challenge);
1135
1369
  }
1136
- const request = this.snapshotX402Request(url, initialInit);
1370
+ if (!mcpSessionId && await responseHasBazaarExtension(response)) {
1371
+ mcpSessionId = await this.mcpInitialize(url, init);
1372
+ if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
1373
+ }
1374
+ const request = this.snapshotX402Request(url, requestInit);
1137
1375
  const option = selectStandardPaymentOption(paymentRequired.accepts);
1138
1376
  const idempotencyKey = options.idempotencyKey ?? (option ? buildX402IdempotencyKey(paymentRequired, option) : void 0);
1139
1377
  let receipt;
@@ -1151,7 +1389,122 @@ var HavenClient = class {
1151
1389
  }
1152
1390
  throw err;
1153
1391
  }
1154
- return this.retryX402Request(url, initialInit, paymentRequired, receipt);
1392
+ const retryResponse = await this.retryX402Request(url, requestInit, paymentRequired, receipt);
1393
+ return mcpSessionId ? this.surfaceMcpResult(retryResponse) : retryResponse;
1394
+ }
1395
+ // ── MCP-over-x402 transport helpers (issue #315) ─────────────────
1396
+ /**
1397
+ * Run the MCP `initialize` handshake against a Streamable-HTTP endpoint and
1398
+ * return the `mcp-session-id` the server assigns.
1399
+ *
1400
+ * Returns `undefined` whenever the endpoint is not actually an MCP server —
1401
+ * a transport/HTTP error, a missing session id, or a JSON-RPC error in the
1402
+ * handshake response — so the caller can fall back to plain x402.
1403
+ */
1404
+ async mcpInitialize(url, init) {
1405
+ try {
1406
+ const headers = new Headers(init?.headers);
1407
+ headers.set("Content-Type", "application/json");
1408
+ headers.set("Accept", MCP_ACCEPT);
1409
+ const wallet = this.x402PayerAddress();
1410
+ if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1411
+ const response = await globalThis.fetch(url, {
1412
+ method: "POST",
1413
+ headers,
1414
+ body: JSON.stringify({
1415
+ jsonrpc: "2.0",
1416
+ id: ++this.mcpRequestId,
1417
+ method: "initialize",
1418
+ params: {
1419
+ protocolVersion: MCP_PROTOCOL_VERSION,
1420
+ capabilities: {},
1421
+ clientInfo: MCP_CLIENT_INFO
1422
+ }
1423
+ })
1424
+ });
1425
+ if (!response.ok) return void 0;
1426
+ const sessionId = response.headers.get("mcp-session-id");
1427
+ if (!sessionId) return void 0;
1428
+ const message = await this.readMcpMessage(response);
1429
+ if (message && "error" in message) return void 0;
1430
+ await this.mcpNotifyInitialized(url, init, sessionId);
1431
+ return sessionId;
1432
+ } catch {
1433
+ return void 0;
1434
+ }
1435
+ }
1436
+ /**
1437
+ * Send the MCP `notifications/initialized` notification that completes the
1438
+ * lifecycle handshake. Best-effort: the session is already established, so a
1439
+ * failed notification must not abort the payment.
1440
+ */
1441
+ async mcpNotifyInitialized(url, init, sessionId) {
1442
+ try {
1443
+ const headers = new Headers(init?.headers);
1444
+ headers.set("Content-Type", "application/json");
1445
+ headers.set("Accept", MCP_ACCEPT);
1446
+ headers.set("mcp-session-id", sessionId);
1447
+ const wallet = this.x402PayerAddress();
1448
+ if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1449
+ await globalThis.fetch(url, {
1450
+ method: "POST",
1451
+ headers,
1452
+ body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
1453
+ });
1454
+ } catch {
1455
+ }
1456
+ }
1457
+ /** Read a single JSON-RPC message from an MCP response (JSON or SSE body). */
1458
+ async readMcpMessage(response) {
1459
+ let text;
1460
+ try {
1461
+ text = await response.clone().text();
1462
+ } catch {
1463
+ return void 0;
1464
+ }
1465
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
1466
+ return selectJsonRpcResult(parseSseJsonRpcMessages(text));
1467
+ }
1468
+ try {
1469
+ return JSON.parse(text);
1470
+ } catch {
1471
+ return void 0;
1472
+ }
1473
+ }
1474
+ /** Add the MCP transport headers (session id + SSE Accept) to a request. */
1475
+ withMcpHeaders(init, sessionId) {
1476
+ const headers = new Headers(init?.headers);
1477
+ headers.set("mcp-session-id", sessionId);
1478
+ headers.set("Accept", MCP_ACCEPT);
1479
+ return { ...init, headers };
1480
+ }
1481
+ /**
1482
+ * Collapse an MCP SSE response into a plain JSON response carrying the
1483
+ * JSON-RPC `result`, so callers of `fetch()` never see raw SSE framing.
1484
+ * Non-SSE responses pass through untouched.
1485
+ */
1486
+ async surfaceMcpResult(response) {
1487
+ if (!(response.headers.get("content-type") ?? "").includes("text/event-stream")) {
1488
+ return response;
1489
+ }
1490
+ let text;
1491
+ try {
1492
+ text = await response.clone().text();
1493
+ } catch {
1494
+ return response;
1495
+ }
1496
+ const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
1497
+ if (!message) return response;
1498
+ const body = "result" in message ? message.result : message;
1499
+ const headers = new Headers(response.headers);
1500
+ headers.set("content-type", "application/json");
1501
+ headers.delete("content-length");
1502
+ headers.delete("mcp-session-id");
1503
+ return new Response(JSON.stringify(body), {
1504
+ status: response.status,
1505
+ statusText: response.statusText,
1506
+ headers
1507
+ });
1155
1508
  }
1156
1509
  /**
1157
1510
  * Probe a paid MPP endpoint or inspect an existing challenge without creating
@@ -1210,12 +1563,13 @@ var HavenClient = class {
1210
1563
  headers: retryHeaders
1211
1564
  });
1212
1565
  if (!retryResponse.ok) {
1566
+ const merchant = await captureMerchantResponse(retryResponse);
1213
1567
  await this.recordMerchantRetryRejected({
1214
1568
  rail: "x402",
1215
1569
  paymentId: receipt.paymentId,
1216
1570
  txHash: receipt.txHash,
1217
1571
  resourceUrl: receipt.resourceUrl,
1218
- retryResponse,
1572
+ merchant,
1219
1573
  details: {
1220
1574
  merchant_to: receipt.merchantTo,
1221
1575
  delegate_to: receipt.to
@@ -1223,14 +1577,15 @@ var HavenClient = class {
1223
1577
  });
1224
1578
  throw new HavenApiError(
1225
1579
  "x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
1226
- retryResponse.status,
1580
+ merchant.merchant_status,
1227
1581
  {
1228
1582
  marker: "x402_retry_rejected_after_funding",
1229
1583
  payment_id: receipt.paymentId,
1230
1584
  tx_hash: receipt.txHash,
1231
1585
  resource_url: receipt.resourceUrl,
1232
1586
  merchant_to: receipt.merchantTo,
1233
- delegate_to: receipt.to
1587
+ delegate_to: receipt.to,
1588
+ ...merchant
1234
1589
  }
1235
1590
  );
1236
1591
  }
@@ -1363,25 +1718,27 @@ var HavenClient = class {
1363
1718
  headers: retryHeaders
1364
1719
  });
1365
1720
  if (!retryResponse.ok) {
1721
+ const merchant = await captureMerchantResponse(retryResponse);
1366
1722
  await this.recordMerchantRetryRejected({
1367
1723
  rail: receipt.rail,
1368
1724
  paymentId: receipt.paymentId,
1369
1725
  txHash: receipt.txHash,
1370
1726
  resourceUrl: receipt.resourceUrl,
1371
- retryResponse,
1727
+ merchant,
1372
1728
  details: {
1373
1729
  challenge_id: receipt.challengeId
1374
1730
  }
1375
1731
  });
1376
1732
  throw new HavenApiError(
1377
1733
  "Machine payment retry failed after Haven sent the payment.",
1378
- retryResponse.status,
1734
+ merchant.merchant_status,
1379
1735
  {
1380
1736
  marker: "machine_payment_retry_rejected_after_payment",
1381
1737
  payment_id: receipt.paymentId,
1382
1738
  tx_hash: receipt.txHash,
1383
1739
  resource_url: receipt.resourceUrl,
1384
- rail: receipt.rail
1740
+ rail: receipt.rail,
1741
+ ...merchant
1385
1742
  }
1386
1743
  );
1387
1744
  }
@@ -1617,12 +1974,12 @@ var HavenClient = class {
1617
1974
  requirements
1618
1975
  );
1619
1976
  if (paymentRequired.x402Version < 2) return header;
1620
- const payment = decodeBase64Json3(header);
1621
- return btoa(JSON.stringify({
1977
+ const payment = decodeBase64Json(header);
1978
+ return encodeBase64Json({
1622
1979
  x402Version: paymentRequired.x402Version,
1623
1980
  accepted: option,
1624
1981
  payload: payment.payload
1625
- }));
1982
+ });
1626
1983
  }
1627
1984
  cacheX402Receipt(idempotencyKey, paymentHeader, receipt) {
1628
1985
  const expiresAt = getPaymentHeaderValidBefore(paymentHeader);
@@ -1684,11 +2041,11 @@ var HavenClient = class {
1684
2041
  rail: input.rail,
1685
2042
  eventType: "merchant_retry_rejected_after_payment",
1686
2043
  txHash: input.txHash,
1687
- reason: `Merchant returned HTTP ${input.retryResponse.status} after Haven payment confirmation`,
2044
+ reason: `Merchant returned HTTP ${input.merchant.merchant_status} after Haven payment confirmation`,
1688
2045
  details: {
1689
2046
  resource_url: input.resourceUrl,
1690
- retry_status: input.retryResponse.status,
1691
- retry_body: await responseSnippet(input.retryResponse),
2047
+ retry_status: input.merchant.merchant_status,
2048
+ retry_body: input.merchant.merchant_body.slice(0, MERCHANT_BODY_SNIPPET_LIMIT) || null,
1692
2049
  ...input.details
1693
2050
  }
1694
2051
  });
@@ -1714,6 +2071,29 @@ var HavenClient = class {
1714
2071
  } catch {
1715
2072
  }
1716
2073
  }
2074
+ /**
2075
+ * Wait for a funding tx to be mined with ≥1 confirmation before the
2076
+ * merchant retry, eliminating the race where the merchant's
2077
+ * `balanceOf(delegate)` runs before the funding block propagates.
2078
+ *
2079
+ * Skipped when `chainRpcs` does not include the chain; in that case Haven's
2080
+ * backend has already confirmed on-chain submission and callers accept the
2081
+ * small propagation window as a trade-off for not configuring an RPC URL.
2082
+ */
2083
+ async waitForFundingTx(txHash, chainId, timeoutMs = 3e4) {
2084
+ if (!txHash || !chainId) return;
2085
+ const rpcUrl = this.chainRpcs[chainId];
2086
+ if (!rpcUrl) return;
2087
+ const provider = createJsonRpcProvider(rpcUrl);
2088
+ const onChainReceipt = await provider.waitForTransaction(txHash, 1, timeoutMs);
2089
+ if (!onChainReceipt || onChainReceipt.status !== 1) {
2090
+ throw new HavenApiError(
2091
+ "Funding tx did not confirm on-chain within the timeout window.",
2092
+ 500,
2093
+ { txHash, chainId }
2094
+ );
2095
+ }
2096
+ }
1717
2097
  throwIfNonSignableAuthorizationState(label, raw) {
1718
2098
  if (raw.status === "pending_signature") return;
1719
2099
  this.throwPaymentStateError(label, raw);
@@ -2299,7 +2679,7 @@ function sleep(ms) {
2299
2679
  }
2300
2680
  function getPaymentHeaderValidBefore(paymentHeader) {
2301
2681
  try {
2302
- const payment = decodeBase64Json3(
2682
+ const payment = decodeBase64Json(
2303
2683
  paymentHeader
2304
2684
  );
2305
2685
  const payload = payment.payload;
@@ -2309,12 +2689,9 @@ function getPaymentHeaderValidBefore(paymentHeader) {
2309
2689
  }
2310
2690
  return 0;
2311
2691
  }
2312
- function decodeBase64Json3(value) {
2313
- return JSON.parse(atob(value));
2314
- }
2315
2692
  function parseProtocolReceiptHeader(value) {
2316
2693
  try {
2317
- return JSON.parse(atob(value));
2694
+ return decodeBase64Json(value);
2318
2695
  } catch {
2319
2696
  try {
2320
2697
  return JSON.parse(value);
@@ -2323,13 +2700,14 @@ function parseProtocolReceiptHeader(value) {
2323
2700
  }
2324
2701
  }
2325
2702
  }
2326
- async function responseSnippet(response) {
2327
- try {
2328
- const text = await response.clone().text();
2329
- return text.slice(0, 1e3) || null;
2330
- } catch {
2331
- return null;
2332
- }
2703
+ async function captureMerchantResponse(response) {
2704
+ const merchant_body = await response.text().catch(() => "");
2705
+ return {
2706
+ merchant_status: response.status,
2707
+ merchant_status_text: response.statusText,
2708
+ merchant_headers: Object.fromEntries(response.headers.entries()),
2709
+ merchant_body
2710
+ };
2333
2711
  }
2334
2712
 
2335
2713
  // src/tool-descriptions.ts
@@ -2401,6 +2779,30 @@ var toolDescriptions = {
2401
2779
  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.",
2402
2780
  behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
2403
2781
  nextActionGuidance: ""
2782
+ },
2783
+ payMcpTool: {
2784
+ summary: "Call a named tool on an MCP merchant that requires an x402 payment, handling the full initialize \u2192 pay \u2192 retry round trip.",
2785
+ selectionGuidance: "Use this when the agent wants to call a specific tool on an MCP merchant (e.g. Soundside, Coinbase Bazaar) and payment is required. Prefer this over haven_pay_x402 when you know the merchant_url and tool_name \u2014 it builds the JSON-RPC envelope internally. Use haven_pay_x402 for arbitrary HTTP resources. Do NOT use for read-only allowance or budget questions \u2014 use haven_get_allowances.",
2786
+ behavior: "Builds the JSON-RPC tools/call envelope, runs the MCP Streamable-HTTP initialize handshake automatically (if the endpoint is MCP-shaped), pays any HTTP 402 x402 challenge through Haven's AllowanceModule path, and retries the request. Returns the JSON-RPC result (the actual merchant output) on success. Amounts within the on-chain allowance execute automatically; over-allowance transfers are queued as pending_approval.",
2787
+ nextActionGuidance: "If pending_approval is returned, preserve payment_id and resume_state and wait for the wallet owner to approve in Haven. Use haven_resume_x402_payment once nextAction=retry_original_x402_request."
2788
+ },
2789
+ discoverTools: {
2790
+ summary: "Discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use.",
2791
+ 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)."
2794
+ },
2795
+ sweep_delegate: {
2796
+ summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
2797
+ selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402 or haven_pay_mpp_challenge. Do NOT use to read balances only \u2014 use haven_get_allowances.",
2798
+ behavior: "Reads the delegate EOA's on-chain USDC and ETH balances. For each non-zero balance, signs and submits a transfer from the delegate EOA to the originating Safe (hardcoded destination). The delegate key signs locally \u2014 Haven never sees it and the backend never constructs signed transactions (CASP/MiCA Red Line #2). Returns tx hashes and recovered amounts. Returns an empty transfers list when nothing is stranded.",
2799
+ nextActionGuidance: "If transfers is non-empty, confirm the amounts with the user. No further action required \u2014 funds are on their way back to the Safe."
2800
+ },
2801
+ send: {
2802
+ summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
2803
+ selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints (use haven_pay_x402 instead) or MPP merchant payments (use haven_pay_mpp_challenge). Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances.",
2804
+ behavior: "Sends the requested amount through the Safe AllowanceModule. Amounts within the remaining on-chain allowance for the asset execute automatically; amounts that exceed the allowance are queued as pending_approval for the wallet owner to approve in Haven. The agent's signing key signs the AllowanceModule transfer hash; Haven never receives the key.",
2805
+ nextActionGuidance: "If pending_approval is returned, preserve the payment_id and wait for the wallet owner to approve in Haven. Poll haven_get_payment_status until nextAction=none."
2404
2806
  }
2405
2807
  };
2406
2808
 
@@ -2530,6 +2932,12 @@ var GET_ALLOWANCES_DESCRIPTION = composeDescription(toolDescriptions.getAllowanc
2530
2932
  var AUTHORIZE_X402_DESCRIPTION = composeDescription(toolDescriptions.payX402) + " In this SDK tool set, the allowance lookup tool is get_allowances. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, preserve the original merchant/MCP session and x402 details, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not start a new merchant session or loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
2531
2933
  var RESUME_X402_DESCRIPTION = toolDescriptions.resumeX402.summary + " Use this only after get_payment_status returns next_action=retry_original_x402_request. It checks the approved payment, validates the original x402 details, and returns a merchant X-PAYMENT header without creating a new approval request or merchant session.";
2532
2934
  var AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION = composeDescription(toolDescriptions.payMpp) + " In this SDK tool set, the allowance lookup tool is get_allowances. Currently scoped to the internal MPP demo rail. The agent signs the payment, Haven relays it within the on-chain allowance, and the tool returns a proof header for the retry request.";
2935
+ var SWEEP_DELEGATE_DESCRIPTION = composeDescription(toolDescriptions.sweep_delegate);
2936
+ var sweepDelegateSchema = {
2937
+ type: "object",
2938
+ properties: {},
2939
+ required: []
2940
+ };
2533
2941
  function claudeTools() {
2534
2942
  return [
2535
2943
  {
@@ -2561,6 +2969,11 @@ function claudeTools() {
2561
2969
  name: "authorize_machine_payment",
2562
2970
  description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
2563
2971
  input_schema: authorizeMachinePaymentSchema
2972
+ },
2973
+ {
2974
+ name: "haven_sweep_delegate",
2975
+ description: SWEEP_DELEGATE_DESCRIPTION,
2976
+ input_schema: sweepDelegateSchema
2564
2977
  }
2565
2978
  ];
2566
2979
  }
@@ -2613,6 +3026,14 @@ function openaiTools() {
2613
3026
  description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
2614
3027
  parameters: authorizeMachinePaymentSchema
2615
3028
  }
3029
+ },
3030
+ {
3031
+ type: "function",
3032
+ function: {
3033
+ name: "haven_sweep_delegate",
3034
+ description: SWEEP_DELEGATE_DESCRIPTION,
3035
+ parameters: sweepDelegateSchema
3036
+ }
2616
3037
  }
2617
3038
  ];
2618
3039
  }
@@ -2623,6 +3044,75 @@ var havenTools = {
2623
3044
  openai: openaiTools
2624
3045
  };
2625
3046
 
2626
- export { AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, addressFromKey, buildMachinePaymentIdempotencyKey, buildX402ExpectedMessage, composeDescription, encodeMachinePaymentProof, encodePaymentProof, havenTools, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, toStandardPaymentRequirements, toolDescriptions, verifySignature, x402AuthorizationAmount };
3047
+ // src/skill-content.ts
3048
+ var HAVEN_SKILL_MD = `---
3049
+ name: haven-pay
3050
+ description: Pay for things from the user's Haven wallet within their agent rules. Use when the user asks to send, pay, tip, or transfer crypto \u2014 or when a request hits an HTTP 402 (x402) paywall.
3051
+ ---
3052
+
3053
+ # Haven: pay from a Haven wallet
3054
+
3055
+ This skill lets the agent make payments from the user's Haven wallet through
3056
+ the Haven MCP tools. Every payment is checked against the agent's on-chain
3057
+ budget before money moves; payments above the remaining budget wait for the
3058
+ user's approval in Haven.
3059
+
3060
+ ## When to use this skill
3061
+
3062
+ - The user asks to send money, pay someone, tip, donate, or transfer tokens.
3063
+ - A request returns HTTP 402 (x402): use the Haven pay tools to settle it,
3064
+ then retry the original request.
3065
+
3066
+ ## Identity and budget come from the tools \u2014 never assume them
3067
+
3068
+ Do not guess the wallet address, network, or budget. Read them live:
3069
+
3070
+ - \`haven_get_agent\` \u2014 agent identity, Haven wallet address, network.
3071
+ - \`haven_get_allowances\` \u2014 current per-token budgets and what remains.
3072
+
3073
+ Budgets reset on a period the user chose. If a payment exceeds the remaining
3074
+ budget it is queued for the user to approve in the Haven dashboard \u2014 this is
3075
+ normal, not an error.
3076
+
3077
+ ## Paying
3078
+
3079
+ - **Direct transfer:** \`haven_pay\` with recipient, amount, and token.
3080
+ - **x402 paywall:** \`haven_quote_x402\` to get a quote, then
3081
+ \`haven_pay_x402_quote\`. In the hosted setup the signing step happens in
3082
+ the local Haven signer; follow the tool results \u2014 they tell you the next
3083
+ action at every step. Retry the original request only when the result says
3084
+ \`retry_original_x402_request\`.
3085
+ - **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
3086
+ queued or in-flight payments. Do not poll in a tight loop.
3087
+
3088
+ ## Approval semantics
3089
+
3090
+ - A result with \`pending_approval\` means the payment exceeded the remaining
3091
+ budget and is waiting for the user in Haven. Tell the user, then check
3092
+ 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.
3096
+
3097
+ ## Failure handling
3098
+
3099
+ Haven errors are shaped \`{ error, status, details? }\` and written for
3100
+ humans \u2014 surface the message verbatim. Common cases:
3101
+
3102
+ - \`pending_approval\`: queued for the user's approval (see above).
3103
+ - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
3104
+ Suggest the user add funds in the Haven dashboard.
3105
+ - Budget exceeded: tell the user how much remains (from
3106
+ \`haven_get_allowances\`) and that they can raise the budget in Haven.
3107
+
3108
+ ## Revoke
3109
+
3110
+ If this agent's credential may have leaked, tell the user to pause or revoke
3111
+ the agent in the Haven dashboard under Agents. New requests stop immediately
3112
+ for that credential.
3113
+ `;
3114
+ var SKILL_FOLDER_NAME = "haven-pay";
3115
+
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 };
2627
3117
  //# sourceMappingURL=index.js.map
2628
3118
  //# sourceMappingURL=index.js.map