@haven_ai/sdk 0.1.17-alpha.0 → 0.1.19-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
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'async_hooks';
2
2
  import { exact } from 'x402/schemes';
3
+ import { hashTypedData } from 'viem';
3
4
  import { privateKeyToAccount } from 'viem/accounts';
4
5
  import { ethers } from 'ethers';
5
6
  import { createHash } from 'crypto';
@@ -240,6 +241,22 @@ function signHash(privateKey, hash) {
240
241
  );
241
242
  }
242
243
  }
244
+ async function signUserOpTypedDataForDelegation(privateKey, typedData) {
245
+ try {
246
+ const wallet = new ethers.Wallet(privateKey);
247
+ const types = { ...typedData.types };
248
+ delete types.EIP712Domain;
249
+ return await wallet.signTypedData(
250
+ typedData.domain,
251
+ types,
252
+ typedData.message
253
+ );
254
+ } catch (err) {
255
+ throw new HavenSigningError(
256
+ `Failed to sign delegation UserOperation: ${err instanceof Error ? err.message : String(err)}`
257
+ );
258
+ }
259
+ }
243
260
  function addressFromKey(privateKey) {
244
261
  try {
245
262
  return new ethers.Wallet(privateKey).address;
@@ -257,6 +274,24 @@ function verifySignature(hash, signature, expectedAddress) {
257
274
  return false;
258
275
  }
259
276
  }
277
+ var RECEIPT_VERSION = "haven-receipt-1";
278
+ function defaultRecover(hash, signature) {
279
+ return ethers.recoverAddress(hash, signature);
280
+ }
281
+ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
282
+ const { delegate, signHash: signHash2, signature } = receipt.authorization;
283
+ if (!signature) return { verified: false, reason: "missing_signature" };
284
+ let recovered;
285
+ try {
286
+ recovered = recover(signHash2, signature);
287
+ } catch {
288
+ return { verified: false, reason: "bad_signature" };
289
+ }
290
+ if (recovered.toLowerCase() !== delegate.toLowerCase()) {
291
+ return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
292
+ }
293
+ return { verified: true, recoveredSigner: recovered };
294
+ }
260
295
 
261
296
  // src/base64.ts
