@haven_ai/sdk 0.5.0-alpha.1 → 0.6.0-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,7 +1,7 @@
1
- import { selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, resolveTokenFromAddress, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, selectX402SettlementScheme, selectErc7710PaymentOption, isErc7710Option, x402AssetTransferMethod, normalizePaymentRequired } from './chunk-KQ7JXKTV.js';
2
- export { DEFAULT_NEXT_TOOL_BY_ACTION, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_CLIENT_HEADER, HAVEN_MINIMUM_NODE_VERSION, HavenTypedDataRefusedError, NEXT_TOOL_SERVER_NAMES, NEXT_TOOL_SERVER_ROLES, SDK_CLIENT_IDENTITY, SDK_VERSION, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, TYPED_DATA_NOT_ALLOWED, X402PaymentHeaderValidationError, X402_LEGACY_PAYMENT_HEADER_NAME, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_PAYMENT_HEADER_NAME, X402_PAYMENT_HEADER_NAMES_SENT, X402_PAYMENT_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenClientIdentity, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readClientUpdate, renderNextTool, resolveTokenFromAddress, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402V2PaymentEnvelope } from './chunk-KQ7JXKTV.js';
3
- import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402UnexpectedStatusError, X402AlreadySettledError, AgentPaymentWarningCode, MerchantTimeoutError, AgentPaymentNextAction, HavenPaymentStateError, HavenZeroSettlementHashError, chainIdForNetwork } from './chunk-GLNG2GF7.js';
4
- 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, AgentPaymentWarningCode, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DIRECT_SIGN_CONTEXT_VERSION, ENTRY_POINT_V07, HAVEN_CONNECTOR_CHANNEL, HYBRID_DELEGATOR_DOMAIN_NAME, HYBRID_DELEGATOR_DOMAIN_VERSION, HavenApiError, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenUserOpBindingError, HavenZeroSettlementHashError, MerchantTimeoutError, PACKED_USER_OPERATION_FIELDS, ROOT_AUTHORITY, SIGNER_UPDATE_FALLBACK, SignerRefusalCode, X402AlreadySettledError, X402UnexpectedStatusError, assertUserOpTypedDataBinding, connectorRerunCommand, connectorSpec, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild } from './chunk-GLNG2GF7.js';
1
+ import { selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, resolveTokenFromAddress, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, selectX402SettlementScheme, selectErc7710PaymentOption, isErc7710Option, x402AssetTransferMethod, normalizePaymentRequired } from './chunk-3WSEX3GW.js';
2
+ export { DEFAULT_NEXT_TOOL_BY_ACTION, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_CLIENT_HEADER, HAVEN_MINIMUM_NODE_VERSION, HavenTypedDataRefusedError, MAX_TASK_BUDGET_TTL_SECONDS, NEXT_TOOL_SERVER_NAMES, NEXT_TOOL_SERVER_ROLES, SDK_CLIENT_IDENTITY, SDK_VERSION, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, TYPED_DATA_NOT_ALLOWED, X402PaymentHeaderValidationError, X402_LEGACY_PAYMENT_HEADER_NAME, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_PAYMENT_HEADER_NAME, X402_PAYMENT_HEADER_NAMES_SENT, X402_PAYMENT_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, assertOwnTaskBudgetCloseUserOp, assertOwnTaskChild, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, hashDelegation, havenClientIdentity, isErc7710Option, isSupportedNodeVersion, isSweepableChain, isTaskChildTypedData, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readClientUpdate, renderNextTool, resolveTokenFromAddress, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402V2PaymentEnvelope } from './chunk-3WSEX3GW.js';
3
+ import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402UnexpectedStatusError, X402AlreadySettledError, AgentPaymentWarningCode, MerchantTimeoutError, AgentPaymentNextAction, HavenPaymentStateError, HavenZeroSettlementHashError, chainIdForNetwork } from './chunk-63GPTEEY.js';
4
+ 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, AgentPaymentWarningCode, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DIRECT_SIGN_CONTEXT_VERSION, ENTRY_POINT_V07, HAVEN_CONNECTOR_CHANNEL, HYBRID_DELEGATOR_DOMAIN_NAME, HYBRID_DELEGATOR_DOMAIN_VERSION, HavenApiError, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenUserOpBindingError, HavenZeroSettlementHashError, MerchantTimeoutError, PACKED_USER_OPERATION_FIELDS, ROOT_AUTHORITY, SIGNER_UPDATE_FALLBACK, SignerRefusalCode, X402AlreadySettledError, X402UnexpectedStatusError, assertUserOpTypedDataBinding, connectorRerunCommand, connectorSpec, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild } from './chunk-63GPTEEY.js';
5
5
  import { hashTypedData } from 'viem';
