@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.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var async_hooks = require('async_hooks');
4
4
  var schemes = require('x402/schemes');
5
+ var viem = require('viem');
5
6
  var accounts = require('viem/accounts');
6
7
  var ethers = require('ethers');
7
8
  var crypto = require('crypto');
@@ -242,6 +243,22 @@ function signHash(privateKey, hash) {
242
243
  );
243
244
  }
244
245
  }
246
+ async function signUserOpTypedDataForDelegation(privateKey, typedData) {
247
+ try {
248
+ const wallet = new ethers.ethers.Wallet(privateKey);
249
+ const types = { ...typedData.types };
250
+ delete types.EIP712Domain;
251
+ return await wallet.signTypedData(
252
+ typedData.domain,
253
+ types,
254
+ typedData.message
255
+ );
256
+ } catch (err) {
257
+ throw new HavenSigningError(
258
+ `Failed to sign delegation UserOperation: ${err instanceof Error ? err.message : String(err)}`
259
+ );
260
+ }
261
+ }
245
262
  function addressFromKey(privateKey) {
246
263
  try {
247
264
  return new ethers.ethers.Wallet(privateKey).address;
@@ -259,6 +276,24 @@ function verifySignature(hash, signature, expectedAddress) {
259
276
  return false;
260
277
  }
261
278
  }
279
+ var RECEIPT_VERSION = "haven-receipt-1";
280
+ function defaultRecover(hash, signature) {
281
+ return ethers.ethers.recoverAddress(hash, signature);
282
+ }
283
+ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
284
+ const { delegate, signHash: signHash2, signature } = receipt.authorization;
285
+ if (!signature) return { verified: false, reason: "missing_signature" };
286
+ let recovered;
287
+ try {
288
+ recovered = recover(signHash2, signature);
289
+ } catch {
290
+ return { verified: false, reason: "bad_signature" };
291
+ }
292
+ if (recovered.toLowerCase() !== delegate.toLowerCase()) {
293
+ return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
294
+ }
295
+ return { verified: true, recoveredSigner: recovered };
296
+ }
262
297
 
263
298
  // src/base64.ts