262
297
  function normalizeBase64(value) {
@@ -301,6 +336,8 @@ function decodeBase64Json(value, label) {
301
336
 
302
337
  // src/x402.ts
303
338
  var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
339
+ var BASE_SEPOLIA_USDC_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
340
+ var STANDARD_X402_USDC_ADDRESSES = /* @__PURE__ */ new Set([BASE_USDC_ADDRESS, BASE_SEPOLIA_USDC_ADDRESS]);
304
341
  var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
305
342
  var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
306
343
  function isPositiveDecimalAtomicAmount(value) {
@@ -309,6 +346,12 @@ function isPositiveDecimalAtomicAmount(value) {
309
346
  function optionAuthorizationAmount(option) {
310
347
  return option.maxAmountRequired ?? option.amount;
311
348
  }
349
+ var X402_MAX_AUTHORIZATION_WINDOW_SECONDS = 600;
350
+ var X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = 300;
351
+ function clampAuthorizationWindow(seconds) {
352
+ const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
353
+ return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
354
+ }
312
355
  function normalizePaymentOption(value) {
313
356
  const candidate = value;
314
357
  if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
@@ -330,7 +373,7 @@ function normalizePaymentOption(value) {
330
373
  mimeType: candidate.mimeType,
331
374
  asset: candidate.asset,
332
375
  payTo: candidate.payTo,
333
- maxTimeoutSeconds: candidate.maxTimeoutSeconds ?? 30,
376
+ maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
334
377
  extra: candidate.extra
335
378
  };
336
379
  }
@@ -360,11 +403,15 @@ function normalizePaymentRequired(value) {
360
403
  var SUPPORTED_X402_NETWORKS = {
361
404
  "eip155:100": "Gnosis Chain",
362
405
  "eip155:8453": "Base",
363
- "base": "Base"
406
+ "base": "Base",
407
+ "eip155:84532": "Base Sepolia",
408
+ "base-sepolia": "Base Sepolia"
364
409
  };
365
410
  var STANDARD_X402_NETWORKS = {
366
411
  "eip155:8453": "base",
367
- "base": "base"
412
+ "base": "base",
413
+ "eip155:84532": "base-sepolia",
414
+ "base-sepolia": "base-sepolia"
368
415
  };
369
416
  var GNOSIS_TOKENS = {
370
417
  "0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
@@ -375,14 +422,21 @@ var BASE_TOKENS = {
375
422
  "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
376
423
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
377
424
  };
425
+ var BASE_SEPOLIA_TOKENS = {
426
+ "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
427
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
428
+ };
378
429
  var ALL_TOKENS = {
379
430
  ...GNOSIS_TOKENS,
380
- ...BASE_TOKENS
431
+ ...BASE_TOKENS,
432
+ ...BASE_SEPOLIA_TOKENS
381
433
  };
382
434
  var NETWORK_TOKENS = {
383
435
  "eip155:100": GNOSIS_TOKENS,
384
436
  "eip155:8453": BASE_TOKENS,
385
- "base": BASE_TOKENS
437
+ "base": BASE_TOKENS,
438
+ "eip155:84532": BASE_SEPOLIA_TOKENS,
439
+ "base-sepolia": BASE_SEPOLIA_TOKENS
386
440
  };
387
441
  function parsePaymentRequired(response) {
388
442
  const v2Header = response.headers.get("PAYMENT-REQUIRED");
@@ -436,7 +490,7 @@ function selectPaymentOption(accepts) {
436
490
  function selectStandardPaymentOption(accepts) {
437
491
  if (!accepts || accepts.length === 0) return null;
438
492
  for (const opt of accepts) {
439
- if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase() === BASE_USDC_ADDRESS && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
493
+ if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
440
494
  return opt;
441
495
  }
442
496
  }
@@ -450,8 +504,9 @@ function x402AuthorizationAmount(option) {
450
504
  return amount;
451
505
  }
452
506
  function buildX402ExpectedMessage(context) {
507
+ const version = context.typedDataHash ? 2 : 1;
453
508
  const payload = {
454
- version: 1,
509
+ version,
455
510
  kind: "haven.x402.expected",
456
511
  paymentId: context.paymentId,
457
512
  payloadHash: context.payloadHash.toLowerCase(),
@@ -464,7 +519,10 @@ function buildX402ExpectedMessage(context) {
464
519
  if (context.expiresAt) {
465
520
  payload.expiresAt = context.expiresAt;
466
521
  }
467
- return `Haven x402 expected context v1
522
+ if (context.typedDataHash) {
523
+ payload.typedDataHash = context.typedDataHash.toLowerCase();
524
+ }
525
+ return `Haven x402 expected context v${version}
468
526
  ${stableStringify(payload)}`;
469
527
  }
470
528
  function toStandardPaymentRequirements(paymentRequired, option) {
@@ -484,7 +542,15 @@ function toStandardPaymentRequirements(paymentRequired, option) {
484
542
  mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
485
543
  payTo: option.payTo,
486
544
  asset: option.asset,
487
- maxTimeoutSeconds: option.maxTimeoutSeconds,
545
+ // Second enforcement point (#715): the parse path clamps too, but this is
546
+ // the last stop before the x402 library turns the timeout into
547
+ // `validBefore` — options constructed without parsing are bounded here.
548
+ // The forward margin (#1256) is added ONLY here, at signing: the parse
549
+ // path keeps recording the merchant's advertised timeout unchanged, and
550
+ // the library's `validBefore = now + this value` then carries enough
551
+ // slack to satisfy the facilitator's `validBefore ≥ now + maxTimeout`
552
+ // verify rule after our funding leg confirms.
553
+ maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds) + X402_SETTLEMENT_FORWARD_MARGIN_SECONDS,
488
554
  extra: option.extra
489
555
  };
490
556
  }
@@ -618,7 +684,8 @@ var CHAIN_EXPLORER_TX = {
618
684
  8453: "https://basescan.org/tx"
619
685
  };
620
686
  var CHAIN_USDC = {
621
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
687
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
688
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
622
689
  };
623
690
  function buildExplorerUrl(chainId, txHash) {
624
691
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
@@ -781,6 +848,16 @@ function parseSseJsonRpcMessages(text) {
781
848
  function selectJsonRpcResult(messages) {
782
849
  return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
783
850
  }
851
+ function x402TypedDataDigest(typedData) {
852
+ if (!typedData || typeof typedData !== "object") return void 0;
853
+ try {
854
+ return hashTypedData(typedData);
855
+ } catch (err) {
856
+ throw new HavenSigningError(
857
+ `The x402 funding intent carried a sign_data.typed_data that is not a valid EIP-712 payload (needs domain, types, primaryType, message), so its digest cannot be derived and no signer could accept it. Underlying error: ${err instanceof Error ? err.message : String(err)}`
858
+ );
859
+ }
860
+ }
784
861
  var HavenClient = class {
785
862
  apiKey;
786
863
  delegateKey;
@@ -930,6 +1007,11 @@ var HavenClient = class {
930
1007
  if (!raw.sign_data?.hash) {
931
1008
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
932
1009
  }
1010
+ if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
1011
+ throw new HavenSigningError(
1012
+ `This account's x402 funding intent declares signature scheme '${raw.sign_data.signature_scheme}' but carried no typed_data to sign. Refusing to fall back to the bare hash \u2014 the account would reject that signature on-chain. This is a backend contract violation; report it rather than working around it.`
1013
+ );
1014
+ }
933
1015
  if (!raw.x402_expected_auth) {
934
1016
  throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
935
1017
  }
@@ -946,6 +1028,14 @@ var HavenClient = class {
946
1028
  asset: option.asset,
947
1029
  network: option.network,
948
1030
  expectedAuth: raw.x402_expected_auth,
1031
+ // #1138: the digest the delegation-rail expected context commits to.
1032
+ // Re-derived locally, exactly like every other context field the edge
1033
+ // signer is handed (amount, merchantTo, …) — none of them are trusted
1034
+ // because they arrived, they are trusted because the reconstructed
1035
+ // message has to match Haven's signature over it. A typed_data altered in
1036
+ // transit therefore fails message equality and is refused, and the signer
1037
+ // re-derives this digest a second time from the payload it actually signs.
1038
+ expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
949
1039
  fundingTo
950
1040
  };
951
1041
  }
@@ -969,6 +1059,42 @@ var HavenClient = class {
969
1059
  }
970
1060
  return signature;
971
1061
  }
1062
+ /**
1063
+ * Sign a payment's `sign_data` with the correct scheme for its rail.
1064
+ *
1065
+ * Dispatching on the server-provided scheme means a caller never has to
1066
+ * know which rail an account is on; an unknown scheme is a hard error,
1067
+ * never a guessed signature. The session rail's 'eip191_userop' is retired
1068
+ * (#834) — the backend refuses those intents with HTTP 410 before any
1069
+ * sign_data reaches a client, so encountering it here is a hard error too.
1070
+ */
1071
+ async signForData(signData) {
1072
+ if (!this.delegateKey) {
1073
+ throw new HavenSigningError(
1074
+ "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
1075
+ );
1076
+ }
1077
+ const scheme = signData.signature_scheme;
1078
+ if (scheme === "eip191_userop") {
1079
+ throw new HavenSigningError(
1080
+ "The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
1081
+ );
1082
+ }
1083
+ if (scheme === "eip712_userop") {
1084
+ if (!signData.typed_data) {
1085
+ throw new HavenSigningError(
1086
+ "sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
1087
+ );
1088
+ }
1089
+ return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
1090
+ }
1091
+ if (scheme === void 0) {
1092
+ return signHash(this.delegateKey, signData.hash);
1093
+ }
1094
+ throw new HavenSigningError(
1095
+ `Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
1096
+ );
1097
+ }
972
1098
  /**
973
1099
  * Step 3: Submit a signature to execute the payment.
974
1100
  *
@@ -1089,9 +1215,10 @@ var HavenClient = class {
1089
1215
  }
1090
1216
  const ethBalance = await provider.getBalance(delegateAddress);
1091
1217
  if (ethBalance > 0n) {
1092
- const gasPrice = (await provider.getFeeData()).gasPrice ?? 1000000n;
1218
+ const fee = await provider.getFeeData();
1219
+ const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
1093
1220
  const gasLimit = 21000n;
1094
- const gasCost = gasPrice * gasLimit;
1221
+ const gasCost = effectiveGasPrice * gasLimit * 2n;
1095
1222
  const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1096
1223
  if (ethToSend > 0n) {
1097
1224
  const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
@@ -1204,6 +1331,18 @@ var HavenClient = class {
1204
1331
  const raw = await this.get(`/machine-payments/receipts${query}`);
1205
1332
  return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
1206
1333
  }
1334
+ /**
1335
+ * Fetch the verifiable receipt bundle for a settled payment and verify it
1336
+ * locally. The server's own verification is ignored — the receipt is verified
1337
+ * here (independently of Haven) by recovering the signer from the
1338
+ * authorisation, so the result is trustworthy even if the backend lied.
1339
+ */
1340
+ async getReceipt(paymentId) {
1341
+ const { receipt } = await this.get(
1342
+ `/payments/${paymentId}/receipt`
1343
+ );
1344
+ return { receipt, verification: verifyPaymentReceipt(receipt) };
1345
+ }
1207
1346
  /**
1208
1347
  * Rehydrate the x402/MPP resume-state bundle for a payment id.
1209
1348
  *
@@ -1346,7 +1485,7 @@ var HavenClient = class {
1346
1485
  if (!raw.sign_data?.hash) {
1347
1486
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1348
1487
  }
1349
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
1488
+ const sig = await this.signForData(raw.sign_data);
1350
1489
  const execResult = await this.post(
1351
1490
  `/payments/${raw.payment_id}/sign`,
1352
1491
  { signature: sig }
@@ -1705,8 +1844,39 @@ var HavenClient = class {
1705
1844
  protocolReceiptHeaderName: "PAYMENT-RESPONSE",
1706
1845
  protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
1707
1846
  });
1847
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1708
1848
  return retryResponse;
1709
1849
  }
1850
+ /**
1851
+ * #956: capture the merchant's OWN receipt when the paid response carries
1852
+ * one, and report it to Haven so the reporting feed can attach it next to
1853
+ * the Haven-generated payment evidence (#498). Two supported signals on the
1854
+ * paid response:
1855
+ *
1856
+ * x-receipt-json: base64-encoded JSON receipt document (inline)
1857
+ * x-receipt-url: https URL to the receipt document (reference)
1858
+ *
1859
+ * Strictly best-effort: absence is the normal case, and no failure here may
1860
+ * ever affect the completed payment — the response is already paid for.
1861
+ */
1862
+ async reportMerchantReceipt(paymentId, response) {
1863
+ try {
1864
+ const inlineB64 = response.headers.get("x-receipt-json");
1865
+ const url = response.headers.get("x-receipt-url");
1866
+ if (!inlineB64 && !url) return;
1867
+ let body = null;
1868
+ if (inlineB64) {
1869
+ if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
1870
+ const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
1871
+ if (decoded && typeof decoded === "object") body = { json: decoded };
1872
+ } else if (url && url.startsWith("https://") && url.length <= 2048) {
1873
+ body = { url };
1874
+ }
1875
+ if (!body) return;
1876
+ await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
1877
+ } catch {
1878
+ }
1879
+ }
1710
1880
  /**
1711
1881
  * Deliver an already-signed x402 payment header to the merchant and return
1712
1882
  * the merchant's response. Used by the hosted MCP server to complete the
@@ -1794,6 +1964,7 @@ var HavenClient = class {
1794
1964
  protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1795
1965
  protocolReceiptHeader
1796
1966
  });
1967
+ await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
1797
1968
  }
1798
1969
  return {
1799
1970
  status: surfaced.status,
@@ -1888,7 +2059,7 @@ var HavenClient = class {
1888
2059
  if (!raw.sign_data?.hash) {
1889
2060
  throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
1890
2061
  }
1891
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
2062
+ const sig = await this.signForData(raw.sign_data);
1892
2063
  const execResult = await this.post(
1893
2064
  `/payments/${raw.payment_id}/sign`,
1894
2065
  { signature: sig }
@@ -1991,6 +2162,7 @@ var HavenClient = class {
1991
2162
  protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
1992
2163
  protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
1993
2164
  });
2165
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1994
2166
  return retryResponse;
1995
2167
  }
1996
2168
  assertCanResumeX402(status, paymentRequired, option) {
@@ -2820,7 +2992,11 @@ var HavenClient = class {
2820
2992
  });
2821
2993
  const data = await res.json();
2822
2994
  if (!res.ok) {
2823
- const message = data.error ?? data.details ?? `API request failed`;
2995
+ const record = data;
2996
+ const errorText = typeof record.error === "string" ? record.error : void 0;
2997
+ const rawDetails = record.details ?? record.detail;
2998
+ const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
2999
+ const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
2824
3000
  throw new HavenApiError(message, res.status, data);
2825
3001
  }
2826
3002
  return data;
@@ -2848,6 +3024,12 @@ var HavenClient = class {
2848
3024
  txHash: raw.tx_hash,
2849
3025
  errorMessage: raw.error_message,
2850
3026
  explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
3027
+ fee: raw.fee ? {
3028
+ amount: raw.fee.amount,
3029
+ token: raw.fee.token,
3030
+ basisPoints: raw.fee.basis_points,
3031
+ applied: raw.fee.applied
3032
+ } : null,
2851
3033
  createdAt: raw.created_at,
2852
3034
  signedAt: raw.signed_at,
2853
3035
  submittedAt: raw.submitted_at,
@@ -2871,6 +3053,12 @@ var HavenClient = class {
2871
3053
  expiresAt: raw.expires_at,
2872
3054
  chainId: raw.chain_id,
2873
3055
  message: raw.message,
3056
+ fee: raw.fee ? {
3057
+ amount: raw.fee.amount,
3058
+ token: raw.fee.token,
3059
+ basisPoints: raw.fee.basis_points,
3060
+ applied: raw.fee.applied
3061
+ } : null,
2874
3062
  amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
2875
3063
  asset: raw.asset ?? raw.x402?.asset ?? null,
2876
3064
  network: raw.network ?? raw.x402?.network ?? null,
@@ -3016,13 +3204,13 @@ var toolDescriptions = {
3016
3204
  getAgent: {
3017
3205
  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
3206
  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.',
3207
+ behavior: 'Reads identity plus the live spend-authority snapshot in one shot \u2014 the on-chain AllowanceModule on the legacy rail, the active budget delegation on the delegation rail. readiness is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. What an over-budget payment does differs by rail: on the legacy AllowanceModule rail it is queued for the wallet owner to approve in Haven; on the delegation rail there is no approval queue \u2014 an over-budget redemption reverts on-chain, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields (id, name, status, safeAddress, delegateAddress, chainId) are unchanged from before.',
3020
3208
  nextActionGuidance: ""
3021
3209
  },
3022
3210
  getAllowances: {
3023
3211
  summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
3024
3212
  selectionGuidance: "Use this when the user asks about allowance, budget, spend limit, remaining amount, remaining allowance, remaining budget, daily limit, reset period, what can I spend, or what the agent can still spend.",
3025
- behavior: "Reads the Safe AllowanceModule snapshot per token (allowance, spent, remaining, reset window). Configured amounts from Haven are returned alongside the on-chain truth.",
3213
+ behavior: "Returns the per-token spend authority for the account's rail: the Safe AllowanceModule snapshot (allowance, spent, remaining, reset window) on the legacy rail, or the active budget delegation (remaining = the period budget; over-budget redemptions revert on-chain, nothing queues) on the delegation rail. Configured amounts from Haven are returned alongside.",
3026
3214
  nextActionGuidance: ""
3027
3215
  },
3028
3216
  listReceipts: {
@@ -3031,6 +3219,12 @@ var toolDescriptions = {
3031
3219
  behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
3032
3220
  nextActionGuidance: ""
3033
3221
  },
3222
+ verifyReceipt: {
3223
+ summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
3224
+ selectionGuidance: "Use this to check a receipt you already hold; it needs no network and does not trust Haven. Use the history tool to fetch receipts in the first place.",
3225
+ behavior: "Recovers the signer from the receipt authorisation and confirms it matches the agent delegate. Returns verified true/false with the recovered signer or a reason. Pure and local \u2014 no backend call.",
3226
+ nextActionGuidance: ""
3227
+ },
3034
3228
  payMcpTool: {
3035
3229
  summary: "Call a named tool on an MCP merchant that requires an x402 payment, handling the full initialize \u2192 pay \u2192 retry round trip.",
3036
3230
  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.",
@@ -3418,20 +3612,72 @@ for that credential.
3418
3612
  `;
3419
3613
  var SKILL_FOLDER_NAME = "haven-pay";
3420
3614
 
3615
+ // src/node-version.ts
3616
+ var HAVEN_MINIMUM_NODE_VERSION = "24.0.0";
3617
+ function parseNodeVersion(value) {
3618
+ const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
3619
+ if (!match) return [0, 0, 0];
3620
+ return [Number(match[1] ?? 0), Number(match[2] ?? 0), Number(match[3] ?? 0)];
3621
+ }
3622
+ function compareNodeVersions(left, right) {
3623
+ const leftParts = parseNodeVersion(left);
3624
+ const rightParts = parseNodeVersion(right);
3625
+ for (let i = 0; i < 3; i += 1) {
3626
+ if (leftParts[i] !== rightParts[i]) return leftParts[i] > rightParts[i] ? 1 : -1;
3627
+ }
3628
+ return 0;
3629
+ }
3630
+ function isSupportedNodeVersion(nodeVersion = process.versions.node, minimumNodeVersion = HAVEN_MINIMUM_NODE_VERSION) {
3631
+ return compareNodeVersions(nodeVersion, minimumNodeVersion) >= 0;
3632
+ }
3633
+ function unsupportedNodeVersionMessage(options) {
3634
+ const nodeVersion = options.nodeVersion ?? process.versions.node;
3635
+ const minimum = options.minimumNodeVersion ?? HAVEN_MINIMUM_NODE_VERSION;
3636
+ const lines = [
3637
+ `${options.subject} requires Node.js >=${minimum}, but this is Node.js ${nodeVersion}.`,
3638
+ "",
3639
+ "Upgrade Node, then try again:",
3640
+ ` nvm install ${major(minimum)} && nvm use ${major(minimum)} (nvm)`,
3641
+ ` fnm install ${major(minimum)} && fnm use ${major(minimum)} (fnm)`,
3642
+ ` volta install node@${major(minimum)} (volta)`,
3643
+ ` or download Node ${major(minimum)} from https://nodejs.org`,
3644
+ "",
3645
+ "If you use a version manager, check that the agent runtime launching Haven picks up the same version \u2014 upgrading your shell does not always change what a desktop app spawns."
3646
+ ];
3647
+ if (options.retryHint) lines.push("", options.retryHint);
3648
+ return lines.join("\n");
3649
+ }
3650
+ function major(version) {
3651
+ return String(parseNodeVersion(version)[0]);
3652
+ }
3653
+
3421
3654
  // src/sweep.ts
3422
3655
  var SWEEP_BASE_CHAIN_ID = 8453;
3656
+ var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
3423
3657
  var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3658
+ var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
3424
3659
  var USDC_EIP712_DOMAIN_BY_CHAIN = {
3425
3660
  [SWEEP_BASE_CHAIN_ID]: {
3426
3661
  name: "USD Coin",
3427
3662
  version: "2",
3428
3663
  chainId: SWEEP_BASE_CHAIN_ID,
3429
3664
  verifyingContract: SWEEP_BASE_USDC_ADDRESS
3665
+ },
3666
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
3667
+ name: "USDC",
3668
+ version: "2",
3669
+ chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
3670
+ verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3430
3671
  }
3431
3672
  };
3432
3673
  var USDC_ADDRESS_BY_CHAIN = {
3433
- [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3674
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
3675
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3434
3676
  };
3677
+ var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
3678
+ function isSweepableChain(chainId) {
3679
+ return chainId in USDC_ADDRESS_BY_CHAIN;
3680
+ }
3435
3681
  var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3436
3682
  TransferWithAuthorization: [
3437
3683
  { name: "from", type: "address" },
@@ -3446,7 +3692,7 @@ function sweepUsdcAddress(chainId) {
3446
3692
  const address = USDC_ADDRESS_BY_CHAIN[chainId];
3447
3693
  if (!address) {
3448
3694
  throw new HavenSigningError(
3449
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3695
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3450
3696
  );
3451
3697
  }
3452
3698
  return address;
@@ -3455,7 +3701,7 @@ function sweepUsdcDomain(chainId) {
3455
3701
  const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3456
3702
  if (!domain) {
3457
3703
  throw new HavenSigningError(
3458
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3704
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3459
3705
  );
3460
3706
  }
3461
3707
  return domain;
@@ -3510,6 +3756,6 @@ function stableStringify2(value) {
3510
3756
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
3511
3757
  }
3512
3758
 
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 };
3759
+ 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_MINIMUM_NODE_VERSION, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, RECEIPT_VERSION, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, isSupportedNodeVersion, isSweepableChain, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, verifyPaymentReceipt, verifySignature, x402AuthorizationAmount };
3514
3760
  //# sourceMappingURL=index.js.map
3515
3761
  //# sourceMappingURL=index.js.map