@haven_ai/sdk 0.1.17-alpha.0 → 0.1.18-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,11 @@ 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
+ function clampAuthorizationWindow(seconds) {
353
+ const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
354
+ return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
355
+ }
314
356
  function normalizePaymentOption(value) {
315
357
  const candidate = value;
316
358
  if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
@@ -332,7 +374,7 @@ function normalizePaymentOption(value) {
332
374
  mimeType: candidate.mimeType,
333
375
  asset: candidate.asset,
334
376
  payTo: candidate.payTo,
335
- maxTimeoutSeconds: candidate.maxTimeoutSeconds ?? 30,
377
+ maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
336
378
  extra: candidate.extra
337
379
  };
338
380
  }
@@ -362,11 +404,15 @@ function normalizePaymentRequired(value) {
362
404
  var SUPPORTED_X402_NETWORKS = {
363
405
  "eip155:100": "Gnosis Chain",
364
406
  "eip155:8453": "Base",
365
- "base": "Base"
407
+ "base": "Base",
408
+ "eip155:84532": "Base Sepolia",
409
+ "base-sepolia": "Base Sepolia"
366
410
  };
367
411
  var STANDARD_X402_NETWORKS = {
368
412
  "eip155:8453": "base",
369
- "base": "base"
413
+ "base": "base",
414
+ "eip155:84532": "base-sepolia",
415
+ "base-sepolia": "base-sepolia"
370
416
  };
371
417
  var GNOSIS_TOKENS = {
372
418
  "0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
@@ -377,14 +423,21 @@ var BASE_TOKENS = {
377
423
  "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
378
424
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
379
425
  };
426
+ var BASE_SEPOLIA_TOKENS = {
427
+ "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
428
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
429
+ };
380
430
  var ALL_TOKENS = {
381
431
  ...GNOSIS_TOKENS,
382
- ...BASE_TOKENS
432
+ ...BASE_TOKENS,
433
+ ...BASE_SEPOLIA_TOKENS
383
434
  };
384
435
  var NETWORK_TOKENS = {
385
436
  "eip155:100": GNOSIS_TOKENS,
386
437
  "eip155:8453": BASE_TOKENS,
387
- "base": BASE_TOKENS
438
+ "base": BASE_TOKENS,
439
+ "eip155:84532": BASE_SEPOLIA_TOKENS,
440
+ "base-sepolia": BASE_SEPOLIA_TOKENS
388
441
  };
389
442
  function parsePaymentRequired(response) {
390
443
  const v2Header = response.headers.get("PAYMENT-REQUIRED");
@@ -438,7 +491,7 @@ function selectPaymentOption(accepts) {
438
491
  function selectStandardPaymentOption(accepts) {
439
492
  if (!accepts || accepts.length === 0) return null;
440
493
  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))) {
494
+ if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
442
495
  return opt;
443
496
  }
444
497
  }
@@ -452,8 +505,9 @@ function x402AuthorizationAmount(option) {
452
505
  return amount;
453
506
  }
454
507
  function buildX402ExpectedMessage(context) {
508
+ const version = context.typedDataHash ? 2 : 1;
455
509
  const payload = {
456
- version: 1,
510
+ version,
457
511
  kind: "haven.x402.expected",
458
512
  paymentId: context.paymentId,
459
513
  payloadHash: context.payloadHash.toLowerCase(),
@@ -466,7 +520,10 @@ function buildX402ExpectedMessage(context) {
466
520
  if (context.expiresAt) {
467
521
  payload.expiresAt = context.expiresAt;
468
522
  }
469
- return `Haven x402 expected context v1
523
+ if (context.typedDataHash) {
524
+ payload.typedDataHash = context.typedDataHash.toLowerCase();
525
+ }
526
+ return `Haven x402 expected context v${version}
470
527
  ${stableStringify(payload)}`;
471
528
  }
472
529
  function toStandardPaymentRequirements(paymentRequired, option) {
@@ -486,7 +543,10 @@ function toStandardPaymentRequirements(paymentRequired, option) {
486
543
  mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
487
544
  payTo: option.payTo,
488
545
  asset: option.asset,
489
- maxTimeoutSeconds: option.maxTimeoutSeconds,
546
+ // Second enforcement point (#715): the parse path clamps too, but this is
547
+ // the last stop before the x402 library turns the timeout into
548
+ // `validBefore` — options constructed without parsing are bounded here.
549
+ maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds),
490
550
  extra: option.extra
491
551
  };
492
552
  }
@@ -620,7 +680,8 @@ var CHAIN_EXPLORER_TX = {
620
680
  8453: "https://basescan.org/tx"
621
681
  };
622
682
  var CHAIN_USDC = {
623
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
683
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
684
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
624
685
  };
625
686
  function buildExplorerUrl(chainId, txHash) {
626
687
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
@@ -783,6 +844,16 @@ function parseSseJsonRpcMessages(text) {
783
844
  function selectJsonRpcResult(messages) {
784
845
  return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
785
846
  }
847
+ function x402TypedDataDigest(typedData) {
848
+ if (!typedData || typeof typedData !== "object") return void 0;
849
+ try {
850
+ return viem.hashTypedData(typedData);
851
+ } catch (err) {
852
+ throw new HavenSigningError(
853
+ `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)}`
854
+ );
855
+ }
856
+ }
786
857
  var HavenClient = class {
787
858
  apiKey;
788
859
  delegateKey;
@@ -932,6 +1003,11 @@ var HavenClient = class {
932
1003
  if (!raw.sign_data?.hash) {
933
1004
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
934
1005
  }
1006
+ if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
1007
+ throw new HavenSigningError(
1008
+ `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.`
1009
+ );
1010
+ }
935
1011
  if (!raw.x402_expected_auth) {
936
1012
  throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
937
1013
  }
@@ -948,6 +1024,14 @@ var HavenClient = class {
948
1024
  asset: option.asset,
949
1025
  network: option.network,
950
1026
  expectedAuth: raw.x402_expected_auth,
1027
+ // #1138: the digest the delegation-rail expected context commits to.
1028
+ // Re-derived locally, exactly like every other context field the edge
1029
+ // signer is handed (amount, merchantTo, …) — none of them are trusted
1030
+ // because they arrived, they are trusted because the reconstructed
1031
+ // message has to match Haven's signature over it. A typed_data altered in
1032
+ // transit therefore fails message equality and is refused, and the signer
1033
+ // re-derives this digest a second time from the payload it actually signs.
1034
+ expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
951
1035
  fundingTo
952
1036
  };
953
1037
  }
@@ -971,6 +1055,42 @@ var HavenClient = class {
971
1055
  }
972
1056
  return signature;
973
1057
  }
1058
+ /**
1059
+ * Sign a payment's `sign_data` with the correct scheme for its rail.
1060
+ *
1061
+ * Dispatching on the server-provided scheme means a caller never has to
1062
+ * know which rail an account is on; an unknown scheme is a hard error,
1063
+ * never a guessed signature. The session rail's 'eip191_userop' is retired
1064
+ * (#834) — the backend refuses those intents with HTTP 410 before any
1065
+ * sign_data reaches a client, so encountering it here is a hard error too.
1066
+ */
1067
+ async signForData(signData) {
1068
+ if (!this.delegateKey) {
1069
+ throw new HavenSigningError(
1070
+ "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
1071
+ );
1072
+ }
1073
+ const scheme = signData.signature_scheme;
1074
+ if (scheme === "eip191_userop") {
1075
+ throw new HavenSigningError(
1076
+ "The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
1077
+ );
1078
+ }
1079
+ if (scheme === "eip712_userop") {
1080
+ if (!signData.typed_data) {
1081
+ throw new HavenSigningError(
1082
+ "sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
1083
+ );
1084
+ }
1085
+ return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
1086
+ }
1087
+ if (scheme === void 0) {
1088
+ return signHash(this.delegateKey, signData.hash);
1089
+ }
1090
+ throw new HavenSigningError(
1091
+ `Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
1092
+ );
1093
+ }
974
1094
  /**
975
1095
  * Step 3: Submit a signature to execute the payment.
976
1096
  *
@@ -1091,9 +1211,10 @@ var HavenClient = class {
1091
1211
  }
1092
1212
  const ethBalance = await provider.getBalance(delegateAddress);
1093
1213
  if (ethBalance > 0n) {
1094
- const gasPrice = (await provider.getFeeData()).gasPrice ?? 1000000n;
1214
+ const fee = await provider.getFeeData();
1215
+ const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
1095
1216
  const gasLimit = 21000n;
1096
- const gasCost = gasPrice * gasLimit;
1217
+ const gasCost = effectiveGasPrice * gasLimit * 2n;
1097
1218
  const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1098
1219
  if (ethToSend > 0n) {
1099
1220
  const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
@@ -1206,6 +1327,18 @@ var HavenClient = class {
1206
1327
  const raw = await this.get(`/machine-payments/receipts${query}`);
1207
1328
  return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
1208
1329
  }
1330
+ /**
1331
+ * Fetch the verifiable receipt bundle for a settled payment and verify it
1332
+ * locally. The server's own verification is ignored — the receipt is verified
1333
+ * here (independently of Haven) by recovering the signer from the
1334
+ * authorisation, so the result is trustworthy even if the backend lied.
1335
+ */
1336
+ async getReceipt(paymentId) {
1337
+ const { receipt } = await this.get(
1338
+ `/payments/${paymentId}/receipt`
1339
+ );
1340
+ return { receipt, verification: verifyPaymentReceipt(receipt) };
1341
+ }
1209
1342
  /**
1210
1343
  * Rehydrate the x402/MPP resume-state bundle for a payment id.
1211
1344
  *
@@ -1348,7 +1481,7 @@ var HavenClient = class {
1348
1481
  if (!raw.sign_data?.hash) {
1349
1482
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1350
1483
  }
1351
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
1484
+ const sig = await this.signForData(raw.sign_data);
1352
1485
  const execResult = await this.post(
1353
1486
  `/payments/${raw.payment_id}/sign`,
1354
1487
  { signature: sig }
@@ -1707,8 +1840,39 @@ var HavenClient = class {
1707
1840
  protocolReceiptHeaderName: "PAYMENT-RESPONSE",
1708
1841
  protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
1709
1842
  });
1843
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1710
1844
  return retryResponse;
1711
1845
  }
1846
+ /**
1847
+ * #956: capture the merchant's OWN receipt when the paid response carries
1848
+ * one, and report it to Haven so the reporting feed can attach it next to
1849
+ * the Haven-generated payment evidence (#498). Two supported signals on the
1850
+ * paid response:
1851
+ *
1852
+ * x-receipt-json: base64-encoded JSON receipt document (inline)
1853
+ * x-receipt-url: https URL to the receipt document (reference)
1854
+ *
1855
+ * Strictly best-effort: absence is the normal case, and no failure here may
1856
+ * ever affect the completed payment — the response is already paid for.
1857
+ */
1858
+ async reportMerchantReceipt(paymentId, response) {
1859
+ try {
1860
+ const inlineB64 = response.headers.get("x-receipt-json");
1861
+ const url = response.headers.get("x-receipt-url");
1862
+ if (!inlineB64 && !url) return;
1863
+ let body = null;
1864
+ if (inlineB64) {
1865
+ if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
1866
+ const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
1867
+ if (decoded && typeof decoded === "object") body = { json: decoded };
1868
+ } else if (url && url.startsWith("https://") && url.length <= 2048) {
1869
+ body = { url };
1870
+ }
1871
+ if (!body) return;
1872
+ await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
1873
+ } catch {
1874
+ }
1875
+ }
1712
1876
  /**
1713
1877
  * Deliver an already-signed x402 payment header to the merchant and return
1714
1878
  * the merchant's response. Used by the hosted MCP server to complete the
@@ -1796,6 +1960,7 @@ var HavenClient = class {
1796
1960
  protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1797
1961
  protocolReceiptHeader
1798
1962
  });
1963
+ await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
1799
1964
  }
1800
1965
  return {
1801
1966
  status: surfaced.status,
@@ -1890,7 +2055,7 @@ var HavenClient = class {
1890
2055
  if (!raw.sign_data?.hash) {
1891
2056
  throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
1892
2057
  }
1893
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
2058
+ const sig = await this.signForData(raw.sign_data);
1894
2059
  const execResult = await this.post(
1895
2060
  `/payments/${raw.payment_id}/sign`,
1896
2061
  { signature: sig }
@@ -1993,6 +2158,7 @@ var HavenClient = class {
1993
2158
  protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
1994
2159
  protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
1995
2160
  });
2161
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1996
2162
  return retryResponse;
1997
2163
  }
1998
2164
  assertCanResumeX402(status, paymentRequired, option) {
@@ -2822,7 +2988,11 @@ var HavenClient = class {
2822
2988
  });
2823
2989
  const data = await res.json();
2824
2990
  if (!res.ok) {
2825
- const message = data.error ?? data.details ?? `API request failed`;
2991
+ const record = data;
2992
+ const errorText = typeof record.error === "string" ? record.error : void 0;
2993
+ const rawDetails = record.details ?? record.detail;
2994
+ const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
2995
+ const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
2826
2996
  throw new HavenApiError(message, res.status, data);
2827
2997
  }
2828
2998
  return data;
@@ -2850,6 +3020,12 @@ var HavenClient = class {
2850
3020
  txHash: raw.tx_hash,
2851
3021
  errorMessage: raw.error_message,
2852
3022
  explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
3023
+ fee: raw.fee ? {
3024
+ amount: raw.fee.amount,
3025
+ token: raw.fee.token,
3026
+ basisPoints: raw.fee.basis_points,
3027
+ applied: raw.fee.applied
3028
+ } : null,
2853
3029
  createdAt: raw.created_at,
2854
3030
  signedAt: raw.signed_at,
2855
3031
  submittedAt: raw.submitted_at,
@@ -2873,6 +3049,12 @@ var HavenClient = class {
2873
3049
  expiresAt: raw.expires_at,
2874
3050
  chainId: raw.chain_id,
2875
3051
  message: raw.message,
3052
+ fee: raw.fee ? {
3053
+ amount: raw.fee.amount,
3054
+ token: raw.fee.token,
3055
+ basisPoints: raw.fee.basis_points,
3056
+ applied: raw.fee.applied
3057
+ } : null,
2876
3058
  amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
2877
3059
  asset: raw.asset ?? raw.x402?.asset ?? null,
2878
3060
  network: raw.network ?? raw.x402?.network ?? null,
@@ -3018,13 +3200,13 @@ var toolDescriptions = {
3018
3200
  getAgent: {
3019
3201
  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
3202
  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.',
3203
+ 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
3204
  nextActionGuidance: ""
3023
3205
  },
3024
3206
  getAllowances: {
3025
3207
  summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
3026
3208
  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.",
3209
+ 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
3210
  nextActionGuidance: ""
3029
3211
  },
3030
3212
  listReceipts: {
@@ -3033,6 +3215,12 @@ var toolDescriptions = {
3033
3215
  behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
3034
3216
  nextActionGuidance: ""
3035
3217
  },
3218
+ verifyReceipt: {
3219
+ summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
3220
+ 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.",
3221
+ 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.",
3222
+ nextActionGuidance: ""
3223
+ },
3036
3224
  payMcpTool: {
3037
3225
  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
3226
  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.",
@@ -3422,18 +3610,31 @@ var SKILL_FOLDER_NAME = "haven-pay";
3422
3610
 
3423
3611
  // src/sweep.ts
3424
3612
  var SWEEP_BASE_CHAIN_ID = 8453;
3613
+ var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
3425
3614
  var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3615
+ var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
3426
3616
  var USDC_EIP712_DOMAIN_BY_CHAIN = {
3427
3617
  [SWEEP_BASE_CHAIN_ID]: {
3428
3618
  name: "USD Coin",
3429
3619
  version: "2",
3430
3620
  chainId: SWEEP_BASE_CHAIN_ID,
3431
3621
  verifyingContract: SWEEP_BASE_USDC_ADDRESS
3622
+ },
3623
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
3624
+ name: "USDC",
3625
+ version: "2",
3626
+ chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
3627
+ verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3432
3628
  }
3433
3629
  };
3434
3630
  var USDC_ADDRESS_BY_CHAIN = {
3435
- [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3631
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
3632
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3436
3633
  };
3634
+ var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
3635
+ function isSweepableChain(chainId) {
3636
+ return chainId in USDC_ADDRESS_BY_CHAIN;
3637
+ }
3437
3638
  var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3438
3639
  TransferWithAuthorization: [
3439
3640
  { name: "from", type: "address" },
@@ -3448,7 +3649,7 @@ function sweepUsdcAddress(chainId) {
3448
3649
  const address = USDC_ADDRESS_BY_CHAIN[chainId];
3449
3650
  if (!address) {
3450
3651
  throw new HavenSigningError(
3451
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3652
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3452
3653
  );
3453
3654
  }
3454
3655
  return address;
@@ -3457,7 +3658,7 @@ function sweepUsdcDomain(chainId) {
3457
3658
  const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3458
3659
  if (!domain) {
3459
3660
  throw new HavenSigningError(
3460
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3661
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3461
3662
  );
3462
3663
  }
3463
3664
  return domain;
@@ -3535,8 +3736,11 @@ exports.HavenError = HavenError;
3535
3736
  exports.HavenPaymentStateError = HavenPaymentStateError;
3536
3737
  exports.HavenSigningError = HavenSigningError;
3537
3738
  exports.HavenTimeoutError = HavenTimeoutError;
3739
+ exports.RECEIPT_VERSION = RECEIPT_VERSION;
3538
3740
  exports.SKILL_FOLDER_NAME = SKILL_FOLDER_NAME;
3539
3741
  exports.SWEEP_BASE_CHAIN_ID = SWEEP_BASE_CHAIN_ID;
3742
+ exports.SWEEP_BASE_SEPOLIA_CHAIN_ID = SWEEP_BASE_SEPOLIA_CHAIN_ID;
3743
+ exports.SWEEP_BASE_SEPOLIA_USDC_ADDRESS = SWEEP_BASE_SEPOLIA_USDC_ADDRESS;
3540
3744
  exports.SWEEP_BASE_USDC_ADDRESS = SWEEP_BASE_USDC_ADDRESS;
3541
3745
  exports.TRANSFER_WITH_AUTHORIZATION_TYPES = TRANSFER_WITH_AUTHORIZATION_TYPES;
3542
3746
  exports.addressFromKey = addressFromKey;
@@ -3552,6 +3756,7 @@ exports.encodeBase64Utf8 = encodeBase64Utf8;
3552
3756
  exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
3553
3757
  exports.encodePaymentProof = encodePaymentProof;
3554
3758
  exports.havenTools = havenTools;
3759
+ exports.isSweepableChain = isSweepableChain;
3555
3760
  exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
3556
3761
  exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
3557
3762
  exports.parsePaymentRequired = parsePaymentRequired;
@@ -3559,10 +3764,12 @@ exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
3559
3764
  exports.selectPaymentOption = selectPaymentOption;
3560
3765
  exports.selectStandardPaymentOption = selectStandardPaymentOption;
3561
3766
  exports.signHash = signHash;
3767
+ exports.signUserOpTypedDataForDelegation = signUserOpTypedDataForDelegation;
3562
3768
  exports.sweepUsdcAddress = sweepUsdcAddress;
3563
3769
  exports.sweepUsdcDomain = sweepUsdcDomain;
3564
3770
  exports.toStandardPaymentRequirements = toStandardPaymentRequirements;
3565
3771
  exports.toolDescriptions = toolDescriptions;
3772
+ exports.verifyPaymentReceipt = verifyPaymentReceipt;
3566
3773
  exports.verifySignature = verifySignature;
3567
3774
  exports.x402AuthorizationAmount = x402AuthorizationAmount;
3568
3775
  //# sourceMappingURL=index.cjs.map