264
299
  function normalizeBase64(value) {
@@ -303,6 +338,8 @@ function decodeBase64Json(value, label) {
303
338
 
304
339
  // src/x402.ts
305
340
  var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
341
+ var BASE_SEPOLIA_USDC_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
342
+ var STANDARD_X402_USDC_ADDRESSES = /* @__PURE__ */ new Set([BASE_USDC_ADDRESS, BASE_SEPOLIA_USDC_ADDRESS]);
306
343
  var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
307
344
  var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
308
345
  function isPositiveDecimalAtomicAmount(value) {
@@ -311,6 +348,12 @@ function isPositiveDecimalAtomicAmount(value) {
311
348
  function optionAuthorizationAmount(option) {
312
349
  return option.maxAmountRequired ?? option.amount;
313
350
  }
351
+ var X402_MAX_AUTHORIZATION_WINDOW_SECONDS = 600;
352
+ var X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = 300;
353
+ function clampAuthorizationWindow(seconds) {
354
+ const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
355
+ return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
356
+ }
314
357
  function normalizePaymentOption(value) {
315
358
  const candidate = value;
316
359
  if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
@@ -332,7 +375,7 @@ function normalizePaymentOption(value) {
332
375
  mimeType: candidate.mimeType,
333
376
  asset: candidate.asset,
334
377
  payTo: candidate.payTo,
335
- maxTimeoutSeconds: candidate.maxTimeoutSeconds ?? 30,
378
+ maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
336
379
  extra: candidate.extra
337
380
  };
338
381
  }
@@ -362,11 +405,15 @@ function normalizePaymentRequired(value) {
362
405
  var SUPPORTED_X402_NETWORKS = {
363
406
  "eip155:100": "Gnosis Chain",
364
407
  "eip155:8453": "Base",
365
- "base": "Base"
408
+ "base": "Base",
409
+ "eip155:84532": "Base Sepolia",
410
+ "base-sepolia": "Base Sepolia"
366
411
  };
367
412
  var STANDARD_X402_NETWORKS = {
368
413
  "eip155:8453": "base",
369
- "base": "base"
414
+ "base": "base",
415
+ "eip155:84532": "base-sepolia",
416
+ "base-sepolia": "base-sepolia"
370
417
  };
371
418
  var GNOSIS_TOKENS = {
372
419
  "0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
@@ -377,14 +424,21 @@ var BASE_TOKENS = {
377
424
  "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
378
425
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
379
426
  };
427
+ var BASE_SEPOLIA_TOKENS = {
428
+ "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
429
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
430
+ };
380
431
  var ALL_TOKENS = {
381
432
  ...GNOSIS_TOKENS,
382
- ...BASE_TOKENS
433
+ ...BASE_TOKENS,
434
+ ...BASE_SEPOLIA_TOKENS
383
435
  };
384
436
  var NETWORK_TOKENS = {
385
437
  "eip155:100": GNOSIS_TOKENS,
386
438
  "eip155:8453": BASE_TOKENS,
387
- "base": BASE_TOKENS
439
+ "base": BASE_TOKENS,
440
+ "eip155:84532": BASE_SEPOLIA_TOKENS,
441
+ "base-sepolia": BASE_SEPOLIA_TOKENS
388
442
  };
389
443
  function parsePaymentRequired(response) {
390
444
  const v2Header = response.headers.get("PAYMENT-REQUIRED");
@@ -438,7 +492,7 @@ function selectPaymentOption(accepts) {
438
492
  function selectStandardPaymentOption(accepts) {
439
493
  if (!accepts || accepts.length === 0) return null;
440
494
  for (const opt of accepts) {
441
- if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase() === BASE_USDC_ADDRESS && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
495
+ if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
442
496
  return opt;
443
497
  }
444
498
  }
@@ -452,8 +506,9 @@ function x402AuthorizationAmount(option) {
452
506
  return amount;
453
507
  }
454
508
  function buildX402ExpectedMessage(context) {
509
+ const version = context.typedDataHash ? 2 : 1;
455
510
  const payload = {
456
- version: 1,
511
+ version,
457
512
  kind: "haven.x402.expected",
458
513
  paymentId: context.paymentId,
459
514
  payloadHash: context.payloadHash.toLowerCase(),
@@ -466,7 +521,10 @@ function buildX402ExpectedMessage(context) {
466
521
  if (context.expiresAt) {
467
522
  payload.expiresAt = context.expiresAt;
468
523
  }
469
- return `Haven x402 expected context v1
524
+ if (context.typedDataHash) {
525
+ payload.typedDataHash = context.typedDataHash.toLowerCase();
526
+ }
527
+ return `Haven x402 expected context v${version}
470
528
  ${stableStringify(payload)}`;
471
529
  }
472
530
  function toStandardPaymentRequirements(paymentRequired, option) {
@@ -486,7 +544,15 @@ function toStandardPaymentRequirements(paymentRequired, option) {
486
544
  mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
487
545
  payTo: option.payTo,
488
546
  asset: option.asset,
489
- maxTimeoutSeconds: option.maxTimeoutSeconds,
547
+ // Second enforcement point (#715): the parse path clamps too, but this is
548
+ // the last stop before the x402 library turns the timeout into
549
+ // `validBefore` — options constructed without parsing are bounded here.
550
+ // The forward margin (#1256) is added ONLY here, at signing: the parse
551
+ // path keeps recording the merchant's advertised timeout unchanged, and
552
+ // the library's `validBefore = now + this value` then carries enough
553
+ // slack to satisfy the facilitator's `validBefore ≥ now + maxTimeout`
554
+ // verify rule after our funding leg confirms.
555
+ maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds) + X402_SETTLEMENT_FORWARD_MARGIN_SECONDS,
490
556
  extra: option.extra
491
557
  };
492
558
  }
@@ -620,7 +686,8 @@ var CHAIN_EXPLORER_TX = {
620
686
  8453: "https://basescan.org/tx"
621
687
  };
622
688
  var CHAIN_USDC = {
623
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
689
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
690
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
624
691
  };
625
692
  function buildExplorerUrl(chainId, txHash) {
626
693
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
@@ -783,6 +850,16 @@ function parseSseJsonRpcMessages(text) {
783
850
  function selectJsonRpcResult(messages) {
784
851
  return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
785
852
  }
853
+ function x402TypedDataDigest(typedData) {
854
+ if (!typedData || typeof typedData !== "object") return void 0;
855
+ try {
856
+ return viem.hashTypedData(typedData);
857
+ } catch (err) {
858
+ throw new HavenSigningError(
859
+ `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)}`
860
+ );
861
+ }
862
+ }
786
863
  var HavenClient = class {
787
864
  apiKey;
788
865
  delegateKey;
@@ -932,6 +1009,11 @@ var HavenClient = class {
932
1009
  if (!raw.sign_data?.hash) {
933
1010
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
934
1011
  }
1012
+ if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
1013
+ throw new HavenSigningError(
1014
+ `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.`
1015
+ );
1016
+ }
935
1017
  if (!raw.x402_expected_auth) {
936
1018
  throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
937
1019
  }
@@ -948,6 +1030,14 @@ var HavenClient = class {
948
1030
  asset: option.asset,
949
1031
  network: option.network,
950
1032
  expectedAuth: raw.x402_expected_auth,
1033
+ // #1138: the digest the delegation-rail expected context commits to.
1034
+ // Re-derived locally, exactly like every other context field the edge
1035
+ // signer is handed (amount, merchantTo, …) — none of them are trusted
1036
+ // because they arrived, they are trusted because the reconstructed
1037
+ // message has to match Haven's signature over it. A typed_data altered in
1038
+ // transit therefore fails message equality and is refused, and the signer
1039
+ // re-derives this digest a second time from the payload it actually signs.
1040
+ expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
951
1041
  fundingTo
952
1042
  };
953
1043
  }
@@ -971,6 +1061,42 @@ var HavenClient = class {
971
1061
  }
972
1062
  return signature;
973
1063
  }
1064
+ /**
1065
+ * Sign a payment's `sign_data` with the correct scheme for its rail.
1066
+ *
1067
+ * Dispatching on the server-provided scheme means a caller never has to
1068
+ * know which rail an account is on; an unknown scheme is a hard error,
1069
+ * never a guessed signature. The session rail's 'eip191_userop' is retired
1070
+ * (#834) — the backend refuses those intents with HTTP 410 before any
1071
+ * sign_data reaches a client, so encountering it here is a hard error too.
1072
+ */
1073
+ async signForData(signData) {
1074
+ if (!this.delegateKey) {
1075
+ throw new HavenSigningError(
1076
+ "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
1077
+ );
1078
+ }
1079
+ const scheme = signData.signature_scheme;
1080
+ if (scheme === "eip191_userop") {
1081
+ throw new HavenSigningError(
1082
+ "The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
1083
+ );
1084
+ }
1085
+ if (scheme === "eip712_userop") {
1086
+ if (!signData.typed_data) {
1087
+ throw new HavenSigningError(
1088
+ "sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
1089
+ );
1090
+ }
1091
+ return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
1092
+ }
1093
+ if (scheme === void 0) {
1094
+ return signHash(this.delegateKey, signData.hash);
1095
+ }
1096
+ throw new HavenSigningError(
1097
+ `Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
1098
+ );
1099
+ }
974
1100
  /**
975
1101
  * Step 3: Submit a signature to execute the payment.
976
1102
  *
@@ -1091,9 +1217,10 @@ var HavenClient = class {
1091
1217
  }
1092
1218
  const ethBalance = await provider.getBalance(delegateAddress);
1093
1219
  if (ethBalance > 0n) {
1094
- const gasPrice = (await provider.getFeeData()).gasPrice ?? 1000000n;
1220
+ const fee = await provider.getFeeData();
1221
+ const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
1095
1222
  const gasLimit = 21000n;
1096
- const gasCost = gasPrice * gasLimit;
1223
+ const gasCost = effectiveGasPrice * gasLimit * 2n;
1097
1224
  const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1098
1225
  if (ethToSend > 0n) {
1099
1226
  const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
@@ -1206,6 +1333,18 @@ var HavenClient = class {
1206
1333
  const raw = await this.get(`/machine-payments/receipts${query}`);
1207
1334
  return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
1208
1335
  }
1336
+ /**
1337
+ * Fetch the verifiable receipt bundle for a settled payment and verify it
1338
+ * locally. The server's own verification is ignored — the receipt is verified
1339
+ * here (independently of Haven) by recovering the signer from the
1340
+ * authorisation, so the result is trustworthy even if the backend lied.
1341
+ */
1342
+ async getReceipt(paymentId) {
1343
+ const { receipt } = await this.get(
1344
+ `/payments/${paymentId}/receipt`
1345
+ );
1346
+ return { receipt, verification: verifyPaymentReceipt(receipt) };
1347
+ }
1209
1348
  /**
1210
1349
  * Rehydrate the x402/MPP resume-state bundle for a payment id.
1211
1350
  *
@@ -1348,7 +1487,7 @@ var HavenClient = class {
1348
1487
  if (!raw.sign_data?.hash) {
1349
1488
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1350
1489
  }
1351
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
1490
+ const sig = await this.signForData(raw.sign_data);
1352
1491
  const execResult = await this.post(
1353
1492
  `/payments/${raw.payment_id}/sign`,
1354
1493
  { signature: sig }
@@ -1707,8 +1846,39 @@ var HavenClient = class {
1707
1846
  protocolReceiptHeaderName: "PAYMENT-RESPONSE",
1708
1847
  protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
1709
1848
  });
1849
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1710
1850
  return retryResponse;
1711
1851
  }
1852
+ /**
1853
+ * #956: capture the merchant's OWN receipt when the paid response carries
1854
+ * one, and report it to Haven so the reporting feed can attach it next to
1855
+ * the Haven-generated payment evidence (#498). Two supported signals on the
1856
+ * paid response:
1857
+ *
1858
+ * x-receipt-json: base64-encoded JSON receipt document (inline)
1859
+ * x-receipt-url: https URL to the receipt document (reference)
1860
+ *
1861
+ * Strictly best-effort: absence is the normal case, and no failure here may
1862
+ * ever affect the completed payment — the response is already paid for.
1863
+ */
1864
+ async reportMerchantReceipt(paymentId, response) {
1865
+ try {
1866
+ const inlineB64 = response.headers.get("x-receipt-json");
1867
+ const url = response.headers.get("x-receipt-url");
1868
+ if (!inlineB64 && !url) return;
1869
+ let body = null;
1870
+ if (inlineB64) {
1871
+ if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
1872
+ const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
1873
+ if (decoded && typeof decoded === "object") body = { json: decoded };
1874
+ } else if (url && url.startsWith("https://") && url.length <= 2048) {
1875
+ body = { url };
1876
+ }
1877
+ if (!body) return;
1878
+ await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
1879
+ } catch {
1880
+ }
1881
+ }
1712
1882
  /**
1713
1883
  * Deliver an already-signed x402 payment header to the merchant and return
1714
1884
  * the merchant's response. Used by the hosted MCP server to complete the
@@ -1796,6 +1966,7 @@ var HavenClient = class {
1796
1966
  protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1797
1967
  protocolReceiptHeader
1798
1968
  });
1969
+ await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
1799
1970
  }
1800
1971
  return {
1801
1972
  status: surfaced.status,
@@ -1890,7 +2061,7 @@ var HavenClient = class {
1890
2061
  if (!raw.sign_data?.hash) {
1891
2062
  throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
1892
2063
  }
1893
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
2064
+ const sig = await this.signForData(raw.sign_data);
1894
2065
  const execResult = await this.post(
1895
2066
  `/payments/${raw.payment_id}/sign`,
1896
2067
  { signature: sig }
@@ -1993,6 +2164,7 @@ var HavenClient = class {
1993
2164
  protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
1994
2165
  protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
1995
2166
  });
2167
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1996
2168
  return retryResponse;
1997
2169
  }
1998
2170
  assertCanResumeX402(status, paymentRequired, option) {
@@ -2822,7 +2994,11 @@ var HavenClient = class {
2822
2994
  });
2823
2995
  const data = await res.json();
2824
2996
  if (!res.ok) {
2825
- const message = data.error ?? data.details ?? `API request failed`;
2997
+ const record = data;
2998
+ const errorText = typeof record.error === "string" ? record.error : void 0;
2999
+ const rawDetails = record.details ?? record.detail;
3000
+ const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
3001
+ const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
2826
3002
  throw new HavenApiError(message, res.status, data);
2827
3003
  }
2828
3004
  return data;
@@ -2850,6 +3026,12 @@ var HavenClient = class {
2850
3026
  txHash: raw.tx_hash,
2851
3027
  errorMessage: raw.error_message,
2852
3028
  explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
3029
+ fee: raw.fee ? {
3030
+ amount: raw.fee.amount,
3031
+ token: raw.fee.token,
3032
+ basisPoints: raw.fee.basis_points,
3033
+ applied: raw.fee.applied
3034
+ } : null,
2853
3035
  createdAt: raw.created_at,
2854
3036
  signedAt: raw.signed_at,
2855
3037
  submittedAt: raw.submitted_at,
@@ -2873,6 +3055,12 @@ var HavenClient = class {
2873
3055
  expiresAt: raw.expires_at,
2874
3056
  chainId: raw.chain_id,
2875
3057
  message: raw.message,
3058
+ fee: raw.fee ? {
3059
+ amount: raw.fee.amount,
3060
+ token: raw.fee.token,
3061
+ basisPoints: raw.fee.basis_points,
3062
+ applied: raw.fee.applied
3063
+ } : null,
2876
3064
  amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
2877
3065
  asset: raw.asset ?? raw.x402?.asset ?? null,
2878
3066
  network: raw.network ?? raw.x402?.network ?? null,
@@ -3018,13 +3206,13 @@ var toolDescriptions = {
3018
3206
  getAgent: {
3019
3207
  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.",
3020
3208
  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.",
3021
- 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.',
3209
+ 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.',
3022
3210
  nextActionGuidance: ""
3023
3211
  },
3024
3212
  getAllowances: {
3025
3213
  summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
3026
3214
  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.",
3027
- behavior: "Reads the Safe AllowanceModule snapshot per token (allowance, spent, remaining, reset window). Configured amounts from Haven are returned alongside the on-chain truth.",
3215
+ 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.",
3028
3216
  nextActionGuidance: ""
3029
3217
  },
3030
3218
  listReceipts: {
@@ -3033,6 +3221,12 @@ var toolDescriptions = {
3033
3221
  behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
3034
3222
  nextActionGuidance: ""
3035
3223
  },
3224
+ verifyReceipt: {
3225
+ summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
3226
+ 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.",
3227
+ 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.",
3228
+ nextActionGuidance: ""
3229
+ },
3036
3230
  payMcpTool: {
3037
3231
  summary: "Call a named tool on an MCP merchant that requires an x402 payment, handling the full initialize \u2192 pay \u2192 retry round trip.",
3038
3232
  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.",
@@ -3420,20 +3614,72 @@ for that credential.
3420
3614
  `;
3421
3615
  var SKILL_FOLDER_NAME = "haven-pay";
3422
3616
 
3617
+ // src/node-version.ts
3618
+ var HAVEN_MINIMUM_NODE_VERSION = "24.0.0";
3619
+ function parseNodeVersion(value) {
3620
+ const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
3621
+ if (!match) return [0, 0, 0];
3622
+ return [Number(match[1] ?? 0), Number(match[2] ?? 0), Number(match[3] ?? 0)];
3623
+ }
3624
+ function compareNodeVersions(left, right) {
3625
+ const leftParts = parseNodeVersion(left);
3626
+ const rightParts = parseNodeVersion(right);
3627
+ for (let i = 0; i < 3; i += 1) {
3628
+ if (leftParts[i] !== rightParts[i]) return leftParts[i] > rightParts[i] ? 1 : -1;
3629
+ }
3630
+ return 0;
3631
+ }
3632
+ function isSupportedNodeVersion(nodeVersion = process.versions.node, minimumNodeVersion = HAVEN_MINIMUM_NODE_VERSION) {
3633
+ return compareNodeVersions(nodeVersion, minimumNodeVersion) >= 0;
3634
+ }
3635
+ function unsupportedNodeVersionMessage(options) {
3636
+ const nodeVersion = options.nodeVersion ?? process.versions.node;
3637
+ const minimum = options.minimumNodeVersion ?? HAVEN_MINIMUM_NODE_VERSION;
3638
+ const lines = [
3639
+ `${options.subject} requires Node.js >=${minimum}, but this is Node.js ${nodeVersion}.`,
3640
+ "",
3641
+ "Upgrade Node, then try again:",
3642
+ ` nvm install ${major(minimum)} && nvm use ${major(minimum)} (nvm)`,
3643
+ ` fnm install ${major(minimum)} && fnm use ${major(minimum)} (fnm)`,
3644
+ ` volta install node@${major(minimum)} (volta)`,
3645
+ ` or download Node ${major(minimum)} from https://nodejs.org`,
3646
+ "",
3647
+ "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."
3648
+ ];
3649
+ if (options.retryHint) lines.push("", options.retryHint);
3650
+ return lines.join("\n");
3651
+ }
3652
+ function major(version) {
3653
+ return String(parseNodeVersion(version)[0]);
3654
+ }
3655
+
3423
3656
  // src/sweep.ts
3424
3657
  var SWEEP_BASE_CHAIN_ID = 8453;
3658
+ var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
3425
3659
  var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3660
+ var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
3426
3661
  var USDC_EIP712_DOMAIN_BY_CHAIN = {
3427
3662
  [SWEEP_BASE_CHAIN_ID]: {
3428
3663
  name: "USD Coin",
3429
3664
  version: "2",
3430
3665
  chainId: SWEEP_BASE_CHAIN_ID,
3431
3666
  verifyingContract: SWEEP_BASE_USDC_ADDRESS
3667
+ },
3668
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
3669
+ name: "USDC",
3670
+ version: "2",
3671
+ chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
3672
+ verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3432
3673
  }
3433
3674
  };
3434
3675
  var USDC_ADDRESS_BY_CHAIN = {
3435
- [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3676
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
3677
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3436
3678
  };
3679
+ var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
3680
+ function isSweepableChain(chainId) {
3681
+ return chainId in USDC_ADDRESS_BY_CHAIN;
3682
+ }
3437
3683
  var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3438
3684
  TransferWithAuthorization: [
3439
3685
  { name: "from", type: "address" },
@@ -3448,7 +3694,7 @@ function sweepUsdcAddress(chainId) {
3448
3694
  const address = USDC_ADDRESS_BY_CHAIN[chainId];
3449
3695
  if (!address) {
3450
3696
  throw new HavenSigningError(
3451
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3697
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3452
3698
  );
3453
3699
  }
3454
3700
  return address;
@@ -3457,7 +3703,7 @@ function sweepUsdcDomain(chainId) {
3457
3703
  const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3458
3704
  if (!domain) {
3459
3705
  throw new HavenSigningError(
3460
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3706
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3461
3707
  );
3462
3708
  }
3463
3709
  return domain;
@@ -3528,6 +3774,7 @@ exports.AgentPaymentPhaseSchema = AgentPaymentPhaseSchema;
3528
3774
  exports.AgentPaymentRail = AgentPaymentRail;
3529
3775
  exports.AgentPaymentRailDescriptions = AgentPaymentRailDescriptions;
3530
3776
  exports.AgentPaymentRailSchema = AgentPaymentRailSchema;
3777
+ exports.HAVEN_MINIMUM_NODE_VERSION = HAVEN_MINIMUM_NODE_VERSION;
3531
3778
  exports.HAVEN_SKILL_MD = HAVEN_SKILL_MD;
3532
3779
  exports.HavenApiError = HavenApiError;
3533
3780
  exports.HavenClient = HavenClient;
@@ -3535,15 +3782,21 @@ exports.HavenError = HavenError;
3535
3782
  exports.HavenPaymentStateError = HavenPaymentStateError;
3536
3783
  exports.HavenSigningError = HavenSigningError;
3537
3784
  exports.HavenTimeoutError = HavenTimeoutError;
3785
+ exports.RECEIPT_VERSION = RECEIPT_VERSION;
3538
3786
  exports.SKILL_FOLDER_NAME = SKILL_FOLDER_NAME;
3539
3787
  exports.SWEEP_BASE_CHAIN_ID = SWEEP_BASE_CHAIN_ID;
3788
+ exports.SWEEP_BASE_SEPOLIA_CHAIN_ID = SWEEP_BASE_SEPOLIA_CHAIN_ID;
3789
+ exports.SWEEP_BASE_SEPOLIA_USDC_ADDRESS = SWEEP_BASE_SEPOLIA_USDC_ADDRESS;
3540
3790
  exports.SWEEP_BASE_USDC_ADDRESS = SWEEP_BASE_USDC_ADDRESS;
3541
3791
  exports.TRANSFER_WITH_AUTHORIZATION_TYPES = TRANSFER_WITH_AUTHORIZATION_TYPES;
3792
+ exports.X402_MAX_AUTHORIZATION_WINDOW_SECONDS = X402_MAX_AUTHORIZATION_WINDOW_SECONDS;
3793
+ exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
3542
3794
  exports.addressFromKey = addressFromKey;
3543
3795
  exports.buildMachinePaymentIdempotencyKey = buildMachinePaymentIdempotencyKey;
3544
3796
  exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
3545
3797
  exports.buildSweepTypedData = buildSweepTypedData;
3546
3798
  exports.buildX402ExpectedMessage = buildX402ExpectedMessage;
3799
+ exports.compareNodeVersions = compareNodeVersions;
3547
3800
  exports.composeDescription = composeDescription;
3548
3801
  exports.decodeBase64Json = decodeBase64Json;
3549
3802
  exports.decodeBase64Utf8 = decodeBase64Utf8;
@@ -3552,6 +3805,8 @@ exports.encodeBase64Utf8 = encodeBase64Utf8;
3552
3805
  exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
3553
3806
  exports.encodePaymentProof = encodePaymentProof;
3554
3807
  exports.havenTools = havenTools;
3808
+ exports.isSupportedNodeVersion = isSupportedNodeVersion;
3809
+ exports.isSweepableChain = isSweepableChain;
3555
3810
  exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
3556
3811
  exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
3557
3812
  exports.parsePaymentRequired = parsePaymentRequired;
@@ -3559,10 +3814,13 @@ exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
3559
3814
  exports.selectPaymentOption = selectPaymentOption;
3560
3815
  exports.selectStandardPaymentOption = selectStandardPaymentOption;
3561
3816
  exports.signHash = signHash;
3817
+ exports.signUserOpTypedDataForDelegation = signUserOpTypedDataForDelegation;
3562
3818
  exports.sweepUsdcAddress = sweepUsdcAddress;
3563
3819
  exports.sweepUsdcDomain = sweepUsdcDomain;
3564
3820
  exports.toStandardPaymentRequirements = toStandardPaymentRequirements;
3565
3821
  exports.toolDescriptions = toolDescriptions;
3822
+ exports.unsupportedNodeVersionMessage = unsupportedNodeVersionMessage;
3823
+ exports.verifyPaymentReceipt = verifyPaymentReceipt;
3566
3824
  exports.verifySignature = verifySignature;
3567
3825
  exports.x402AuthorizationAmount = x402AuthorizationAmount;
3568
3826
  //# sourceMappingURL=index.cjs.map