@haven_ai/sdk 0.1.16-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.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,11 @@ 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
+ function clampAuthorizationWindow(seconds) {
351
+ const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
352
+ return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
353
+ }
312
354
  function normalizePaymentOption(value) {
313
355
  const candidate = value;
314
356
  if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
@@ -330,7 +372,7 @@ function normalizePaymentOption(value) {
330
372
  mimeType: candidate.mimeType,
331
373
  asset: candidate.asset,
332
374
  payTo: candidate.payTo,
333
- maxTimeoutSeconds: candidate.maxTimeoutSeconds ?? 30,
375
+ maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
334
376
  extra: candidate.extra
335
377
  };
336
378
  }
@@ -360,11 +402,15 @@ function normalizePaymentRequired(value) {
360
402
  var SUPPORTED_X402_NETWORKS = {
361
403
  "eip155:100": "Gnosis Chain",
362
404
  "eip155:8453": "Base",
363
- "base": "Base"
405
+ "base": "Base",
406
+ "eip155:84532": "Base Sepolia",
407
+ "base-sepolia": "Base Sepolia"
364
408
  };
365
409
  var STANDARD_X402_NETWORKS = {
366
410
  "eip155:8453": "base",
367
- "base": "base"
411
+ "base": "base",
412
+ "eip155:84532": "base-sepolia",
413
+ "base-sepolia": "base-sepolia"
368
414
  };
369
415
  var GNOSIS_TOKENS = {
370
416
  "0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
@@ -375,14 +421,21 @@ var BASE_TOKENS = {
375
421
  "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
376
422
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
377
423
  };
424
+ var BASE_SEPOLIA_TOKENS = {
425
+ "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
426
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
427
+ };
378
428
  var ALL_TOKENS = {
379
429
  ...GNOSIS_TOKENS,
380
- ...BASE_TOKENS
430
+ ...BASE_TOKENS,
431
+ ...BASE_SEPOLIA_TOKENS
381
432
  };
382
433
  var NETWORK_TOKENS = {
383
434
  "eip155:100": GNOSIS_TOKENS,
384
435
  "eip155:8453": BASE_TOKENS,
385
- "base": BASE_TOKENS
436
+ "base": BASE_TOKENS,
437
+ "eip155:84532": BASE_SEPOLIA_TOKENS,
438
+ "base-sepolia": BASE_SEPOLIA_TOKENS
386
439
  };
387
440
  function parsePaymentRequired(response) {
388
441
  const v2Header = response.headers.get("PAYMENT-REQUIRED");
@@ -436,7 +489,7 @@ function selectPaymentOption(accepts) {
436
489
  function selectStandardPaymentOption(accepts) {
437
490
  if (!accepts || accepts.length === 0) return null;
438
491
  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))) {
492
+ if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
440
493
  return opt;
441
494
  }
442
495
  }
@@ -450,8 +503,9 @@ function x402AuthorizationAmount(option) {
450
503
  return amount;
451
504
  }
452
505
  function buildX402ExpectedMessage(context) {
506
+ const version = context.typedDataHash ? 2 : 1;
453
507
  const payload = {
454
- version: 1,
508
+ version,
455
509
  kind: "haven.x402.expected",
456
510
  paymentId: context.paymentId,
457
511
  payloadHash: context.payloadHash.toLowerCase(),
@@ -464,7 +518,10 @@ function buildX402ExpectedMessage(context) {
464
518
  if (context.expiresAt) {
465
519
  payload.expiresAt = context.expiresAt;
466
520
  }
467
- return `Haven x402 expected context v1
521
+ if (context.typedDataHash) {
522
+ payload.typedDataHash = context.typedDataHash.toLowerCase();
523
+ }
524
+ return `Haven x402 expected context v${version}
468
525
  ${stableStringify(payload)}`;
469
526
  }
470
527
  function toStandardPaymentRequirements(paymentRequired, option) {
@@ -484,7 +541,10 @@ function toStandardPaymentRequirements(paymentRequired, option) {
484
541
  mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
485
542
  payTo: option.payTo,
486
543
  asset: option.asset,
487
- maxTimeoutSeconds: option.maxTimeoutSeconds,
544
+ // Second enforcement point (#715): the parse path clamps too, but this is
545
+ // the last stop before the x402 library turns the timeout into
546
+ // `validBefore` — options constructed without parsing are bounded here.
547
+ maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds),
488
548
  extra: option.extra
489
549
  };
490
550
  }
@@ -618,7 +678,8 @@ var CHAIN_EXPLORER_TX = {
618
678
  8453: "https://basescan.org/tx"
619
679
  };
620
680
  var CHAIN_USDC = {
621
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
681
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
682
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
622
683
  };
623
684
  function buildExplorerUrl(chainId, txHash) {
624
685
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
@@ -781,6 +842,16 @@ function parseSseJsonRpcMessages(text) {
781
842
  function selectJsonRpcResult(messages) {
782
843
  return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
783
844
  }
845
+ function x402TypedDataDigest(typedData) {
846
+ if (!typedData || typeof typedData !== "object") return void 0;
847
+ try {
848
+ return hashTypedData(typedData);
849
+ } catch (err) {
850
+ throw new HavenSigningError(
851
+ `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)}`
852
+ );
853
+ }
854
+ }
784
855
  var HavenClient = class {
785
856
  apiKey;
786
857
  delegateKey;
@@ -930,6 +1001,11 @@ var HavenClient = class {
930
1001
  if (!raw.sign_data?.hash) {
931
1002
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
932
1003
  }
1004
+ if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
1005
+ throw new HavenSigningError(
1006
+ `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.`
1007
+ );
1008
+ }
933
1009
  if (!raw.x402_expected_auth) {
934
1010
  throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
935
1011
  }
@@ -946,6 +1022,14 @@ var HavenClient = class {
946
1022
  asset: option.asset,
947
1023
  network: option.network,
948
1024
  expectedAuth: raw.x402_expected_auth,
1025
+ // #1138: the digest the delegation-rail expected context commits to.
1026
+ // Re-derived locally, exactly like every other context field the edge
1027
+ // signer is handed (amount, merchantTo, …) — none of them are trusted
1028
+ // because they arrived, they are trusted because the reconstructed
1029
+ // message has to match Haven's signature over it. A typed_data altered in
1030
+ // transit therefore fails message equality and is refused, and the signer
1031
+ // re-derives this digest a second time from the payload it actually signs.
1032
+ expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
949
1033
  fundingTo
950
1034
  };
951
1035
  }
@@ -969,6 +1053,42 @@ var HavenClient = class {
969
1053
  }
970
1054
  return signature;
971
1055
  }
1056
+ /**
1057
+ * Sign a payment's `sign_data` with the correct scheme for its rail.
1058
+ *
1059
+ * Dispatching on the server-provided scheme means a caller never has to
1060
+ * know which rail an account is on; an unknown scheme is a hard error,
1061
+ * never a guessed signature. The session rail's 'eip191_userop' is retired
1062
+ * (#834) — the backend refuses those intents with HTTP 410 before any
1063
+ * sign_data reaches a client, so encountering it here is a hard error too.
1064
+ */
1065
+ async signForData(signData) {
1066
+ if (!this.delegateKey) {
1067
+ throw new HavenSigningError(
1068
+ "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
1069
+ );
1070
+ }
1071
+ const scheme = signData.signature_scheme;
1072
+ if (scheme === "eip191_userop") {
1073
+ throw new HavenSigningError(
1074
+ "The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
1075
+ );
1076
+ }
1077
+ if (scheme === "eip712_userop") {
1078
+ if (!signData.typed_data) {
1079
+ throw new HavenSigningError(
1080
+ "sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
1081
+ );
1082
+ }
1083
+ return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
1084
+ }
1085
+ if (scheme === void 0) {
1086
+ return signHash(this.delegateKey, signData.hash);
1087
+ }
1088
+ throw new HavenSigningError(
1089
+ `Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
1090
+ );
1091
+ }
972
1092
  /**
973
1093
  * Step 3: Submit a signature to execute the payment.
974
1094
  *
@@ -1089,9 +1209,10 @@ var HavenClient = class {
1089
1209
  }
1090
1210
  const ethBalance = await provider.getBalance(delegateAddress);
1091
1211
  if (ethBalance > 0n) {
1092
- const gasPrice = (await provider.getFeeData()).gasPrice ?? 1000000n;
1212
+ const fee = await provider.getFeeData();
1213
+ const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
1093
1214
  const gasLimit = 21000n;
1094
- const gasCost = gasPrice * gasLimit;
1215
+ const gasCost = effectiveGasPrice * gasLimit * 2n;
1095
1216
  const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1096
1217
  if (ethToSend > 0n) {
1097
1218
  const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
@@ -1204,6 +1325,18 @@ var HavenClient = class {
1204
1325
  const raw = await this.get(`/machine-payments/receipts${query}`);
1205
1326
  return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
1206
1327
  }
1328
+ /**
1329
+ * Fetch the verifiable receipt bundle for a settled payment and verify it
1330
+ * locally. The server's own verification is ignored — the receipt is verified
1331
+ * here (independently of Haven) by recovering the signer from the
1332
+ * authorisation, so the result is trustworthy even if the backend lied.
1333
+ */
1334
+ async getReceipt(paymentId) {
1335
+ const { receipt } = await this.get(
1336
+ `/payments/${paymentId}/receipt`
1337
+ );
1338
+ return { receipt, verification: verifyPaymentReceipt(receipt) };
1339
+ }
1207
1340
  /**
1208
1341
  * Rehydrate the x402/MPP resume-state bundle for a payment id.
1209
1342
  *
@@ -1346,7 +1479,7 @@ var HavenClient = class {
1346
1479
  if (!raw.sign_data?.hash) {
1347
1480
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1348
1481
  }
1349
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
1482
+ const sig = await this.signForData(raw.sign_data);
1350
1483
  const execResult = await this.post(
1351
1484
  `/payments/${raw.payment_id}/sign`,
1352
1485
  { signature: sig }
@@ -1705,8 +1838,39 @@ var HavenClient = class {
1705
1838
  protocolReceiptHeaderName: "PAYMENT-RESPONSE",
1706
1839
  protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
1707
1840
  });
1841
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1708
1842
  return retryResponse;
1709
1843
  }
1844
+ /**
1845
+ * #956: capture the merchant's OWN receipt when the paid response carries
1846
+ * one, and report it to Haven so the reporting feed can attach it next to
1847
+ * the Haven-generated payment evidence (#498). Two supported signals on the
1848
+ * paid response:
1849
+ *
1850
+ * x-receipt-json: base64-encoded JSON receipt document (inline)
1851
+ * x-receipt-url: https URL to the receipt document (reference)
1852
+ *
1853
+ * Strictly best-effort: absence is the normal case, and no failure here may
1854
+ * ever affect the completed payment — the response is already paid for.
1855
+ */
1856
+ async reportMerchantReceipt(paymentId, response) {
1857
+ try {
1858
+ const inlineB64 = response.headers.get("x-receipt-json");
1859
+ const url = response.headers.get("x-receipt-url");
1860
+ if (!inlineB64 && !url) return;
1861
+ let body = null;
1862
+ if (inlineB64) {
1863
+ if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
1864
+ const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
1865
+ if (decoded && typeof decoded === "object") body = { json: decoded };
1866
+ } else if (url && url.startsWith("https://") && url.length <= 2048) {
1867
+ body = { url };
1868
+ }
1869
+ if (!body) return;
1870
+ await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
1871
+ } catch {
1872
+ }
1873
+ }
1710
1874
  /**
1711
1875
  * Deliver an already-signed x402 payment header to the merchant and return
1712
1876
  * the merchant's response. Used by the hosted MCP server to complete the
@@ -1794,6 +1958,7 @@ var HavenClient = class {
1794
1958
  protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
1795
1959
  protocolReceiptHeader
1796
1960
  });
1961
+ await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
1797
1962
  }
1798
1963
  return {
1799
1964
  status: surfaced.status,
@@ -1888,7 +2053,7 @@ var HavenClient = class {
1888
2053
  if (!raw.sign_data?.hash) {
1889
2054
  throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
1890
2055
  }
1891
- const sig = signHash(this.delegateKey, raw.sign_data.hash);
2056
+ const sig = await this.signForData(raw.sign_data);
1892
2057
  const execResult = await this.post(
1893
2058
  `/payments/${raw.payment_id}/sign`,
1894
2059
  { signature: sig }
@@ -1991,6 +2156,7 @@ var HavenClient = class {
1991
2156
  protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
1992
2157
  protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
1993
2158
  });
2159
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
1994
2160
  return retryResponse;
1995
2161
  }
1996
2162
  assertCanResumeX402(status, paymentRequired, option) {
@@ -2820,7 +2986,11 @@ var HavenClient = class {
2820
2986
  });
2821
2987
  const data = await res.json();
2822
2988
  if (!res.ok) {
2823
- const message = data.error ?? data.details ?? `API request failed`;
2989
+ const record = data;
2990
+ const errorText = typeof record.error === "string" ? record.error : void 0;
2991
+ const rawDetails = record.details ?? record.detail;
2992
+ const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
2993
+ const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
2824
2994
  throw new HavenApiError(message, res.status, data);
2825
2995
  }
2826
2996
  return data;
@@ -2848,6 +3018,12 @@ var HavenClient = class {
2848
3018
  txHash: raw.tx_hash,
2849
3019
  errorMessage: raw.error_message,
2850
3020
  explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
3021
+ fee: raw.fee ? {
3022
+ amount: raw.fee.amount,
3023
+ token: raw.fee.token,
3024
+ basisPoints: raw.fee.basis_points,
3025
+ applied: raw.fee.applied
3026
+ } : null,
2851
3027
  createdAt: raw.created_at,
2852
3028
  signedAt: raw.signed_at,
2853
3029
  submittedAt: raw.submitted_at,
@@ -2871,6 +3047,12 @@ var HavenClient = class {
2871
3047
  expiresAt: raw.expires_at,
2872
3048
  chainId: raw.chain_id,
2873
3049
  message: raw.message,
3050
+ fee: raw.fee ? {
3051
+ amount: raw.fee.amount,
3052
+ token: raw.fee.token,
3053
+ basisPoints: raw.fee.basis_points,
3054
+ applied: raw.fee.applied
3055
+ } : null,
2874
3056
  amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
2875
3057
  asset: raw.asset ?? raw.x402?.asset ?? null,
2876
3058
  network: raw.network ?? raw.x402?.network ?? null,
@@ -3016,13 +3198,13 @@ var toolDescriptions = {
3016
3198
  getAgent: {
3017
3199
  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
3200
  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.',
3201
+ 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
3202
  nextActionGuidance: ""
3021
3203
  },
3022
3204
  getAllowances: {
3023
3205
  summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
3024
3206
  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.",
3207
+ 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
3208
  nextActionGuidance: ""
3027
3209
  },
3028
3210
  listReceipts: {
@@ -3031,6 +3213,12 @@ var toolDescriptions = {
3031
3213
  behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
3032
3214
  nextActionGuidance: ""
3033
3215
  },
3216
+ verifyReceipt: {
3217
+ summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
3218
+ 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.",
3219
+ 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.",
3220
+ nextActionGuidance: ""
3221
+ },
3034
3222
  payMcpTool: {
3035
3223
  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
3224
  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,18 +3608,31 @@ var SKILL_FOLDER_NAME = "haven-pay";
3420
3608
 
3421
3609
  // src/sweep.ts
3422
3610
  var SWEEP_BASE_CHAIN_ID = 8453;
3611
+ var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
3423
3612
  var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
3613
+ var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
3424
3614
  var USDC_EIP712_DOMAIN_BY_CHAIN = {
3425
3615
  [SWEEP_BASE_CHAIN_ID]: {
3426
3616
  name: "USD Coin",
3427
3617
  version: "2",
3428
3618
  chainId: SWEEP_BASE_CHAIN_ID,
3429
3619
  verifyingContract: SWEEP_BASE_USDC_ADDRESS
3620
+ },
3621
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
3622
+ name: "USDC",
3623
+ version: "2",
3624
+ chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
3625
+ verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3430
3626
  }
3431
3627
  };
3432
3628
  var USDC_ADDRESS_BY_CHAIN = {
3433
- [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
3629
+ [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
3630
+ [SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
3434
3631
  };
3632
+ var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
3633
+ function isSweepableChain(chainId) {
3634
+ return chainId in USDC_ADDRESS_BY_CHAIN;
3635
+ }
3435
3636
  var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3436
3637
  TransferWithAuthorization: [
3437
3638
  { name: "from", type: "address" },
@@ -3446,7 +3647,7 @@ function sweepUsdcAddress(chainId) {
3446
3647
  const address = USDC_ADDRESS_BY_CHAIN[chainId];
3447
3648
  if (!address) {
3448
3649
  throw new HavenSigningError(
3449
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3650
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3450
3651
  );
3451
3652
  }
3452
3653
  return address;
@@ -3455,7 +3656,7 @@ function sweepUsdcDomain(chainId) {
3455
3656
  const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
3456
3657
  if (!domain) {
3457
3658
  throw new HavenSigningError(
3458
- `Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
3659
+ `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
3459
3660
  );
3460
3661
  }
3461
3662
  return domain;
@@ -3510,6 +3711,6 @@ function stableStringify2(value) {
3510
3711
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
3511
3712
  }
3512
3713
 
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 };
3714
+ 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, 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, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, isSweepableChain, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, verifyPaymentReceipt, verifySignature, x402AuthorizationAmount };
3514
3715
  //# sourceMappingURL=index.js.map
3515
3716
  //# sourceMappingURL=index.js.map