6
6
  import { ethers } from 'ethers';
7
7
  import { AsyncLocalStorage } from 'async_hooks';
@@ -232,17 +232,13 @@ function mapPaymentReceipt(raw) {
232
232
  id: raw.id,
233
233
  paymentId: raw.payment_id,
234
234
  source: raw.rail,
235
- rail: raw.rail,
236
235
  paymentProofStatus: raw.proof_status,
237
- proofStatus: raw.proof_status,
238
236
  txHash: raw.tx_hash,
239
237
  fundingTxHash: raw.funding_tx_hash ?? null,
240
238
  settlementTxHash: raw.settlement_tx_hash ?? null,
241
239
  chainId: raw.chain_id,
242
240
  x402ResourceUrl: raw.resource_url,
243
- resourceUrl: raw.resource_url,
244
241
  x402MerchantAddress: raw.merchant_address,
245
- merchantAddress: raw.merchant_address,
246
242
  payerAddress: raw.payer_address,
247
243
  parties: mapParties(raw.parties),
248
244
  settlementAddress: raw.settlement_address,
@@ -883,6 +879,39 @@ function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
883
879
  const token = resolveTokenFromAddress(tokenAddress);
884
880
  return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
885
881
  }
882
+ function mapTaskBudget(raw) {
883
+ return {
884
+ id: raw.id,
885
+ agentId: raw.agent_id,
886
+ chainId: raw.chain_id,
887
+ tokenAddress: raw.token_address,
888
+ recipientAddress: raw.recipient_address,
889
+ parentDelegationHash: raw.parent_delegation_hash,
890
+ delegationHash: raw.delegation_hash,
891
+ label: raw.label,
892
+ maxAtomic: raw.max_atomic,
893
+ status: raw.status,
894
+ expiresAt: raw.expires_at,
895
+ isExpired: raw.is_expired,
896
+ createdAt: raw.created_at,
897
+ openedAt: raw.opened_at,
898
+ closedAt: raw.closed_at,
899
+ closeTxHash: raw.close_tx_hash
900
+ };
901
+ }
902
+ function summarizeTaskBudget(taskBudget) {
903
+ const token = resolveTokenFromAddress(taskBudget.tokenAddress);
904
+ const maxDisplay = token ? `${formatAtomicAmount(safeBigInt(taskBudget.maxAtomic), token.decimals)} ${token.symbol}` : `${taskBudget.maxAtomic} (atomic; unknown decimals)`;
905
+ return {
906
+ id: taskBudget.id,
907
+ label: taskBudget.label,
908
+ tokenAddress: taskBudget.tokenAddress,
909
+ maxAtomic: taskBudget.maxAtomic,
910
+ maxDisplay,
911
+ recipientAddress: taskBudget.recipientAddress,
912
+ expiresAt: taskBudget.expiresAt
913
+ };
914
+ }
886
915
  var AccountReads = class {
887
916
  transport;
888
917
  getPaymentStatus;
@@ -902,7 +931,11 @@ var AccountReads = class {
902
931
  return request;
903
932
  }
904
933
  async getAgentSummary() {
905
- const [agent, allowanceSummary] = await Promise.all([this.getAgent(), this.getAllowances()]);
934
+ const [agent, allowanceSummary, taskBudgets] = await Promise.all([
935
+ this.getAgent(),
936
+ this.getAllowances(),
937
+ this.listOpenTaskBudgetsSummary()
938
+ ]);
906
939
  const allowances = allowanceSummary.allowances.map((allowance) => {
907
940
  return {
908
941
  id: allowance.id,
@@ -916,7 +949,30 @@ var AccountReads = class {
916
949
  };
917
950
  });
918
951
  const readiness = deriveReadiness(agent.status, allowances);
919
- return { ...agent, readiness, spend_authority_readiness: readiness, allowances };
952
+ return { ...agent, readiness, spend_authority_readiness: readiness, allowances, taskBudgets };
953
+ }
954
+ /**
955
+ * #3329: `GET /task-budgets?status=open`, mapped to the condensed summary
956
+ * row `getAgentSummary()` carries. Fails SOFT per the #3093 rule (default
957
+ * every array wire key to `[]` so an absent/malformed key degrades instead
958
+ * of taking the route down): a 404 (a backend that predates #3329's
959
+ * route), ANY other transport error, an undefined/null body, or a missing
960
+ * or non-array `task_budgets` key all return `[]` rather than throwing —
961
+ * `getAgentSummary`'s identity + readiness + allowances fields are what
962
+ * callers depend on, and this read must never be why the whole summary
963
+ * fails.
964
+ */
965
+ async listOpenTaskBudgetsSummary() {
966
+ try {
967
+ const raw = await this.transport.get(
968
+ "/task-budgets?status=open"
969
+ );
970
+ const rows = raw?.task_budgets;
971
+ if (!Array.isArray(rows)) return [];
972
+ return rows.map((row) => summarizeTaskBudget(mapTaskBudget(row)));
973
+ } catch {
974
+ return [];
975
+ }
920
976
  }
921
977
  async getAllowances() {
922
978
  const raw = await this.transport.get("/machine-payments/allowances");
@@ -1446,7 +1502,7 @@ var X402FundingLeg = class {
1446
1502
  }
1447
1503
  }
1448
1504
  // ── Authorization ────────────────────────────────────────────────
1449
- async authorize(paymentRequired, option, idempotencyKey) {
1505
+ async authorize(paymentRequired, option, idempotencyKey, taskBudgetId) {
1450
1506
  const raw = await this.post("/x402", {
1451
1507
  url: paymentRequired.resource.url,
1452
1508
  payTo: this.delegateAddress,
@@ -1459,7 +1515,10 @@ var X402FundingLeg = class {
1459
1515
  // #1360: same explicit funding-leg declaration as createX402Intent —
1460
1516
  // this local-key path derives payTo from the key (never stale), but the
1461
1517
  // declaration keeps both writers of the 3009 shape loud-by-default.
1462
- settlementScheme: "eip3009"
1518
+ settlementScheme: "eip3009",
1519
+ // #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`) — do not
1520
+ // switch this to `task_budget_id`, which is only the `POST /payments` key.
1521
+ ...taskBudgetId ? { taskBudgetId } : {}
1463
1522
  });
1464
1523
  const state = paymentStateFromRaw("x402 payment", raw);
1465
1524
  const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
@@ -1494,7 +1553,10 @@ var X402FundingLeg = class {
1494
1553
  if (!raw.sign_data?.hash) {
1495
1554
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1496
1555
  }
1497
- const sig = await this.signForData(raw.sign_data);
1556
+ const sig = await this.signForData(raw.sign_data, {
1557
+ asset: option.asset,
1558
+ amount: x402AuthorizationAmount(option)
1559
+ });
1498
1560
  const execResult = await this.post(
1499
1561
  `/payments/${raw.payment_id}/sign`,
1500
1562
  { signature: sig }
@@ -1770,7 +1832,10 @@ var X402Erc7710 = class {
1770
1832
  ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
1771
1833
  // #1307/#1547: persisted so the settle leg can rehydrate the merchant
1772
1834
  // call by payment_id on this scheme too, not only on the 3009 bridge.
1773
- ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
1835
+ ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {},
1836
+ // #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`) — do not
1837
+ // switch this to `task_budget_id`, which is only the `POST /payments` key.
1838
+ ...options.taskBudgetId ? { taskBudgetId: options.taskBudgetId } : {}
1774
1839
  });
1775
1840
  if (!raw.payment_id) {
1776
1841
  throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
@@ -2416,7 +2481,7 @@ var HavenClient = class {
2416
2481
  x402Wallet: this.x402Wallet,
2417
2482
  chainRpcs: this.chainRpcs,
2418
2483
  post: (path, body) => this.post(path, body),
2419
- signForData: (signData) => this.signForData(signData),
2484
+ signForData: (signData, fundingExpectation) => this.signForData(signData, void 0, fundingExpectation),
2420
2485
  assertSignableAuthorizationState: (label, raw) => this.throwIfNonSignableAuthorizationState(label, raw)
2421
2486
  });
2422
2487
  this.merchantCompletion = new MerchantCompletion({
@@ -2493,7 +2558,8 @@ var HavenClient = class {
2493
2558
  token: request.token,
2494
2559
  amount: request.amount,
2495
2560
  to: request.to,
2496
- ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
2561
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {},
2562
+ ...request.taskBudgetId ? { task_budget_id: request.taskBudgetId } : {}
2497
2563
  });
2498
2564
  if (raw.status === "pending_approval") {
2499
2565
  throwPaymentStateError("Payment", raw);
@@ -2553,7 +2619,11 @@ var HavenClient = class {
2553
2619
  // the agent passes only payment_id. Bounded: the backend rejects >64KB,
2554
2620
  // so an oversized blob is omitted here (signer falls back to the
2555
2621
  // caller-supplied copy) rather than failing the intent.
2556
- ...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {}
2622
+ ...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {},
2623
+ // #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`), unlike
2624
+ // `POST /payments`'s snake_case `task_budget_id` above — the two
2625
+ // surfaces use different conventions, so this key is NOT `task_budget_id`.
2626
+ ...options.taskBudgetId ? { taskBudgetId: options.taskBudgetId } : {}
2557
2627
  });
2558
2628
  if (raw.status !== "pending_signature") {
2559
2629
  throwPaymentStateError("x402 payment", raw);
@@ -2625,7 +2695,7 @@ var HavenClient = class {
2625
2695
  * (#834) — the backend refuses those intents with HTTP 410 before any
2626
2696
  * sign_data reaches a client, so encountering it here is a hard error too.
2627
2697
  */
2628
- async signForData(signData, settlementExpectation) {
2698
+ async signForData(signData, settlementExpectation, fundingExpectation) {
2629
2699
  if (!this.delegateKey) {
2630
2700
  throw new HavenSigningError(
2631
2701
  "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
@@ -2648,6 +2718,13 @@ var HavenClient = class {
2648
2718
  signData.typed_data,
2649
2719
  this.delegateAddress
2650
2720
  );
2721
+ if (fundingExpectation) {
2722
+ assertFundingLegPaysDelegate(signData.typed_data, {
2723
+ delegateAddress: this.delegateAddress,
2724
+ asset: fundingExpectation.asset,
2725
+ amount: fundingExpectation.amount
2726
+ });
2727
+ }
2651
2728
  return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
2652
2729
  }
2653
2730
  if (scheme === "eip712_delegation") {
@@ -2939,6 +3016,96 @@ var HavenClient = class {
2939
3016
  async getReceipt(paymentId) {
2940
3017
  return this.accountReads.getReceipt(paymentId);
2941
3018
  }
3019
+ // ── Task Budgets (#3329) ────────────────────────────────────────────
3020
+ /**
3021
+ * Open a task budget: a self-delegated, time-boxed slice of the agent's own
3022
+ * budget delegation. Returns the pending row plus the child delegation's
3023
+ * `sign_data` — sign it (via `signForData` / `haven_sign` with
3024
+ * `task_budget_id`) and pass the signature to `submitTaskBudget` to make it
3025
+ * OPEN. Nothing is on-chain until then.
3026
+ */
3027
+ async openTaskBudget(request) {
3028
+ const raw = await this.post("/task-budgets", {
3029
+ ...request.tokenAddress ? { token_address: request.tokenAddress } : {},
3030
+ max_amount_atomic: request.maxAmountAtomic,
3031
+ ttl_seconds: request.ttlSeconds,
3032
+ ...request.recipientAddress ? { recipient_address: request.recipientAddress } : {},
3033
+ ...request.label ? { label: request.label } : {}
3034
+ });
3035
+ return {
3036
+ taskBudget: mapTaskBudget(raw.task_budget),
3037
+ signData: raw.sign_data,
3038
+ nextAction: raw.next_action,
3039
+ instructions: raw.instructions
3040
+ };
3041
+ }
3042
+ /** Read one task budget by id. */
3043
+ async getTaskBudget(id) {
3044
+ const raw = await this.get(`/task-budgets/${encodeURIComponent(id)}`);
3045
+ return mapTaskBudget(raw.task_budget);
3046
+ }
3047
+ /** List this agent's task budgets — `status: 'open'` (default; unexpired only) or `'all'`. */
3048
+ async listTaskBudgets(options = {}) {
3049
+ const query = options.status ? `?status=${encodeURIComponent(options.status)}` : "";
3050
+ const raw = await this.get(`/task-budgets${query}`);
3051
+ return raw.task_budgets.map(mapTaskBudget);
3052
+ }
3053
+ /**
3054
+ * The exact signing payload for a pending (purpose `open`) or closing
3055
+ * (purpose `close`) task budget — the `task_budget_id` counterpart of
3056
+ * `getResumeState` / the payment sign-context reads. `haven_sign` with
3057
+ * `task_budget_id` fetches this itself; call it directly only for manual
3058
+ * signing.
3059
+ */
3060
+ async getTaskBudgetSignContext(id) {
3061
+ const raw = await this.get(`/task-budgets/${encodeURIComponent(id)}/sign-context`);
3062
+ if (raw.purpose === "close") {
3063
+ return {
3064
+ taskBudgetId: raw.task_budget_id,
3065
+ purpose: "close",
3066
+ taskSignContextVersion: raw.task_sign_context_version,
3067
+ typedData: raw.typed_data,
3068
+ userOperation: raw.user_operation,
3069
+ userOpHash: raw.user_op_hash,
3070
+ expected: raw.expected
3071
+ };
3072
+ }
3073
+ return {
3074
+ taskBudgetId: raw.task_budget_id,
3075
+ purpose: "open",
3076
+ taskSignContextVersion: raw.task_sign_context_version,
3077
+ typedData: raw.typed_data,
3078
+ expected: raw.expected
3079
+ };
3080
+ }
3081
+ /** Submit a signature for a pending (open) or closing task budget. */
3082
+ async submitTaskBudget(id, signature) {
3083
+ const raw = await this.post(
3084
+ `/task-budgets/${encodeURIComponent(id)}/submit`,
3085
+ { signature }
3086
+ );
3087
+ return {
3088
+ taskBudget: mapTaskBudget(raw.task_budget),
3089
+ status: raw.status,
3090
+ ...raw.close_tx_hash ? { closeTxHash: raw.close_tx_hash } : {}
3091
+ };
3092
+ }
3093
+ /**
3094
+ * Close a task budget early. A `pending` or already-expired `open` budget
3095
+ * closes immediately (nothing was ever signed or is on-chain). A live
3096
+ * `open` budget returns `sign_data` for an on-chain `disableDelegation`
3097
+ * UserOp — sign it (`haven_sign` with `task_budget_id`) and call
3098
+ * `submitTaskBudget`.
3099
+ */
3100
+ async closeTaskBudget(id) {
3101
+ const raw = await this.post(`/task-budgets/${encodeURIComponent(id)}/close`, {});
3102
+ return {
3103
+ taskBudget: mapTaskBudget(raw.task_budget),
3104
+ status: raw.status,
3105
+ ...raw.sign_data ? { signData: raw.sign_data } : {},
3106
+ ...raw.next_action ? { nextAction: raw.next_action } : {}
3107
+ };
3108
+ }
2942
3109
  /**
2943
3110
  * Rehydrate the x402 resume-state bundle for a payment id (#1328: the MPP
2944
3111
  * resume-state variant retired along with the rest of the mpp_demo surface).
@@ -2991,7 +3158,7 @@ var HavenClient = class {
2991
3158
  if (cached) return cached;
2992
3159
  const inFlight = this.inFlightX402.get(idempotencyKey);
2993
3160
  if (inFlight) return inFlight;
2994
- const promise = this.fundingLeg.authorize(paymentRequired, option, idempotencyKey);
3161
+ const promise = this.fundingLeg.authorize(paymentRequired, option, idempotencyKey, options.taskBudgetId);
2995
3162
  this.inFlightX402.set(idempotencyKey, promise);
2996
3163
  try {
2997
3164
  return await promise;
@@ -3073,7 +3240,7 @@ var HavenClient = class {
3073
3240
  async payX402Quote(quote, options = {}) {
3074
3241
  const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
3075
3242
  try {
3076
- const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
3243
+ const receipt = await this.authorizeX402(quote.paymentRequired, { ...options, idempotencyKey });
3077
3244
  return this.merchantCompletion.retryRequest(
3078
3245
  quote.request.url,
3079
3246
  requestInitFromSnapshot(quote.request),
@@ -3651,7 +3818,7 @@ var toolDescriptions = {
3651
3818
  send: {
3652
3819
  summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
3653
3820
  selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints \u2014 use haven_pay_x402 instead. Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances. Do NOT use to check whether funds are held before sending \u2014 use haven_check_funds.",
3654
- behavior: "Sends the amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves or any signing \u2014 never queued for a human to approve. The agent's signing key signs the account's typed data, refusing any that mismatches the payment's hash; Haven never receives the key.",
3821
+ behavior: "Sends the amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves or any signing \u2014 never queued for a human to approve. The agent's signing key signs the account's typed data; a current signer refuses any that does not match the payment's hash (USEROP_BINDING_MISMATCH), an older signer signs it and the account rejects it on-chain. Haven never receives the key.",
3655
3822
  nextActionGuidance: "On a decline, report the reason to the user and ask them to grant or raise the budget in Haven \u2014 there is nothing to poll and no approval will arrive. After a successful send, poll haven_get_payment_status until nextAction=none."
3656
3823
  },
3657
3824
  reportSettlementEvidence: {
@@ -3984,6 +4151,10 @@ Call \`haven_get_agent\`, one of the Haven MCP tools the connector wires into yo
3984
4151
 
3985
4152
  Nothing here needs you to. Steps 1-3 are links: hand your user the full \`<host>/\u2026\` URL and ask them to say when it is done. Step 5 is a link only when your run reported one in \`approval.url\` \u2014 otherwise it is the tab they already have open, as above. Then poll \`haven_get_agent\` until it reads \`ready\`. Do not route around the sign-in wall \u2014 it makes the account theirs, not yours.
3986
4153
 
4154
+ ## If something breaks
4155
+
4156
+ If a Haven result carries \`client_update\`, that client is out of date: update it \u2014 \`upgrade_command\` names the package and channel \u2014 then retry. \`required: true\` means payments are refused until you do. What changed: [/releases](/releases).
4157
+
3987
4158
  ## Vocabulary
3988
4159
 
3989
4160
  | Term | What it is |