@haven_ai/sdk 0.4.0-alpha.0 → 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,464 +1,13 @@
1
- import { recoverTypedDataAddress, hashTypedData } from 'viem';
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
+ import { hashTypedData } from 'viem';
2
6
  import { ethers } from 'ethers';
3
- import { createHash } from 'crypto';
4
7
  import { AsyncLocalStorage } from 'async_hooks';
5
8
  import { exact } from 'x402/schemes';
6
9
  import { privateKeyToAccount } from 'viem/accounts';
7
10
 
8
- // src/client.ts
9
-
10
- // src/connector-channel.ts
11
- var CONNECTOR_PACKAGE_NAME = "@haven_ai/connect";
12
- var HAVEN_CONNECTOR_CHANNEL = "alpha";
13
- var CHANNEL_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
14
- function isConnectorChannel(value) {
15
- return CHANNEL_PATTERN.test(value);
16
- }
17
- function resolveConnectorChannel(raw, fallback = HAVEN_CONNECTOR_CHANNEL) {
18
- const trimmed = (raw ?? "").trim();
19
- if (trimmed === "") return fallback;
20
- if (!isConnectorChannel(trimmed)) {
21
- throw new Error(
22
- `HAVEN_CONNECTOR_CHANNEL is set to ${JSON.stringify(raw)}, which is not a valid npm dist-tag (lowercase letter first, then letters, digits or hyphens). Refusing to start rather than fall back to the default channel, because falling back would hand out the production connector while looking configured.`
23
- );
24
- }
25
- return trimmed;
26
- }
27
- function connectorSpec(channel = HAVEN_CONNECTOR_CHANNEL) {
28
- return `${CONNECTOR_PACKAGE_NAME}@${channel}`;
29
- }
30
- function connectorRerunCommand(args, options) {
31
- const channel = options?.channel ?? HAVEN_CONNECTOR_CHANNEL;
32
- const flags = options?.npxFlags ? `${options.npxFlags} ` : "";
33
- const command = `npx ${flags}${connectorSpec(channel)}`;
34
- return args ? `${command} ${args}` : command;
35
- }
36
-
37
- // src/types.ts
38
- var DEFAULT_CONFIRMATION_TIMEOUT_MS = 9e4;
39
- var AgentPaymentPhase = {
40
- /** The agent must sign and submit the prepared payment before Haven can relay it. */
41
- AgentSignatureRequired: "agent_signature_required",
42
- /** Haven has received the signed payment and the agent should poll for confirmation. */
43
- PaymentSubmitted: "payment_submitted",
44
- /** The direct payment is confirmed; the agent does not need to do more for this payment id. */
45
- PaymentConfirmed: "payment_confirmed",
46
- /**
47
- * #2115: RETIRED wire value — no live rail produces it. It described the
48
- * Safe rail's approval queue, which no longer exists. Kept so a stored value
49
- * still typechecks; see `AgentPaymentPhaseDescriptions` below for the
50
- * agent-visible wording, which this comment used to contradict.
51
- */
52
- UserApprovalRequired: "user_approval_required",
53
- /** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user. */
54
- UserExecutionRequired: "user_execution_required",
55
- /** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user. */
56
- WaitingForAdditionalApprovals: "waiting_for_additional_approvals",
57
- /** The Haven funding leg was sent; the agent can continue the merchant/protocol leg. */
58
- FundingSent: "funding_sent",
59
- /** The payment was rejected and cannot proceed; the agent should stop and tell the user. */
60
- Rejected: "rejected",
61
- /** The payment expired before completion. */
62
- Expired: "expired",
63
- /** Haven could not complete the payment; the agent should stop and surface the failure. */
64
- Failed: "failed",
65
- /**
66
- * Pre-flight check determined the delegate's existing balance plus the
67
- * remaining on-chain budget cannot cover the requested amount, so no
68
- * payment intent was created. The account must be funded or the agent's
69
- * budget raised before retrying — #2115: the old wording contrasted this
70
- * with `UserApprovalRequired` as if that were a live alternative, and named
71
- * the retired rail's Safe and per-token allowance as the fix.
72
- */
73
- InsufficientFunds: "insufficient_funds",
74
- /**
75
- * Haven's funding leg (account → delegate, the #946 EIP-3009 bridge)
76
- * confirmed on-chain, but the merchant rejected the x402 retry. The delegate
77
- * wallet may hold stranded USDC that was never settled to the merchant. The
78
- * agent should stop, tell the user, and wait for the sweep flow to reclaim
79
- * the funds.
80
- */
81
- FundedButUnsettled: "funded_but_unsettled"
82
- };
83
- var AgentPaymentNextAction = {
84
- /** Sign with the delegate key and submit the payment to Haven. */
85
- SignAndSubmitPayment: "sign_and_submit_payment",
86
- /** Poll getPaymentStatus later using this payment id. */
87
- CheckStatusLater: "check_status_later",
88
- /** No further agent action is required for this payment id. */
89
- None: "none",
90
- /**
91
- * #2115: RETIRED wire value — no live rail produces it and nothing maps to
92
- * it. Stop and tell the user rather than polling; no approval will arrive.
93
- */
94
- WaitForUserApproval: "wait_for_user_approval",
95
- /** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user rather than polling. */
96
- WaitForUserToCompletePayment: "wait_for_user_to_complete_payment",
97
- /** Resume this payment id and retry the original x402 request with the merchant payment header. */
98
- RetryOriginalX402Request: "retry_original_x402_request",
99
- /** Stop retrying this payment and tell the user what happened. */
100
- StopAndTellUser: "stop_and_tell_user",
101
- /** Ask again only if the user still wants the payment after expiry. */
102
- RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it",
103
- /** #1307: retry the SAME tool call, supplying the explicit context fields the server could not rehydrate. */
104
- RetryWithExplicitContext: "retry_with_explicit_context",
105
- /**
106
- * The x402 funding/quote window expired. Re-quote the same logical merchant
107
- * operation with the same idempotency key to stay double-charge-safe.
108
- */
109
- PaymentWindowExpired: "payment_window_expired",
110
- /**
111
- * Stop and tell the user that the originating account needs to be funded or
112
- * the agent's per-token allowance needs to be raised before the payment
113
- * can succeed. A user approval will not fix this state on its own.
114
- *
115
- * #2914: the account-vocabulary spelling, and the only one — the
116
- * pre-#2907 `fund_safe_or_raise_allowance` wire value (and the
117
- * `AgentPaymentNextActionAccountAlias` seam #2908 added to bridge it) are
118
- * retired along with the rest of the #2908 compatibility window.
119
- */
120
- FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance",
121
- /**
122
- * The delegate wallet may hold funds that were sent from the Safe but never
123
- * settled to the merchant. The wallet owner should initiate a sweep to
124
- * return those funds to the originating Safe.
125
- */
126
- SweepStrandedFunds: "sweep_stranded_funds",
127
- /**
128
- * #2970: a `submitted` erc7710 x402 intent whose settlement window has
129
- * passed with no on-chain settlement evidence Haven could verify. Distinct
130
- * from {@link CheckStatusLater}, which this REPLACES once the window is
131
- * past — but it is not futile: Haven's settlement sweep (120s tick) scans
132
- * each candidate over its own window plus a 120s clock-skew allowance, so
133
- * it can still attribute the settlement for a short while after this value
134
- * first appears. Poll {@link CheckStatusLater}'s tool
135
- * (`haven_get_payment_status`) once more, roughly two minutes later; if it
136
- * still shows no evidence, tell the user the goods were delivered but
137
- * Haven holds no verified settlement evidence for this payment. If the
138
- * agent holds the merchant's real settlement transaction hash (from
139
- * `PAYMENT-RESPONSE`'s `transaction` field, or a prior settle/complete
140
- * result's `settlement_tx_hash`), report it with the hosted
141
- * `haven_report_settlement_evidence` tool instead of waiting —
142
- * `haven_report_x402_outcome` takes no hash and refuses a non-`confirmed`
143
- * intent.
144
- */
145
- AwaitingSettlementEvidence: "awaiting_settlement_evidence"
146
- };
147
- var AgentPaymentFailureCode = {
148
- /** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
149
- PriceExceedsMax: "PRICE_EXCEEDS_MAX",
150
- /** The x402 funding/quote window expired before the signer or hosted settle step could finish. */
151
- PaymentWindowExpired: "PAYMENT_WINDOW_EXPIRED",
152
- /** The merchant rejected the paid retry. On eip3009 the funding leg had succeeded (sweep);
153
- * on erc7710 there is no funding leg — nothing to sweep, follow the message (#2983). */
154
- MerchantRejectedAfterFunding: "MERCHANT_REJECTED_AFTER_FUNDING",
155
- /** #1300 review: funding is on-chain but the merchant never ANSWERED the
156
- * paid retry within the timeout. NOT proof of rejection — the merchant
157
- * may still settle late, so the guidance is verify-then-act. On eip3009
158
- * the funding leg had succeeded (verify-then-sweep); on erc7710 there is
159
- * no funding leg — nothing to sweep, follow the message (#3000). */
160
- MerchantUnresponsiveAfterFunding: "MERCHANT_UNRESPONSIVE_AFTER_FUNDING",
161
- /**
162
- * #1307: the caller omitted merchant_url/tool_name (asking Haven to
163
- * rehydrate the stored MCP merchant-call context by payment_id), but no
164
- * usable context was stored for this intent — either it was never an
165
- * MCP-tool quote, or the stored context is incomplete. The fallback is
166
- * mechanical: re-send merchant_url, tool_name, arguments, and
167
- * mcp_transport explicitly (the version-skew path).
168
- */
169
- MerchantCallContextUnavailable: "MERCHANT_CALL_CONTEXT_UNAVAILABLE",
170
- /**
171
- * #1351: the caller supplied BOTH the atomic `max_amount` and the
172
- * human-denominated `max_amount_human` cap for one purchase. Haven refuses
173
- * to guess which the user meant — the two differ by a factor of 10^decimals,
174
- * so picking wrong is exactly the silent-overspend this cap exists to
175
- * prevent. Rejected before any merchant probe, funding intent, or signature.
176
- */
177
- AmbiguousMaxAmount: "AMBIGUOUS_MAX_AMOUNT",
178
- /**
179
- * #1351: a human-denominated cap was supplied, but it cannot be converted to
180
- * atomic units against THIS quote — either the quote's asset has no known
181
- * decimals on its network, or the cap carries more fraction digits than the
182
- * asset can represent (truncating it would silently change the user's cap).
183
- * The fallback is the exact atomic `max_amount`.
184
- */
185
- MaxAmountUnconvertible: "MAX_AMOUNT_UNCONVERTIBLE",
186
- /**
187
- * #2979: the merchant answered a `tools/call` probe with its own
188
- * machine-readable "cannot settle right now" refusal (HTTP 503,
189
- * `{ error: 'merchant_not_ready', reason_code, ... }`) instead of a 402
190
- * challenge — e.g. its settlement wallet is out of gas. No 402 was ever
191
- * issued and no payment was created; this is honest and (per
192
- * `retry_after_s`, when present) usually transient, unlike a permanent
193
- * endpoint miss.
194
- */
195
- MerchantNotReady: "MERCHANT_NOT_READY"
196
- };
197
- var AgentPaymentRail = {
198
- /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
199
- Direct: "direct",
200
- /** x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg. */
201
- X402: "x402",
202
- /** Machine Payment Protocol family — categorical value used as a resume-state discriminator. */
203
- Mpp: "mpp",
204
- /** Haven internal MPP demo rail. Not for production traffic. */
205
- MppDemo: "mpp_demo",
206
- /** Crypto-settled MPP rail. */
207
- MppCrypto: "mpp_crypto",
208
- /** Stripe-deposit-backed MPP rail. */
209
- StripeDeposit: "stripe_deposit",
210
- /** Stripe Payment Token MPP rail. */
211
- Spt: "spt"
212
- };
213
- var AGENT_PAYMENT_PHASE_VALUES = Object.values(AgentPaymentPhase);
214
- var AGENT_PAYMENT_NEXT_ACTION_VALUES = Object.values(AgentPaymentNextAction);
215
- var AGENT_PAYMENT_FAILURE_CODE_VALUES = Object.values(AgentPaymentFailureCode);
216
- var AGENT_PAYMENT_RAIL_VALUES = Object.values(AgentPaymentRail);
217
- var AgentPaymentPhaseDescriptions = {
218
- [AgentPaymentPhase.AgentSignatureRequired]: "The agent must sign and submit the prepared payment before Haven can relay it.",
219
- [AgentPaymentPhase.PaymentSubmitted]: "Haven has received the signed payment and the agent should poll for confirmation.",
220
- [AgentPaymentPhase.PaymentConfirmed]: "The direct payment is confirmed; the agent does not need to do more for this payment id.",
221
- [AgentPaymentPhase.UserApprovalRequired]: "Retired wire value: no live rail produces it. It described the Safe rail's approval queue, which no longer exists \u2014 an out-of-policy payment is declined before any money moves. If it is ever seen, stop and tell the user; no approval is pending.",
222
- [AgentPaymentPhase.UserExecutionRequired]: "Retired wire value: no live rail produces it. Stop and tell the user.",
223
- [AgentPaymentPhase.WaitingForAdditionalApprovals]: "Retired wire value: no live rail produces it. Stop and tell the user.",
224
- [AgentPaymentPhase.FundingSent]: "The Haven funding leg was sent; the agent can continue the merchant/protocol leg.",
225
- [AgentPaymentPhase.Rejected]: "The payment was rejected and cannot proceed; the agent should stop and tell the user.",
226
- [AgentPaymentPhase.Expired]: "The payment expired before completion.",
227
- [AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure.",
228
- [AgentPaymentPhase.InsufficientFunds]: "Pre-flight check determined the delegate balance plus the remaining on-chain budget cannot cover the requested amount, so no payment was created. The account must be funded or the agent budget raised before retrying.",
229
- [AgentPaymentPhase.FundedButUnsettled]: "Haven's funding leg confirmed on-chain but the merchant rejected the x402 retry. The delegate wallet may hold stranded funds. The agent should stop and wait for the wallet owner to sweep the stranded funds back to the account."
230
- };
231
- var AgentPaymentNextActionDescriptions = {
232
- [AgentPaymentNextAction.SignAndSubmitPayment]: "Sign with the delegate key and submit the payment to Haven.",
233
- [AgentPaymentNextAction.CheckStatusLater]: "Poll getPaymentStatus later using this payment id.",
234
- [AgentPaymentNextAction.None]: "No further agent action is required for this payment id.",
235
- [AgentPaymentNextAction.WaitForUserApproval]: "Retired wire value: no live rail produces it, and nothing maps to it. It described a per-payment approval queue that no longer exists. If it is ever seen, stop and tell the user rather than polling \u2014 no approval will arrive.",
236
- [AgentPaymentNextAction.WaitForUserToCompletePayment]: "Retired wire value: no live rail produces it. Stop and tell the user rather than polling.",
237
- [AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
238
- [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
239
- [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
240
- [AgentPaymentNextAction.PaymentWindowExpired]: "The x402 funding/quote window expired. Re-quote with the same idempotency key before asking the signer to build a merchant payment header again.",
241
- [AgentPaymentNextAction.FundAccountOrRaiseAllowance]: "Stop and tell the user that the account needs to be funded or the agent budget raised before the payment can succeed.",
242
- [AgentPaymentNextAction.RetryWithExplicitContext]: "Retry the same tool call, this time passing merchant_url, tool_name, arguments, and mcp_transport explicitly \u2014 the server had no stored context to rehydrate for this payment id.",
243
- [AgentPaymentNextAction.SweepStrandedFunds]: "Tell the user that funds may be stranded in the delegate wallet and prompt them to initiate a sweep in Haven to return them to the originating account.",
244
- [AgentPaymentNextAction.AwaitingSettlementEvidence]: "The settlement window passed with no verified on-chain evidence yet. If you hold the merchant's real settlement transaction hash, report it with haven_report_settlement_evidence. Otherwise, Haven's settlement sweep may still attribute it within about two minutes \u2014 poll getPaymentStatus once more, then tell the user the goods were delivered but unverified if it still shows nothing."
245
- };
246
- var AgentPaymentFailureCodeDescriptions = {
247
- [AgentPaymentFailureCode.PriceExceedsMax]: "The merchant-authoritative x402 amount exceeds the caller's max_amount cap. No funding transfer was created; ask the user before retrying with a larger cap.",
248
- [AgentPaymentFailureCode.PaymentWindowExpired]: "The x402 funding/quote window expired before the signer or hosted settle step could finish. Re-quote via haven_pay_mcp_tool with the same idempotency key to avoid duplicate funding.",
249
- [AgentPaymentFailureCode.MerchantRejectedAfterFunding]: "The merchant rejected the paid retry. eip3009: the funding leg succeeded \u2014 stop retrying the merchant and reconcile stranded delegate funds with haven_sweep_delegate. erc7710: no funding leg, nothing to sweep \u2014 follow the message (re-quote later, or check haven_get_payment_status after the window first).",
250
- [AgentPaymentFailureCode.MerchantUnresponsiveAfterFunding]: "The merchant did not answer the paid retry before the timeout. The merchant may still settle late. eip3009: the funding leg succeeded \u2014 check haven_get_payment_status, retry haven_complete_mcp_tool once, sweep only if no settlement appears. erc7710: no funding leg, nothing to sweep, and haven_complete_mcp_tool has no erc7710 branch \u2014 do not retry it; check haven_get_payment_status after the payment window and re-quote only if it shows no settlement.",
251
- [AgentPaymentFailureCode.MerchantCallContextUnavailable]: "merchant_url/tool_name were omitted and no stored merchant call context is available for this payment_id. Re-send merchant_url, tool_name, arguments, and mcp_transport explicitly.",
252
- [AgentPaymentFailureCode.AmbiguousMaxAmount]: "Both max_amount (atomic units) and max_amount_human (whole tokens) were supplied for one purchase. Nothing was contacted and nothing was spent. Re-send with exactly ONE: max_amount_human for a cap the user stated in tokens, max_amount for an exact atomic figure.",
253
- [AgentPaymentFailureCode.MaxAmountUnconvertible]: "max_amount_human could not be converted to atomic units against this quote's asset \u2014 either its decimals are unknown to Haven or the cap has more decimal places than the asset supports. Nothing was spent. Round the cap, or re-send it as an exact atomic max_amount.",
254
- [AgentPaymentFailureCode.MerchantNotReady]: 'The merchant refused the probe with its own "cannot settle right now" signal instead of a 402 challenge. No payment was created. Often transient \u2014 retry later (see retry_after_s in the message, if given) rather than treating this as a broken or wrong endpoint.'
255
- };
256
- var AgentPaymentWarningCode = {
257
- /** No max_amount cap was supplied — the live quoted price was accepted as-is. */
258
- MissingMaxAmount: "MISSING_MAX_AMOUNT",
259
- /** The signing window closes soon; sign promptly or re-quote with the same idempotency key. */
260
- QuoteExpiresSoon: "QUOTE_EXPIRES_SOON",
261
- /** The merchant URL was resolved via discovery — pass the RESOLVED url forward. */
262
- MerchantUrlDiscovered: "MERCHANT_URL_DISCOVERED",
263
- /**
264
- * #1306: the catalog's last-verified price_atomic differs from the LIVE
265
- * merchant quote for a guided catalog purchase. The catalog price is only
266
- * ever indicative; the live quote in the same response is authoritative.
267
- */
268
- CatalogPriceDiffers: "CATALOG_PRICE_DIFFERS",
269
- /**
270
- * #1306: the rail-aware allowance/budget pre-check could not be read (RPC
271
- * failure, etc). `sufficient` is reported as null rather than a fabricated
272
- * true/false — the on-chain policy remains the actual gate either way.
273
- */
274
- AllowanceCheckUnavailable: "ALLOWANCE_CHECK_UNAVAILABLE",
275
- /**
276
- * #1319: the delegation-rail read itself SUCCEEDED, but the remaining
277
- * figure it returned is the #1145 fallback (the full configured budget)
278
- * rather than a live ERC20PeriodTransferEnforcer read — `sufficient` is a
279
- * real true/false, just computed from an optimistic number. Distinct from
280
- * {@link AgentPaymentWarningCode.AllowanceCheckUnavailable}, which fires
281
- * when the read failed outright and `sufficient` degrades to null. The
282
- * on-chain policy re-checks at redemption either way; this only says the
283
- * guidance shown here may be optimistic.
284
- */
285
- AllowanceReadOptimistic: "ALLOWANCE_READ_OPTIMISTIC",
286
- /**
287
- * #2991: the quote tools' `expected_settlement_scheme` prediction of what
288
- * `haven_prepare_catalog_purchase` / `haven_pay_mcp_tool` will actually
289
- * select could not be computed — the agent's execution rail could not be
290
- * read from Haven, so `expected_settlement_scheme` is `null` rather than a
291
- * guess. `accepted_scheme` (the merchant's offer) is unaffected.
292
- */
293
- X402SchemeUnknown: "X402_SCHEME_UNKNOWN",
294
- /**
295
- * #2968: the merchant answered 200 and handed over goods, but Haven holds NO
296
- * on-chain evidence that the payment moved. `settled: false` beside this code
297
- * is not a failure — it is the absence of proof, and the two must travel
298
- * together so an agent can tell "the user has the goods" apart from "the
299
- * money moved". Carries the intent's `expires_at`: after that instant the
300
- * settlement can no longer land at all.
301
- */
302
- SettlementUnconfirmed: "SETTLEMENT_UNCONFIRMED"
303
- };
304
- var AgentPaymentRailDescriptions = {
305
- [AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled account, redeeming the agent's on-chain budget delegation.",
306
- [AgentPaymentRail.X402]: "x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg.",
307
- [AgentPaymentRail.Mpp]: "Categorical MPP rail value used as a resume-state discriminator. Response bodies carry a granular mpp_* value instead.",
308
- [AgentPaymentRail.MppDemo]: "Haven internal MPP demo rail. Not for production traffic.",
309
- [AgentPaymentRail.MppCrypto]: "Crypto-settled MPP rail.",
310
- [AgentPaymentRail.StripeDeposit]: "Stripe-deposit-backed MPP rail.",
311
- [AgentPaymentRail.Spt]: "Stripe Payment Token MPP rail."
312
- };
313
- var AgentPaymentPhaseSchema = {
314
- type: "string",
315
- enum: AGENT_PAYMENT_PHASE_VALUES,
316
- description: "Stable Haven agent payment state phase.",
317
- "x-enumDescriptions": AgentPaymentPhaseDescriptions
318
- };
319
- var AgentPaymentNextActionSchema = {
320
- type: "string",
321
- enum: AGENT_PAYMENT_NEXT_ACTION_VALUES,
322
- description: "Stable next action an agent should take for a Haven payment state.",
323
- "x-enumDescriptions": AgentPaymentNextActionDescriptions
324
- };
325
- var AgentPaymentFailureCodeSchema = {
326
- type: "string",
327
- enum: AGENT_PAYMENT_FAILURE_CODE_VALUES,
328
- description: "Stable machine-readable failure codes for Haven agent payment recovery paths.",
329
- "x-enumDescriptions": AgentPaymentFailureCodeDescriptions
330
- };
331
- var AgentPaymentRailSchema = {
332
- type: "string",
333
- enum: AGENT_PAYMENT_RAIL_VALUES,
334
- description: "Stable rail identifier for Haven agent payment states.",
335
- "x-enumDescriptions": AgentPaymentRailDescriptions
336
- };
337
- var HavenError = class extends Error {
338
- constructor(message, code, statusCode, paymentId) {
339
- super(message);
340
- this.code = code;
341
- this.statusCode = statusCode;
342
- this.paymentId = paymentId;
343
- this.name = "HavenError";
344
- }
345
- code;
346
- statusCode;
347
- paymentId;
348
- };
349
- var HavenApiError = class extends HavenError {
350
- constructor(message, statusCode, body, paymentId) {
351
- super(message, "API_ERROR", statusCode, paymentId);
352
- this.body = body;
353
- this.name = "HavenApiError";
354
- }
355
- body;
356
- };
357
- var MerchantTimeoutError = class extends HavenApiError {
358
- merchantErrorCode = "merchant_timeout";
359
- constructor(message) {
360
- super(message, 504);
361
- this.name = "MerchantTimeoutError";
362
- }
363
- };
364
- var X402UnexpectedStatusError = class extends HavenApiError {
365
- x402ErrorCode = "unexpected_non_402_status";
366
- /**
367
- * #2979: `body` is the merchant's own JSON, when the non-402 response
368
- * carried one — e.g. the demo merchant's `/mcp` readiness gate answers
369
- * `503 { error: 'merchant_not_ready', reason_code, ... }`. Optional and
370
- * best-effort: a non-JSON or unreadable body leaves this `undefined`, same
371
- * as before this field existed. Consumers key on it (not on the message
372
- * string) to distinguish an honest, machine-readable merchant refusal from
373
- * a genuine "this is not the x402 endpoint" miss, which otherwise look
374
- * identical — both are just "some non-402 status".
375
- */
376
- constructor(message, statusCode, body) {
377
- super(message, statusCode, body);
378
- this.name = "X402UnexpectedStatusError";
379
- }
380
- };
381
- var X402AlreadySettledError = class extends HavenApiError {
382
- constructor(message, receipt, basis) {
383
- super(message, 409, void 0, receipt.paymentId);
384
- this.receipt = receipt;
385
- this.basis = basis;
386
- this.name = "X402AlreadySettledError";
387
- }
388
- receipt;
389
- basis;
390
- x402ErrorCode = "already_settled";
391
- };
392
- var HavenPaymentStateError = class extends HavenApiError {
393
- constructor(message, statusCode, state, body) {
394
- super(message, statusCode, body, state.paymentId);
395
- this.state = state;
396
- this.name = "HavenPaymentStateError";
397
- }
398
- state;
399
- resumeState;
400
- get status() {
401
- return this.state.status;
402
- }
403
- get phase() {
404
- return this.state.phase;
405
- }
406
- get nextAction() {
407
- return this.state.nextAction;
408
- }
409
- };
410
- var HavenSigningError = class extends HavenError {
411
- constructor(message) {
412
- super(message, "SIGNING_ERROR");
413
- this.name = "HavenSigningError";
414
- }
415
- };
416
- var HavenZeroSettlementHashError = class extends HavenError {
417
- constructor(paymentId) {
418
- super(
419
- "settlement_tx_hash is the zero hash (0x00\u202600), which is never a real settlement transaction \u2014 refused before any report was sent.",
420
- "ZERO_SETTLEMENT_HASH",
421
- 400,
422
- paymentId
423
- );
424
- this.name = "HavenZeroSettlementHashError";
425
- }
426
- };
427
- var SignerRefusalCode = {
428
- /** `SUPPORTED_X402_EXPECTED_VERSIONS` in `@haven_ai/signer` does not include the received version. */
429
- UnsupportedExpectedContextVersion: "UNSUPPORTED_EXPECTED_CONTEXT_VERSION",
430
- /** `SUPPORTED_SWEEP_BINDING_VERSIONS` in `@haven_ai/signer` does not include the received version. */
431
- UnsupportedSweepBindingVersion: "UNSUPPORTED_SWEEP_BINDING_VERSION"
432
- };
433
- function signerUpdateFallback(channel = HAVEN_CONNECTOR_CHANNEL) {
434
- return `Update @haven_ai/signer by rerunning \`${connectorRerunCommand(void 0, { channel })}\`, which reinstalls the pinned MCP runtime, then retry the same signing call. Nothing was signed or spent \u2014 the quote or payment this version came from is unaffected and does not need to be re-quoted.`;
435
- }
436
- var SIGNER_UPDATE_FALLBACK = signerUpdateFallback();
437
- var HavenUnsupportedSignerVersionError = class extends HavenError {
438
- constructor(message, code, supportedVersions, receivedVersion, fallback) {
439
- super(message, code);
440
- this.supportedVersions = supportedVersions;
441
- this.receivedVersion = receivedVersion;
442
- this.fallback = fallback;
443
- this.name = "HavenUnsupportedSignerVersionError";
444
- }
445
- supportedVersions;
446
- receivedVersion;
447
- fallback;
448
- };
449
- var HavenTimeoutError = class extends HavenError {
450
- constructor(paymentId) {
451
- super(
452
- `Timed out waiting for payment ${paymentId} to confirm`,
453
- "TIMEOUT",
454
- void 0,
455
- paymentId
456
- );
457
- this.name = "HavenTimeoutError";
458
- }
459
- };
460
-
461
- // src/signer.ts
462
11
  function signHash(privateKey, hash) {
463
12
  try {
464
13
  const signingKey = new ethers.SigningKey(privateKey);
@@ -509,632 +58,43 @@ function verifySignature(hash, signature, expectedAddress) {
509
58
  return false;
510
59
  }
511
60
  }
512
-
513
- // src/base64.ts
514
- function normalizeBase64(value) {
515
- const standard = value.replace(/-/g, "+").replace(/_/g, "/");
516
- const remainder = standard.length % 4;
517
- return remainder === 0 ? standard : standard + "=".repeat(4 - remainder);
518
- }
519
- function encodeBase64Utf8(value) {
520
- if (typeof Buffer !== "undefined") {
521
- return Buffer.from(value, "utf8").toString("base64");
522
- }
523
- const bytes = new TextEncoder().encode(value);
524
- let binary = "";
525
- for (let i = 0; i < bytes.length; i++) {
526
- binary += String.fromCharCode(bytes[i]);
527
- }
528
- return btoa(binary);
529
- }
530
- function decodeBase64Utf8(value) {
531
- const normalized = normalizeBase64(value);
532
- if (typeof Buffer !== "undefined") {
533
- return Buffer.from(normalized, "base64").toString("utf8");
534
- }
535
- const binary = atob(normalized);
536
- const bytes = new Uint8Array(binary.length);
537
- for (let i = 0; i < binary.length; i++) {
538
- bytes[i] = binary.charCodeAt(i);
539
- }
540
- return new TextDecoder().decode(bytes);
541
- }
542
- function encodeBase64Json(value) {
543
- return encodeBase64Utf8(JSON.stringify(value));
544
- }
545
- function decodeBase64Json(value, label) {
546
- try {
547
- return JSON.parse(decodeBase64Utf8(value));
548
- } catch (err) {
549
- if (label) throw new Error(`Failed to decode ${label}`);
550
- throw err;
551
- }
552
- }
553
-
554
- // src/sweep.ts
555
- var SWEEP_BASE_CHAIN_ID = 8453;
556
- var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
557
- var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
558
- var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
559
- var USDC_EIP712_DOMAIN_BY_CHAIN = {
560
- [SWEEP_BASE_CHAIN_ID]: {
561
- name: "USD Coin",
562
- version: "2",
563
- chainId: SWEEP_BASE_CHAIN_ID,
564
- verifyingContract: SWEEP_BASE_USDC_ADDRESS
565
- },
566
- [SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
567
- name: "USDC",
568
- version: "2",
569
- chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
570
- verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
571
- }
572
- };
573
- var USDC_ADDRESS_BY_CHAIN = {
574
- [SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
575
- [SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
576
- };
577
- var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
578
- function isSweepableChain(chainId) {
579
- return chainId in USDC_ADDRESS_BY_CHAIN;
580
- }
581
- var TRANSFER_WITH_AUTHORIZATION_TYPES = {
582
- TransferWithAuthorization: [
583
- { name: "from", type: "address" },
584
- { name: "to", type: "address" },
585
- { name: "value", type: "uint256" },
586
- { name: "validAfter", type: "uint256" },
587
- { name: "validBefore", type: "uint256" },
588
- { name: "nonce", type: "bytes32" }
589
- ]
590
- };
591
- function sweepUsdcAddress(chainId) {
592
- const address = USDC_ADDRESS_BY_CHAIN[chainId];
593
- if (!address) {
594
- throw new HavenSigningError(
595
- `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
596
- );
597
- }
598
- return address;
599
- }
600
- function sweepUsdcDomain(chainId) {
601
- const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
602
- if (!domain) {
603
- throw new HavenSigningError(
604
- `Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
605
- );
606
- }
607
- return domain;
608
- }
609
- function sameAddress(a, b) {
610
- return a.toLowerCase() === b.toLowerCase();
611
- }
612
- function buildSweepTypedData(auth) {
613
- const domain = sweepUsdcDomain(auth.chainId);
614
- const expectedToken = sweepUsdcAddress(auth.chainId);
615
- if (!sameAddress(auth.token, expectedToken)) {
616
- throw new HavenSigningError(
617
- `Sweep token ${auth.token} is not the canonical USDC contract for chain ${auth.chainId}.`
618
- );
619
- }
620
- if (!/^0x[0-9a-fA-F]{64}$/.test(auth.nonce)) {
621
- throw new HavenSigningError("Sweep nonce must be a 0x-prefixed 32-byte hex string.");
622
- }
623
- return {
624
- domain,
625
- types: TRANSFER_WITH_AUTHORIZATION_TYPES,
626
- primaryType: "TransferWithAuthorization",
627
- message: {
628
- from: auth.from,
629
- to: auth.to,
630
- value: BigInt(auth.value),
631
- validAfter: BigInt(auth.validAfter),
632
- validBefore: BigInt(auth.validBefore),
633
- nonce: auth.nonce
634
- }
635
- };
636
- }
637
- function buildSweepAuthorizationMessage(auth) {
638
- return `Haven sweep authorization v1
639
- ${stableStringify({
640
- version: 1,
641
- kind: "haven.sweep.authorization",
642
- from: auth.from.toLowerCase(),
643
- to: auth.to.toLowerCase(),
644
- value: auth.value,
645
- validAfter: auth.validAfter,
646
- validBefore: auth.validBefore,
647
- nonce: auth.nonce.toLowerCase(),
648
- token: auth.token.toLowerCase(),
649
- chainId: auth.chainId
650
- })}`;
651
- }
652
- function stableStringify(value) {
653
- if (value === null || typeof value !== "object") return JSON.stringify(value);
654
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
655
- const object = value;
656
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
657
- }
658
-
659
- // src/x402.ts
660
- var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
661
- var BASE_SEPOLIA_USDC_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
662
- var STANDARD_X402_USDC_ADDRESSES = /* @__PURE__ */ new Set([BASE_USDC_ADDRESS, BASE_SEPOLIA_USDC_ADDRESS]);
663
- var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
664
- var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
665
- var X402_PAYMENT_HEADER_MAX_LENGTH = 65536;
666
- var BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
667
- var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
668
- var SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
669
- var NONCE_RE = /^0x[0-9a-fA-F]{64}$/;
670
- var X402PaymentHeaderValidationError = class extends Error {
671
- constructor() {
672
- super("Invalid X-PAYMENT header.");
673
- this.name = "X402PaymentHeaderValidationError";
674
- }
675
- };
676
- function isPositiveDecimalAtomicAmount(value) {
677
- return DECIMAL_ATOMIC_AMOUNT_RE.test(value) && BigInt(value) > 0n;
678
- }
679
- function optionAuthorizationAmount(option) {
680
- return option.maxAmountRequired ?? option.amount;
681
- }
682
- var X402_MAX_AUTHORIZATION_WINDOW_SECONDS = 600;
683
- var X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = 300;
684
- function clampAuthorizationWindow(seconds) {
685
- const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
686
- return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
687
- }
688
- function normalizePaymentOption(value) {
689
- const candidate = value;
690
- if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
691
- return null;
692
- }
693
- const amount = typeof candidate.amount === "string" ? candidate.amount : typeof candidate.maxAmountRequired === "string" ? candidate.maxAmountRequired : null;
694
- if (!amount) return null;
695
- if (!isPositiveDecimalAtomicAmount(amount)) return null;
696
- if (candidate.maxAmountRequired !== void 0 && (typeof candidate.maxAmountRequired !== "string" || !isPositiveDecimalAtomicAmount(candidate.maxAmountRequired))) {
697
- return null;
698
- }
699
- return {
700
- scheme: candidate.scheme,
701
- network: candidate.network,
702
- amount,
703
- maxAmountRequired: candidate.maxAmountRequired,
704
- resource: candidate.resource,
705
- description: candidate.description,
706
- mimeType: candidate.mimeType,
707
- asset: candidate.asset,
708
- payTo: candidate.payTo,
709
- // Keep the merchant offer intact for v2 `accepted` matching. Only the
710
- // signing conversion may cap its authorization lifetime (#3117).
711
- // Retain legacy fallback behavior for missing or unusable timeouts.
712
- // Integer, because the authorize body types it `integer` and the child's
713
- // timestamp caveat is built from it — the old clamp floored, so keeping a
714
- // fractional value here would 400 a merchant that used to work (#3117).
715
- maxTimeoutSeconds: typeof candidate.maxTimeoutSeconds === "number" && Number.isFinite(candidate.maxTimeoutSeconds) && candidate.maxTimeoutSeconds >= 1 ? Math.floor(candidate.maxTimeoutSeconds) : clampAuthorizationWindow(candidate.maxTimeoutSeconds),
716
- extra: candidate.extra
717
- };
718
- }
719
- function normalizePaymentRequired(value) {
720
- const candidate = value;
721
- if (!candidate || typeof candidate !== "object" || typeof candidate.x402Version !== "number" || !Array.isArray(candidate.accepts)) {
722
- return null;
723
- }
724
- const accepts = candidate.accepts.map((option) => normalizePaymentOption(option)).filter((option) => !!option && typeof option === "object");
725
- if (accepts.length === 0) return null;
726
- const first = accepts[0];
727
- const resourceUrl = candidate.resource?.url ?? first.resource;
728
- if (!resourceUrl) return null;
729
- const resource = {
730
- ...candidate.resource && typeof candidate.resource === "object" ? candidate.resource : {},
731
- url: resourceUrl,
732
- description: candidate.resource?.description ?? first.description,
733
- mimeType: candidate.resource?.mimeType ?? first.mimeType
734
- };
735
- return {
736
- x402Version: candidate.x402Version,
737
- resource,
738
- accepts,
739
- error: candidate.error,
740
- ...candidate.extensions && typeof candidate.extensions === "object" ? { extensions: candidate.extensions } : {}
741
- };
742
- }
743
- var SUPPORTED_X402_NETWORKS = {
744
- "eip155:100": "Gnosis Chain",
745
- "eip155:8453": "Base",
746
- "base": "Base",
747
- "eip155:84532": "Base Sepolia",
748
- "base-sepolia": "Base Sepolia"
749
- };
750
- var STANDARD_X402_NETWORKS = {
751
- "eip155:8453": "base",
752
- "base": "base",
753
- "eip155:84532": "base-sepolia",
754
- "base-sepolia": "base-sepolia"
755
- };
756
- var GNOSIS_TOKENS = {
757
- "0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
758
- "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { symbol: "EURe", decimals: 18 },
759
- "0x2a22f9c3b484c3629090feed35f17ff8f88f76f0": { symbol: "USDC.e", decimals: 6 }
760
- };
761
- var BASE_TOKENS = {
762
- "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
763
- "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
764
- };
765
- var BASE_SEPOLIA_TOKENS = {
766
- "0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
767
- "0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
768
- };
769
- var ALL_TOKENS = {
770
- ...GNOSIS_TOKENS,
771
- ...BASE_TOKENS,
772
- ...BASE_SEPOLIA_TOKENS
773
- };
774
- var NETWORK_TOKENS = {
775
- "eip155:100": GNOSIS_TOKENS,
776
- "eip155:8453": BASE_TOKENS,
777
- "base": BASE_TOKENS,
778
- "eip155:84532": BASE_SEPOLIA_TOKENS,
779
- "base-sepolia": BASE_SEPOLIA_TOKENS
780
- };
781
- var X402_PAYMENT_HEADER_NAME = "PAYMENT-SIGNATURE";
782
- var X402_LEGACY_PAYMENT_HEADER_NAME = "X-PAYMENT";
783
- var X402_PAYMENT_REQUIRED_HEADER_NAME = "PAYMENT-REQUIRED";
784
- var X402_PAYMENT_RESPONSE_HEADER_NAME = "PAYMENT-RESPONSE";
785
- var X402_PAYMENT_HEADER_NAMES_SENT = `${X402_PAYMENT_HEADER_NAME}, ${X402_LEGACY_PAYMENT_HEADER_NAME}`;
786
- var X402_PAYMENT_HEADER_NAMES = [
787
- X402_PAYMENT_HEADER_NAME,
788
- X402_LEGACY_PAYMENT_HEADER_NAME
789
- ];
790
- function x402PaymentHeaderNamesFor(paymentHeader) {
791
- const both = [X402_PAYMENT_HEADER_NAME, X402_LEGACY_PAYMENT_HEADER_NAME];
792
- let decoded;
793
- try {
794
- decoded = decodeBase64Json(paymentHeader);
795
- } catch {
796
- return both;
797
- }
798
- const accepted = decoded?.accepted;
799
- if (!accepted || typeof accepted !== "object" || Array.isArray(accepted)) return both;
800
- return isErc7710Option(accepted) ? [X402_PAYMENT_HEADER_NAME] : both;
801
- }
802
- function x402PaymentHeaderNamesSent(paymentHeader) {
803
- return x402PaymentHeaderNamesFor(paymentHeader).join(", ");
804
- }
805
- function x402V2PaymentEnvelope(paymentRequired, accepted, payload) {
806
- const resource = paymentRequired.resource;
807
- const extensions = paymentRequired.extensions;
808
- return {
809
- x402Version: paymentRequired.x402Version,
810
- ...resource && typeof resource === "object" && !Array.isArray(resource) ? { resource } : {},
811
- accepted,
812
- payload,
813
- ...extensions && typeof extensions === "object" && !Array.isArray(extensions) ? { extensions } : {}
814
- };
815
- }
816
- function parsePaymentRequired(response) {
817
- const v2Header = response.headers.get("PAYMENT-REQUIRED");
818
- if (v2Header) {
819
- const parsed = normalizePaymentRequired(
820
- decodeBase64Json(v2Header, "PAYMENT-REQUIRED header")
821
- );
822
- if (parsed) return parsed;
823
- }
824
- const v1Header = response.headers.get("X-PAYMENT");
825
- if (v1Header) {
826
- const parsed = normalizePaymentRequired(
827
- decodeBase64Json(v1Header, "X-PAYMENT header")
828
- );
829
- if (parsed) return parsed;
830
- }
831
- throw new Error(
832
- "No x402 payment headers found in 402 response. Expected PAYMENT-REQUIRED (v2) or X-PAYMENT (v1) header."
833
- );
834
- }
835
- async function parsePaymentRequiredResponse(response) {
836
- try {
837
- return parsePaymentRequired(response);
838
- } catch (headerErr) {
839
- try {
840
- const body = await response.clone().json();
841
- const parsed = normalizePaymentRequired(body);
842
- if (parsed) return parsed;
843
- } catch {
844
- }
845
- throw headerErr;
846
- }
847
- }
848
- function selectPaymentOption(accepts) {
849
- if (!accepts || accepts.length === 0) return null;
850
- for (const opt of accepts) {
851
- if (opt.network in SUPPORTED_X402_NETWORKS) {
852
- const networkTokens = NETWORK_TOKENS[opt.network];
853
- if (networkTokens?.[opt.asset.toLowerCase()] && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
854
- return opt;
855
- }
856
- }
857
- }
858
- for (const opt of accepts) {
859
- if (opt.network in SUPPORTED_X402_NETWORKS && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
860
- return opt;
861
- }
862
- }
863
- return null;
864
- }
865
- var ERC7710_ASSET_TRANSFER_METHOD = "erc7710";
866
- function isEip3009ConstructibleOption(option) {
867
- const rawMethod = option.extra?.assetTransferMethod;
868
- if (rawMethod !== void 0 && rawMethod !== "eip3009") return false;
869
- const rawFlow = option.extra?.paymentFlow;
870
- if (rawFlow !== void 0 && rawFlow !== "authorization") return false;
871
- return true;
872
- }
873
- function x402AssetTransferMethod(option) {
874
- const raw = option.extra?.assetTransferMethod;
875
- return typeof raw === "string" ? raw : null;
876
- }
877
- function isErc7710Option(option) {
878
- return x402AssetTransferMethod(option) === ERC7710_ASSET_TRANSFER_METHOD;
879
- }
880
- function x402FacilitatorAddresses(option) {
881
- const raw = option.extra?.facilitatorAddresses;
882
- if (!Array.isArray(raw)) return null;
883
- const addresses = raw.filter((a) => typeof a === "string" && ADDRESS_RE.test(a));
884
- return addresses.length > 0 ? addresses : null;
885
- }
886
- function isPayableStandardOption(opt) {
887
- return opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt));
888
- }
889
- function selectStandardPaymentOption(accepts) {
890
- if (!accepts || accepts.length === 0) return null;
891
- for (const opt of accepts) {
892
- if (opt === null || typeof opt !== "object") continue;
893
- if (!isEip3009ConstructibleOption(opt)) continue;
894
- if (isPayableStandardOption(opt)) return opt;
895
- }
896
- return null;
897
- }
898
- function selectErc7710PaymentOption(accepts) {
899
- if (!accepts || accepts.length === 0) return null;
900
- for (const opt of accepts) {
901
- if (opt === null || typeof opt !== "object") continue;
902
- if (isErc7710Option(opt) && isPayableStandardOption(opt)) return opt;
903
- if (!isEip3009ConstructibleOption(opt)) continue;
904
- }
905
- return null;
906
- }
907
- function selectX402SettlementScheme(accepts, opts) {
908
- if (opts.delegationRail) {
909
- const preferred = selectErc7710PaymentOption(accepts);
910
- if (preferred) {
911
- return {
912
- scheme: "erc7710",
913
- option: preferred,
914
- facilitatorAddresses: x402FacilitatorAddresses(preferred)
915
- };
916
- }
917
- }
918
- const fallback = selectStandardPaymentOption(accepts);
919
- if (fallback) return { scheme: "eip3009", option: fallback, facilitatorAddresses: null };
920
- return null;
921
- }
922
- function x402AuthorizationAmount(option) {
923
- const amount = optionAuthorizationAmount(option);
924
- if (!isPositiveDecimalAtomicAmount(amount)) {
925
- throw new Error("Invalid x402 amount: must be a positive decimal atomic amount");
926
- }
927
- return amount;
928
- }
929
- function buildX402ExpectedMessage(context) {
930
- const version = context.payerDelegate ? 3 : context.typedDataHash ? 2 : 1;
931
- const payload = {
932
- version,
933
- kind: "haven.x402.expected",
934
- paymentId: context.paymentId,
935
- payloadHash: context.payloadHash.toLowerCase(),
936
- resourceUrl: context.resourceUrl,
937
- merchantTo: context.merchantTo.toLowerCase(),
938
- amount: context.amount,
939
- asset: context.asset.toLowerCase(),
940
- network: context.network
941
- };
942
- if (context.expiresAt) {
943
- payload.expiresAt = context.expiresAt;
944
- }
945
- if (context.typedDataHash) {
946
- payload.typedDataHash = context.typedDataHash.toLowerCase();
947
- }
948
- if (context.payerDelegate) {
949
- payload.payerDelegate = context.payerDelegate.toLowerCase();
950
- if (context.payerAgentId) {
951
- payload.payerAgentId = context.payerAgentId;
952
- }
953
- }
954
- return `Haven x402 expected context v${version}
955
- ${stableStringify2(payload)}`;
956
- }
957
- function toStandardPaymentRequirements(paymentRequired, option) {
958
- const network = STANDARD_X402_NETWORKS[option.network];
959
- if (!network) {
960
- throw new Error(`x402 exact payments are not supported on ${option.network}`);
961
- }
962
- if (option.scheme !== "exact") {
963
- throw new Error(`Unsupported x402 scheme: ${option.scheme}`);
964
- }
965
- if (!isEip3009ConstructibleOption(option)) {
966
- throw new Error(
967
- "Unsupported x402 payment requirements: this SDK constructs EIP-3009 authorization payments only. The merchant advertised a different extra.assetTransferMethod or an unrecognized extra.paymentFlow."
968
- );
969
- }
970
- return {
971
- scheme: "exact",
972
- network,
973
- maxAmountRequired: x402AuthorizationAmount(option),
974
- resource: option.resource ?? paymentRequired.resource.url,
975
- description: option.description ?? paymentRequired.resource.description ?? "Haven x402 payment",
976
- mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
977
- payTo: option.payTo,
978
- asset: option.asset,
979
- // Signing-only policy (#715/#3117): parsed and directly supplied options
980
- // share this cap before the library computes validBefore. Keep the offer
981
- // echoed in `accepted` unchanged. The forward margin (#1256) adds time
982
- // for funding/retry, but does not guarantee a merchant timeout above the
983
- // total bounded lifetime can pass facilitator verification.
984
- maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds) + X402_SETTLEMENT_FORWARD_MARGIN_SECONDS,
985
- extra: option.extra
986
- };
987
- }
988
- async function validateStandardX402PaymentHeader(paymentHeader, context) {
989
- try {
990
- if (typeof paymentHeader !== "string" || paymentHeader.length === 0 || paymentHeader.length > X402_PAYMENT_HEADER_MAX_LENGTH || paymentHeader.length % 4 !== 0 || !BASE64_RE.test(paymentHeader)) {
991
- throw new Error("wire");
992
- }
993
- const decoded = decodeBase64Json(paymentHeader);
994
- if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) throw new Error("shape");
995
- const version = decoded.x402Version;
996
- const payload = decoded.payload;
997
- if (version !== 1 && version !== 2 || !payload || typeof payload !== "object" || Array.isArray(payload)) {
998
- throw new Error("shape");
999
- }
1000
- if (version === 1) {
1001
- if (!hasOnlyKeys(decoded, ["x402Version", "scheme", "network", "payload"])) throw new Error("shape");
1002
- if (decoded.scheme !== "exact" || decoded.network !== standardX402WireNetwork(context.network)) {
1003
- throw new Error("context");
1004
- }
1005
- } else {
1006
- if (!hasOnlyKeys(decoded, ["x402Version", "accepted", "payload"], ["resource", "extensions"])) {
1007
- throw new Error("shape");
1008
- }
1009
- if ("resource" in decoded && (!decoded.resource || typeof decoded.resource !== "object" || Array.isArray(decoded.resource))) {
1010
- throw new Error("shape");
1011
- }
1012
- if ("extensions" in decoded && (!decoded.extensions || typeof decoded.extensions !== "object" || Array.isArray(decoded.extensions))) {
1013
- throw new Error("shape");
1014
- }
1015
- const accepted = selectStandardPaymentOption([decoded.accepted]);
1016
- if (!accepted || !matchesHeaderContext(accepted, context)) throw new Error("context");
1017
- }
1018
- const record = payload;
1019
- if (!hasOnlyKeys(record, ["signature", "authorization"])) throw new Error("shape");
1020
- if (typeof record.signature !== "string" || !SIGNATURE_RE.test(record.signature)) throw new Error("shape");
1021
- const authorization = record.authorization;
1022
- if (!authorization || typeof authorization !== "object" || Array.isArray(authorization)) throw new Error("shape");
1023
- const auth = authorization;
1024
- if (!hasOnlyKeys(auth, ["from", "to", "value", "validAfter", "validBefore", "nonce"])) throw new Error("shape");
1025
- if (typeof auth.from !== "string" || !ADDRESS_RE.test(auth.from) || typeof auth.to !== "string" || !ADDRESS_RE.test(auth.to) || typeof auth.value !== "string" || !isPositiveDecimalAtomicAmount(auth.value) || typeof auth.validAfter !== "string" || !DECIMAL_ATOMIC_AMOUNT_RE.test(auth.validAfter) || typeof auth.validBefore !== "string" || !DECIMAL_ATOMIC_AMOUNT_RE.test(auth.validBefore) || typeof auth.nonce !== "string" || !NONCE_RE.test(auth.nonce)) {
1026
- throw new Error("shape");
1027
- }
1028
- if (!sameAddress2(auth.from, context.payer) || !sameAddress2(auth.to, context.merchantTo) || auth.value !== context.amountAtomic) {
1029
- throw new Error("context");
1030
- }
1031
- const validAfter = BigInt(auth.validAfter);
1032
- const validBefore = BigInt(auth.validBefore);
1033
- const now = BigInt(Math.floor(Date.now() / 1e3));
1034
- if (validBefore <= now || validAfter > validBefore) throw new Error("expired");
1035
- const typedData = buildSweepTypedData({
1036
- from: auth.from,
1037
- to: auth.to,
1038
- value: auth.value,
1039
- validAfter: auth.validAfter,
1040
- validBefore: auth.validBefore,
1041
- nonce: auth.nonce,
1042
- token: context.asset,
1043
- chainId: context.chainId
1044
- });
1045
- const recovered = await recoverTypedDataAddress({
1046
- ...typedData,
1047
- // `buildSweepTypedData` keeps the public domain framework-neutral;
1048
- // viem brands contract addresses at this crypto call boundary only.
1049
- domain: {
1050
- ...typedData.domain,
1051
- verifyingContract: typedData.domain.verifyingContract
1052
- },
1053
- message: {
1054
- ...typedData.message,
1055
- from: typedData.message.from,
1056
- to: typedData.message.to,
1057
- nonce: typedData.message.nonce
1058
- },
1059
- signature: record.signature
1060
- });
1061
- if (!sameAddress2(recovered, context.payer)) throw new Error("recovery");
1062
- } catch {
1063
- throw new X402PaymentHeaderValidationError();
1064
- }
1065
- }
1066
- function hasOnlyKeys(value, required, optional = []) {
1067
- return Object.keys(value).every((key) => required.includes(key) || optional.includes(key)) && required.every((key) => key in value);
1068
- }
1069
- function sameAddress2(left, right) {
1070
- return left.toLowerCase() === right.toLowerCase();
1071
- }
1072
- function standardX402WireNetwork(network) {
1073
- return STANDARD_X402_NETWORKS[network] ?? null;
1074
- }
1075
- function matchesHeaderContext(option, context) {
1076
- if (option.scheme !== "exact" || !sameAddress2(option.payTo, context.merchantTo) || !sameAddress2(option.asset, context.asset) || option.network !== context.network || x402AuthorizationAmount(option) !== context.amountAtomic) return false;
1077
- return option.resource === void 0 || option.resource === context.resourceUrl;
1078
- }
1079
- function buildX402IdempotencyKey(paymentRequired, option, now = Date.now()) {
1080
- const bucket = Math.floor(now / X402_IDEMPOTENCY_BUCKET_MS);
1081
- const material = [
1082
- paymentRequired.resource.url,
1083
- paymentRequired.resource.description ?? "",
1084
- option.payTo.toLowerCase(),
1085
- option.asset.toLowerCase(),
1086
- x402AuthorizationAmount(option),
1087
- option.network,
1088
- bucket
1089
- ].join("|");
1090
- return `x402:${createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
1091
- }
1092
- function encodePaymentProof(receipt) {
1093
- const payload = {
1094
- x402Version: 2,
1095
- resource: receipt.resourceUrl ? { url: receipt.resourceUrl } : void 0,
1096
- accepted: receipt.accepted,
1097
- payload: {
1098
- type: "haven_tx_hash",
1099
- txHash: receipt.txHash,
1100
- paymentId: receipt.paymentId,
1101
- settledVia: "haven",
1102
- payer: receipt.payer,
1103
- chainId: receipt.chainId
1104
- }
1105
- };
1106
- return encodeBase64Json(payload);
1107
- }
1108
- function resolveTokenFromAddress(address, network) {
1109
- const lower = address.toLowerCase();
1110
- if (network && network in NETWORK_TOKENS) {
1111
- return NETWORK_TOKENS[network][lower] ?? null;
1112
- }
1113
- return ALL_TOKENS[lower] ?? null;
1114
- }
1115
- function stableStringify2(value) {
1116
- if (value === null || typeof value !== "object") {
1117
- const primitive = JSON.stringify(value);
1118
- return primitive === void 0 ? "undefined" : primitive;
1119
- }
1120
- if (value instanceof Date) return JSON.stringify(value.toISOString());
1121
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
1122
- const object = value;
1123
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
1124
- }
1125
61
  var DEFAULT_BASE_URL = "http://localhost:3001";
1126
62
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
63
+ function withoutClientHeader(headers) {
64
+ const target = HAVEN_CLIENT_HEADER.toLowerCase();
65
+ return Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== target));
66
+ }
1127
67
  var HavenApiTransport = class {
1128
68
  apiKey;
1129
69
  baseUrl;
1130
70
  requestTimeout;
1131
71
  defaultHeaders;
72
+ clientIdentity;
1132
73
  requestContext = new AsyncLocalStorage();
74
+ lastClientUpdate;
1133
75
  constructor(config) {
1134
76
  this.apiKey = config.apiKey;
1135
77
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
1136
78
  this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
1137
79
  this.defaultHeaders = { ...config.defaultHeaders ?? {} };
80
+ this.clientIdentity = config.clientIdentity ?? SDK_CLIENT_IDENTITY;
81
+ }
82
+ /**
83
+ * The newest `client_update` the backend sent (#3303): inside a
84
+ * `withRequestContext` dispatch, the one that dispatch's own requests
85
+ * received; outside one, the latest this transport has seen.
86
+ */
87
+ clientUpdate() {
88
+ const store = this.requestContext.getStore();
89
+ return store ? store.clientUpdate : this.lastClientUpdate;
90
+ }
91
+ recordClientUpdate(data) {
92
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return;
93
+ const hint = readClientUpdate(data.client_update);
94
+ if (!hint) return;
95
+ this.lastClientUpdate = hint;
96
+ const store = this.requestContext.getStore();
97
+ if (store) store.clientUpdate = hint;
1138
98
  }
1139
99
  /** Run `fn` with extra headers scoped to its asynchronous Haven API work. */
1140
100
  withRequestContext(headers, fn) {
@@ -1157,13 +117,19 @@ var HavenApiTransport = class {
1157
117
  headers: {
1158
118
  "Content-Type": "application/json",
1159
119
  "Authorization": `Bearer ${this.apiKey}`,
1160
- ...this.defaultHeaders,
1161
- ...contextHeaders
120
+ ...withoutClientHeader(this.defaultHeaders),
121
+ ...withoutClientHeader(contextHeaders),
122
+ // Last, and any other spelling of it dropped above (fetch would
123
+ // JOIN `x-haven-client` and `X-Haven-Client` into one unparseable
124
+ // value), so neither `defaultHeaders` nor a dispatch context can
125
+ // misname the client; `clientIdentity` is the one way to set it.
126
+ [HAVEN_CLIENT_HEADER]: this.clientIdentity
1162
127
  },
1163
128
  body: body ? JSON.stringify(body) : void 0,
1164
129
  signal: controller.signal
1165
130
  });
1166
131
  const data = await res.json();
132
+ this.recordClientUpdate(data);
1167
133
  if (!res.ok) {
1168
134
  const record = data;
1169
135
  const errorText = typeof record.error === "string" ? record.error : void 0;
@@ -1265,14 +231,14 @@ function mapPaymentReceipt(raw) {
1265
231
  const receipt = {
1266
232
  id: raw.id,
1267
233
  paymentId: raw.payment_id,
1268
- rail: raw.rail,
1269
- proofStatus: raw.proof_status,
234
+ source: raw.rail,
235
+ paymentProofStatus: raw.proof_status,
1270
236
  txHash: raw.tx_hash,
1271
237
  fundingTxHash: raw.funding_tx_hash ?? null,
1272
238
  settlementTxHash: raw.settlement_tx_hash ?? null,
1273
239
  chainId: raw.chain_id,
1274
- resourceUrl: raw.resource_url,
1275
- merchantAddress: raw.merchant_address,
240
+ x402ResourceUrl: raw.resource_url,
241
+ x402MerchantAddress: raw.merchant_address,
1276
242
  payerAddress: raw.payer_address,
1277
243
  parties: mapParties(raw.parties),
1278
244
  settlementAddress: raw.settlement_address,
@@ -1295,6 +261,9 @@ function mapPaymentReceipt(raw) {
1295
261
  if ("payment_intent_id" in raw) {
1296
262
  receipt.paymentIntentId = raw.payment_intent_id ?? null;
1297
263
  }
264
+ if (raw.scope) {
265
+ receipt.scope = { source: raw.scope.source, filter: raw.scope.filter };
266
+ }
1298
267
  if ("approval_request_id" in raw) {
1299
268
  receipt.approvalRequestId = raw.approval_request_id ?? null;
1300
269
  }
@@ -1486,6 +455,10 @@ var MCP_NOTIFICATION_TIMEOUT = 1e4;
1486
455
  var MCP_PROTOCOL_VERSION = "2025-06-18";
1487
456
  var MCP_ACCEPT = "application/json, text/event-stream";
1488
457
  var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
458
+ var MCP_X402_PAYMENT_META_KEY = "x402/payment";
459
+ var MCP_X402_PAYMENT_RESPONSE_META_KEY = "x402/payment-response";
460
+ var X402_RETRY_REJECTED_STATUS = 402;
461
+ var SESSION_NOT_FOUND_NEXT_ACTION = "reinitialize_then_retry_same_payment_header";
1489
462
  var McpMerchantTransport = class {
1490
463
  merchantTimeout;
1491
464
  fetchImpl;
@@ -1581,7 +554,7 @@ var McpMerchantTransport = class {
1581
554
  return response;
1582
555
  }
1583
556
  const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
1584
- if (!message) return response;
557
+ if (!message || !("result" in message || "error" in message)) return response;
1585
558
  const body = "result" in message ? message.result : message;
1586
559
  const headers = new Headers(response.headers);
1587
560
  headers.set("content-type", "application/json");
@@ -1644,7 +617,57 @@ var McpMerchantTransport = class {
1644
617
  if (send.includes(name)) headers.set(name, paymentHeader);
1645
618
  else headers.delete(name);
1646
619
  }
1647
- return this.fetch(url, { ...init, headers });
620
+ return this.fetch(url, withMcpPaymentMeta({ ...init, headers }, paymentHeader));
621
+ }
622
+ /**
623
+ * #3171: deliver the payment; if the merchant answers its unknown-session
624
+ * 404 WITH the settled-nothing recovery data, re-initialize once and resend
625
+ * the SAME header on the new session. The merchant's own guarantee (#1578:
626
+ * the session guard runs before its payment gate) is what makes the resend
627
+ * safe — a bare -32001, or any other 404, is returned to the caller
628
+ * untouched (body re-wrapped, since reading it consumed the stream). One
629
+ * recovery only: a second 404 is the answer.
630
+ */
631
+ async deliverPaymentRecoveringSession(url, init, paymentHeader, reinitialize) {
632
+ const first = await this.deliverPayment(url, init, paymentHeader);
633
+ if (first.status !== 404) return first;
634
+ let text;
635
+ try {
636
+ text = await first.clone().text();
637
+ } catch {
638
+ return first;
639
+ }
640
+ if (!sessionNotFoundRecovery(text)) return first;
641
+ const sessionId = await reinitialize();
642
+ if (!sessionId) return first;
643
+ return this.deliverPayment(url, this.withSessionHeaders(init, sessionId), paymentHeader);
644
+ }
645
+ /**
646
+ * #3118: read a native MCP payment-required tool result from a response
647
+ * WITHOUT consuming it — JSON or SSE framed. `undefined` for anything that
648
+ * is not an `isError: true` tool result carrying a valid `PaymentRequired`
649
+ * (structured content first, text fallback second), so an ordinary tool
650
+ * error, a successful result that merely resembles a challenge, or an
651
+ * unparseable body is never mistaken for a payment demand.
652
+ */
653
+ async extractToolResultChallenge(response, init) {
654
+ const toolResult = await this.readToolResult(response, init);
655
+ return toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
656
+ }
657
+ /**
658
+ * #3118: the tool RESULT a response carries, read WITHOUT consuming it, or
659
+ * `undefined`. Only a JSON or SSE body is read at all (#3155 review S2):
660
+ * a non-402 answer used to be returned untouched, and buffering an
661
+ * arbitrary body — a streaming or binary paid resource — to look for a
662
+ * challenge would block the caller until the stream ended or the merchant
663
+ * timeout fired. A merchant that speaks JSON-RPC declares one of those two
664
+ * content types.
665
+ */
666
+ async readToolResult(response, init) {
667
+ if (init !== void 0 && !isJsonRpcToolsCallBody(init.body)) return void 0;
668
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
669
+ if (!contentType.includes("application/json") && !contentType.includes("text/event-stream")) return void 0;
670
+ return mcpToolResultOf(await this.readMessage(response));
1648
671
  }
1649
672
  async notifyInitialized(url, init, sessionId, wallet) {
1650
673
  try {
@@ -1666,6 +689,106 @@ var McpMerchantTransport = class {
1666
689
  }
1667
690
  }
1668
691
  };
692
+ function mcpToolResultOf(value) {
693
+ if (!isRecord(value)) return void 0;
694
+ if ("jsonrpc" in value) {
695
+ return isRecord(value.result) ? value.result : void 0;
696
+ }
697
+ if ("isError" in value || Array.isArray(value.content) || isRecord(value._meta)) return value;
698
+ return void 0;
699
+ }
700
+ function extractMcpPaymentRequired(toolResult) {
701
+ if (toolResult.isError !== true) return void 0;
702
+ if (isRecord(toolResult.structuredContent)) {
703
+ const parsed = normalizePaymentRequired(toolResult.structuredContent);
704
+ if (parsed) return parsed;
705
+ }
706
+ if (Array.isArray(toolResult.content)) {
707
+ for (const item of toolResult.content) {
708
+ if (!isRecord(item) || item.type !== "text" || typeof item.text !== "string") continue;
709
+ let candidate;
710
+ try {
711
+ candidate = JSON.parse(item.text);
712
+ } catch {
713
+ continue;
714
+ }
715
+ const parsed = normalizePaymentRequired(candidate);
716
+ if (parsed) return parsed;
717
+ }
718
+ }
719
+ return void 0;
720
+ }
721
+ function mcpSettlementFromToolResult(toolResult) {
722
+ const meta = toolResult._meta;
723
+ if (!isRecord(meta)) return void 0;
724
+ const settlement = meta[MCP_X402_PAYMENT_RESPONSE_META_KEY];
725
+ if (!isRecord(settlement) || typeof settlement.success !== "boolean") return void 0;
726
+ return {
727
+ success: settlement.success,
728
+ ...typeof settlement.transaction === "string" ? { transaction: settlement.transaction } : {},
729
+ ...typeof settlement.network === "string" ? { network: settlement.network } : {},
730
+ ...typeof settlement.payer === "string" ? { payer: settlement.payer } : {},
731
+ ...typeof settlement.errorReason === "string" ? { errorReason: settlement.errorReason } : {}
732
+ };
733
+ }
734
+ function withMcpPaymentMeta(init, paymentHeader) {
735
+ const request = parseJsonRpcToolsCall(init.body);
736
+ if (!request) return init;
737
+ let envelope;
738
+ try {
739
+ envelope = decodeBase64Json(paymentHeader);
740
+ } catch {
741
+ return init;
742
+ }
743
+ if (!isRecord(envelope)) return init;
744
+ const params = request.params;
745
+ const existingMeta = isRecord(params._meta) ? params._meta : {};
746
+ return {
747
+ ...init,
748
+ body: JSON.stringify({
749
+ ...request,
750
+ params: { ...params, _meta: { ...existingMeta, [MCP_X402_PAYMENT_META_KEY]: envelope } }
751
+ })
752
+ };
753
+ }
754
+ function encodeMcpSettlementReceipt(settlement) {
755
+ return encodeBase64Json(settlement);
756
+ }
757
+ function parseJsonRpcToolsCall(body) {
758
+ if (typeof body !== "string") return void 0;
759
+ let request;
760
+ try {
761
+ request = JSON.parse(body);
762
+ } catch {
763
+ return void 0;
764
+ }
765
+ if (!isRecord(request) || request.method !== "tools/call" || !isRecord(request.params)) return void 0;
766
+ return request;
767
+ }
768
+ function isJsonRpcToolsCallBody(body) {
769
+ return parseJsonRpcToolsCall(body) !== void 0;
770
+ }
771
+ function sessionNotFoundRecovery(body) {
772
+ let parsed;
773
+ try {
774
+ parsed = JSON.parse(body);
775
+ } catch {
776
+ return void 0;
777
+ }
778
+ if (!isRecord(parsed) || !isRecord(parsed.error)) return void 0;
779
+ if (parsed.error.code !== -32001) return void 0;
780
+ const data = parsed.error.data;
781
+ if (!isRecord(data)) return void 0;
782
+ if (data.settled !== false || data.next_action !== SESSION_NOT_FOUND_NEXT_ACTION) return void 0;
783
+ return {
784
+ reason: typeof data.reason === "string" ? data.reason : "session_expired",
785
+ settled: false,
786
+ next_action: SESSION_NOT_FOUND_NEXT_ACTION
787
+ };
788
+ }
789
+ function isRecord(value) {
790
+ return typeof value === "object" && value !== null && !Array.isArray(value);
791
+ }
1669
792
  async function captureMerchantResponse(response) {
1670
793
  const merchant_body = await response.text().catch(() => "");
1671
794
  return {
@@ -1756,6 +879,39 @@ function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
1756
879
  const token = resolveTokenFromAddress(tokenAddress);
1757
880
  return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
1758
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
+ }
1759
915
  var AccountReads = class {
1760
916
  transport;
1761
917
  getPaymentStatus;
@@ -1775,7 +931,11 @@ var AccountReads = class {
1775
931
  return request;
1776
932
  }
1777
933
  async getAgentSummary() {
1778
- 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
+ ]);
1779
939
  const allowances = allowanceSummary.allowances.map((allowance) => {
1780
940
  return {
1781
941
  id: allowance.id,
@@ -1789,7 +949,30 @@ var AccountReads = class {
1789
949
  };
1790
950
  });
1791
951
  const readiness = deriveReadiness(agent.status, allowances);
1792
- 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
+ }
1793
976
  }
1794
977
  async getAllowances() {
1795
978
  const raw = await this.transport.get("/machine-payments/allowances");
@@ -2035,7 +1218,7 @@ function chainIdFromNetwork(network) {
2035
1218
  function chainIdOrNull(network) {
2036
1219
  return chainIdFromNetwork(network) ?? null;
2037
1220
  }
2038
- function sameAddress3(a, b) {
1221
+ function sameAddress(a, b) {
2039
1222
  return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
2040
1223
  }
2041
1224
  function decimalFromUsdcAtomic(value) {
@@ -2238,7 +1421,7 @@ function assertCanResumeX402(status, paymentRequired, option) {
2238
1421
  status.paymentId
2239
1422
  );
2240
1423
  }
2241
- if (status.merchantAddress && !sameAddress3(status.merchantAddress, option.payTo)) {
1424
+ if (status.merchantAddress && !sameAddress(status.merchantAddress, option.payTo)) {
2242
1425
  throw new HavenApiError(
2243
1426
  "x402 resume request does not match the approved merchant.",
2244
1427
  409,
@@ -2319,7 +1502,7 @@ var X402FundingLeg = class {
2319
1502
  }
2320
1503
  }
2321
1504
  // ── Authorization ────────────────────────────────────────────────
2322
- async authorize(paymentRequired, option, idempotencyKey) {
1505
+ async authorize(paymentRequired, option, idempotencyKey, taskBudgetId) {
2323
1506
  const raw = await this.post("/x402", {
2324
1507
  url: paymentRequired.resource.url,
2325
1508
  payTo: this.delegateAddress,
@@ -2332,7 +1515,10 @@ var X402FundingLeg = class {
2332
1515
  // #1360: same explicit funding-leg declaration as createX402Intent —
2333
1516
  // this local-key path derives payTo from the key (never stale), but the
2334
1517
  // declaration keeps both writers of the 3009 shape loud-by-default.
2335
- 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 } : {}
2336
1522
  });
2337
1523
  const state = paymentStateFromRaw("x402 payment", raw);
2338
1524
  const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
@@ -2367,7 +1553,10 @@ var X402FundingLeg = class {
2367
1553
  if (!raw.sign_data?.hash) {
2368
1554
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
2369
1555
  }
2370
- 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
+ });
2371
1560
  const execResult = await this.post(
2372
1561
  `/payments/${raw.payment_id}/sign`,
2373
1562
  { signature: sig }
@@ -2574,7 +1763,10 @@ var X402Erc7710 = class {
2574
1763
  );
2575
1764
  }
2576
1765
  const prepared = await this.prepare(paymentRequired, options);
2577
- const signature = await this.signForData(prepared.signData);
1766
+ const signature = await this.signForData(
1767
+ prepared.signData,
1768
+ settlementExpectationFromQuote(prepared.settlement)
1769
+ );
2578
1770
  const paymentHeader = await this.submit(prepared.paymentId, signature);
2579
1771
  return { ...prepared.settlement, paymentHeader };
2580
1772
  }
@@ -2640,7 +1832,10 @@ var X402Erc7710 = class {
2640
1832
  ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
2641
1833
  // #1307/#1547: persisted so the settle leg can rehydrate the merchant
2642
1834
  // call by payment_id on this scheme too, not only on the 3009 bridge.
2643
- ...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 } : {}
2644
1839
  });
2645
1840
  if (!raw.payment_id) {
2646
1841
  throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
@@ -2689,6 +1884,21 @@ var X402Erc7710 = class {
2689
1884
  return settled.payment_header;
2690
1885
  }
2691
1886
  };
1887
+ function settlementExpectationFromQuote(settlement) {
1888
+ const chainId = chainIdForNetwork(settlement.network);
1889
+ if (chainId === void 0) {
1890
+ throw new HavenSigningError(
1891
+ `Refusing to sign the x402 settlement child: the merchant's network ${JSON.stringify(settlement.network)} is not one this SDK can verify a settlement child on.`
1892
+ );
1893
+ }
1894
+ return {
1895
+ merchantTo: settlement.merchantPayTo,
1896
+ amount: settlement.amountAtomic,
1897
+ asset: settlement.asset,
1898
+ chainId,
1899
+ ...settlement.facilitatorAddresses && settlement.facilitatorAddresses.length > 0 ? { redeemers: settlement.facilitatorAddresses } : {}
1900
+ };
1901
+ }
2692
1902
 
2693
1903
  // src/tool-adapter.ts
2694
1904
  function toolX402PaymentRequired(input) {
@@ -2814,12 +2024,17 @@ var MerchantCompletion = class {
2814
2024
  if (!receipt.paymentHeader) {
2815
2025
  throw new HavenApiError("No x402 payment header was returned for payment retry", 500);
2816
2026
  }
2817
- const retryResponse = await this.merchantTransport.deliverPayment(
2027
+ const retryResponse = await this.merchantTransport.deliverPaymentRecoveringSession(
2818
2028
  url,
2819
2029
  initialInit,
2820
- receipt.paymentHeader
2030
+ receipt.paymentHeader,
2031
+ () => this.merchantTransport.initialize(url, initialInit)
2821
2032
  );
2822
- if (!retryResponse.ok) {
2033
+ const toolResult = retryResponse.ok ? await this.merchantTransport.readToolResult(retryResponse) : void 0;
2034
+ const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
2035
+ const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
2036
+ const inBandRejection = inBandChallenge !== void 0 || metaSettlement?.success === false;
2037
+ if (!retryResponse.ok || inBandRejection) {
2823
2038
  const merchant = await captureMerchantResponse(retryResponse);
2824
2039
  await this.recordRetryRejected({
2825
2040
  rail: "x402",
@@ -2834,7 +2049,7 @@ var MerchantCompletion = class {
2834
2049
  });
2835
2050
  throw new HavenApiError(
2836
2051
  "x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
2837
- merchant.merchant_status,
2052
+ inBandRejection ? X402_RETRY_REJECTED_STATUS : merchant.merchant_status,
2838
2053
  {
2839
2054
  marker: "x402_retry_rejected_after_funding",
2840
2055
  payment_id: receipt.paymentId,
@@ -2846,7 +2061,10 @@ var MerchantCompletion = class {
2846
2061
  }
2847
2062
  );
2848
2063
  }
2849
- const merchantSettlement = parseMerchantSettlement(retryResponse.headers.get("PAYMENT-RESPONSE"));
2064
+ const headerReceipt = retryResponse.headers.get("PAYMENT-RESPONSE");
2065
+ const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
2066
+ const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
2067
+ const merchantSettlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
2850
2068
  if (receipt.merchant && merchantSettlement.settlementTxHash) {
2851
2069
  receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
2852
2070
  receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
@@ -2864,8 +2082,8 @@ var MerchantCompletion = class {
2864
2082
  selectedPayment: receipt.accepted,
2865
2083
  paymentProofHeaderName: x402PaymentHeaderNamesSent(receipt.paymentHeader),
2866
2084
  paymentProofHeader: receipt.paymentHeader,
2867
- protocolReceiptHeaderName: "PAYMENT-RESPONSE",
2868
- protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
2085
+ protocolReceiptHeaderName,
2086
+ protocolReceiptHeader
2869
2087
  });
2870
2088
  await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
2871
2089
  return retryResponse;
@@ -3263,7 +2481,7 @@ var HavenClient = class {
3263
2481
  x402Wallet: this.x402Wallet,
3264
2482
  chainRpcs: this.chainRpcs,
3265
2483
  post: (path, body) => this.post(path, body),
3266
- signForData: (signData) => this.signForData(signData),
2484
+ signForData: (signData, fundingExpectation) => this.signForData(signData, void 0, fundingExpectation),
3267
2485
  assertSignableAuthorizationState: (label, raw) => this.throwIfNonSignableAuthorizationState(label, raw)
3268
2486
  });
3269
2487
  this.merchantCompletion = new MerchantCompletion({
@@ -3277,7 +2495,7 @@ var HavenClient = class {
3277
2495
  this.erc7710 = new X402Erc7710({
3278
2496
  delegateKey: this.delegateKey,
3279
2497
  post: (path, body) => this.post(path, body),
3280
- signForData: (signData) => this.signForData(signData),
2498
+ signForData: (signData, settlementExpectation) => this.signForData(signData, settlementExpectation),
3281
2499
  getAgent: () => this.getAgent()
3282
2500
  });
3283
2501
  }
@@ -3299,11 +2517,21 @@ var HavenClient = class {
3299
2517
  withRequestContext(headers, fn) {
3300
2518
  return this.havenApi.withRequestContext(headers, fn);
3301
2519
  }
2520
+ /**
2521
+ * The backend's newest `client_update` hint for this client (#3303): inside
2522
+ * a `withRequestContext` dispatch, the one that dispatch's own requests
2523
+ * received; outside one, the latest seen. Undefined when the client is
2524
+ * current or the backend predates #3303.
2525
+ */
2526
+ clientUpdate() {
2527
+ return this.havenApi.clientUpdate();
2528
+ }
3302
2529
  // ── High-Level API ───────────────────────────────────────────────
3303
2530
  /**
3304
2531
  * Send a payment in one call.
3305
2532
  *
3306
- * Creates the intent, signs the hash, submits the signature,
2533
+ * Creates the intent, signs its `sign_data` (a delegation-rail intent's
2534
+ * EIP-712 typed data, after the #3271 UserOp binding check), submits the signature,
3307
2535
  * and polls until confirmed (or throws on failure/timeout).
3308
2536
  *
3309
2537
  * Requires `delegateKey` to be set in the client config.
@@ -3315,7 +2543,7 @@ var HavenClient = class {
3315
2543
  );
3316
2544
  }
3317
2545
  const intent = await this.createIntent(request);
3318
- const signature = this.sign(intent.signData.hash);
2546
+ const signature = await this.signForData(intent.signData);
3319
2547
  await this.submitSignature(intent.paymentId, signature);
3320
2548
  return this.waitForConfirmation(intent.paymentId);
3321
2549
  }
@@ -3330,7 +2558,8 @@ var HavenClient = class {
3330
2558
  token: request.token,
3331
2559
  amount: request.amount,
3332
2560
  to: request.to,
3333
- ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
2561
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {},
2562
+ ...request.taskBudgetId ? { task_budget_id: request.taskBudgetId } : {}
3334
2563
  });
3335
2564
  if (raw.status === "pending_approval") {
3336
2565
  throwPaymentStateError("Payment", raw);
@@ -3390,7 +2619,11 @@ var HavenClient = class {
3390
2619
  // the agent passes only payment_id. Bounded: the backend rejects >64KB,
3391
2620
  // so an oversized blob is omitted here (signer falls back to the
3392
2621
  // caller-supplied copy) rather than failing the intent.
3393
- ...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 } : {}
3394
2627
  });
3395
2628
  if (raw.status !== "pending_signature") {
3396
2629
  throwPaymentStateError("x402 payment", raw);
@@ -3462,7 +2695,7 @@ var HavenClient = class {
3462
2695
  * (#834) — the backend refuses those intents with HTTP 410 before any
3463
2696
  * sign_data reaches a client, so encountering it here is a hard error too.
3464
2697
  */
3465
- async signForData(signData) {
2698
+ async signForData(signData, settlementExpectation, fundingExpectation) {
3466
2699
  if (!this.delegateKey) {
3467
2700
  throw new HavenSigningError(
3468
2701
  "Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
@@ -3480,6 +2713,18 @@ var HavenClient = class {
3480
2713
  "sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
3481
2714
  );
3482
2715
  }
2716
+ assertUserOpTypedDataBinding(signData.typed_data, signData.hash);
2717
+ assertBoundDirectPaymentUserOp(
2718
+ signData.typed_data,
2719
+ this.delegateAddress
2720
+ );
2721
+ if (fundingExpectation) {
2722
+ assertFundingLegPaysDelegate(signData.typed_data, {
2723
+ delegateAddress: this.delegateAddress,
2724
+ asset: fundingExpectation.asset,
2725
+ amount: fundingExpectation.amount
2726
+ });
2727
+ }
3483
2728
  return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
3484
2729
  }
3485
2730
  if (scheme === "eip712_delegation") {
@@ -3488,6 +2733,12 @@ var HavenClient = class {
3488
2733
  "sign_data.signature_scheme is eip712_delegation but typed_data is missing \u2014 refusing to sign the bare hash (the settlement would be rejected on redemption)."
3489
2734
  );
3490
2735
  }
2736
+ if (!settlementExpectation) {
2737
+ throw new HavenTypedDataRefusedError(
2738
+ "Refusing to sign an x402 settlement child without an independent expectation of what it must pay (the merchant's 402). Pay erc7710 merchants through settleX402Erc7710(), which supplies it."
2739
+ );
2740
+ }
2741
+ assertOwnSettlementChild(signData.typed_data, settlementExpectation, this.delegateAddress);
3491
2742
  return signSettlementDelegationTypedData(this.delegateKey, signData.typed_data);
3492
2743
  }
3493
2744
  if (scheme === void 0) {
@@ -3548,11 +2799,12 @@ var HavenClient = class {
3548
2799
  return this.accountReads.getAgentSummary();
3549
2800
  }
3550
2801
  /**
3551
- * Sweep stranded USDC and ETH from the delegate EOA back to the originating Safe.
2802
+ * Sweep stranded USDC and ETH from the delegate EOA back to the agent's Haven account.
3552
2803
  *
3553
2804
  * The delegate key held by this client signs and submits the transfer transactions
3554
2805
  * directly — Haven's backend never handles the key or constructs signed txs
3555
- * (CASP/MiCA Red Line #2). Funds always go to the Safe linked to this agent.
2806
+ * (CASP/MiCA Red Line #2). Funds always go to the Haven account linked to this agent
2807
+ * (`accountAddress` from `getAgent()`).
3556
2808
  *
3557
2809
  * Requires `chainRpcs` to be set for the agent's chain in `HavenClientConfig`.
3558
2810
  */
@@ -3764,6 +3016,96 @@ var HavenClient = class {
3764
3016
  async getReceipt(paymentId) {
3765
3017
  return this.accountReads.getReceipt(paymentId);
3766
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
+ }
3767
3109
  /**
3768
3110
  * Rehydrate the x402 resume-state bundle for a payment id (#1328: the MPP
3769
3111
  * resume-state variant retired along with the rest of the mpp_demo surface).
@@ -3816,7 +3158,7 @@ var HavenClient = class {
3816
3158
  if (cached) return cached;
3817
3159
  const inFlight = this.inFlightX402.get(idempotencyKey);
3818
3160
  if (inFlight) return inFlight;
3819
- const promise = this.fundingLeg.authorize(paymentRequired, option, idempotencyKey);
3161
+ const promise = this.fundingLeg.authorize(paymentRequired, option, idempotencyKey, options.taskBudgetId);
3820
3162
  this.inFlightX402.set(idempotencyKey, promise);
3821
3163
  try {
3822
3164
  return await promise;
@@ -3840,6 +3182,11 @@ var HavenClient = class {
3840
3182
  const request = snapshotX402Request(url, initialInit);
3841
3183
  const response = await this.merchantTransport.fetch(url, initialInit);
3842
3184
  if (response.status !== 402) {
3185
+ const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, initialInit);
3186
+ if (toolResultChallenge) {
3187
+ const mcpTransport2 = await this.merchantTransport.detect(url, toolResultChallenge, response);
3188
+ return buildX402Quote(toolResultChallenge, request, options.idempotencyKey, mcpTransport2);
3189
+ }
3843
3190
  let body;
3844
3191
  try {
3845
3192
  body = await response.clone().json();
@@ -3893,7 +3240,7 @@ var HavenClient = class {
3893
3240
  async payX402Quote(quote, options = {}) {
3894
3241
  const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
3895
3242
  try {
3896
- const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
3243
+ const receipt = await this.authorizeX402(quote.paymentRequired, { ...options, idempotencyKey });
3897
3244
  return this.merchantCompletion.retryRequest(
3898
3245
  quote.request.url,
3899
3246
  requestInitFromSnapshot(quote.request),
@@ -4006,10 +3353,11 @@ var HavenClient = class {
4006
3353
  return this.merchantCompletion.retryRequest(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
4007
3354
  }
4008
3355
  /**
4009
- * Fetch wrapper that automatically handles HTTP 402 responses.
3356
+ * Fetch wrapper that automatically handles HTTP 402 responses — and, since
3357
+ * #3118, a native-MCP payment-required tool result answered under HTTP 200.
4010
3358
  *
4011
- * Works like the standard `fetch()` but intercepts 402 responses,
4012
- * pays via x402 through Haven, and retries the request.
3359
+ * Works like the standard `fetch()` but intercepts 402 responses (or that
3360
+ * tool-result challenge), pays via x402 through Haven, and retries the request.
4013
3361
  *
4014
3362
  * ```ts
4015
3363
  * const response = await haven.fetch('https://paid-api.com/data')
@@ -4017,8 +3365,9 @@ var HavenClient = class {
4017
3365
  * ```
4018
3366
  *
4019
3367
  * **MCP-over-x402 auto-handshake (issue #315):** when the endpoint is
4020
- * MCP-shaped — the URL path ends in `/mcp`, or the 402 body carries a
4021
- * Coinbase Bazaar `extensions.bazaar` block — the SDK runs the MCP
3368
+ * MCP-shaped — the URL path ends in `/mcp`, or the 402 body (or, since
3369
+ * #3118, the tool-result challenge) carries a Coinbase Bazaar
3370
+ * `extensions.bazaar` block — the SDK runs the MCP
4022
3371
  * `initialize` handshake, threads the resulting `mcp-session-id`,
4023
3372
  * `Accept: application/json, text/event-stream`, and `x402-wallet` headers
4024
3373
  * through every request, and collapses SSE responses to the JSON-RPC
@@ -4036,16 +3385,23 @@ var HavenClient = class {
4036
3385
  let requestInit = withX402Wallet(init, x402PayerAddress(this.delegateAddress, this.x402Wallet));
4037
3386
  if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
4038
3387
  const response = await this.merchantTransport.fetch(url, requestInit);
4039
- if (response.status !== 402) {
4040
- return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
4041
- }
4042
3388
  let paymentRequired;
4043
- try {
4044
- paymentRequired = await parsePaymentRequiredResponse(response);
4045
- } catch {
4046
- return response;
3389
+ let nativeChallenge = false;
3390
+ if (response.status !== 402) {
3391
+ const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, requestInit);
3392
+ if (!toolResultChallenge) {
3393
+ return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
3394
+ }
3395
+ paymentRequired = toolResultChallenge;
3396
+ nativeChallenge = true;
3397
+ } else {
3398
+ try {
3399
+ paymentRequired = await parsePaymentRequiredResponse(response);
3400
+ } catch {
3401
+ return response;
3402
+ }
4047
3403
  }
4048
- if (!mcpSessionId && await this.merchantTransport.hasBazaarExtension(response)) {
3404
+ if (!mcpSessionId && (paymentRequired.extensions?.bazaar != null || await this.merchantTransport.hasBazaarExtension(response))) {
4049
3405
  mcpSessionId = await this.merchantTransport.initialize(url, init);
4050
3406
  if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
4051
3407
  }
@@ -4067,7 +3423,7 @@ var HavenClient = class {
4067
3423
  throw err;
4068
3424
  }
4069
3425
  const retryResponse = await this.merchantCompletion.retryRequest(url, requestInit, paymentRequired, receipt);
4070
- return mcpSessionId ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
3426
+ return mcpSessionId || nativeChallenge ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
4071
3427
  }
4072
3428
  /**
4073
3429
  * Deliver an already-signed x402 payment header to the merchant and return
@@ -4088,7 +3444,7 @@ var HavenClient = class {
4088
3444
  * collapses an SSE JSON-RPC response to its `result`.
4089
3445
  */
4090
3446
  /**
4091
- * Wait for a payment's Safe→delegate funding tx to reach ≥1 on-chain
3447
+ * Wait for a payment's account→delegate funding tx to reach ≥1 on-chain
4092
3448
  * confirmation. The hosted x402 completion path MUST call this after funding
4093
3449
  * and before delivering the merchant payment header, so the merchant's
4094
3450
  * balanceOf(delegate) / transferWithAuthorization verification sees the funded
@@ -4127,10 +3483,13 @@ var HavenClient = class {
4127
3483
  }
4128
3484
  let requestInit = withX402Wallet(input.init, x402Wallet) ?? {};
4129
3485
  if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
4130
- const response = await this.merchantTransport.deliverPayment(input.url, requestInit, input.paymentHeader);
4131
- const surfaced = mcpSessionId ? await this.merchantTransport.surfaceResult(response) : response;
4132
- const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
4133
- const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
3486
+ const response = await this.merchantTransport.deliverPaymentRecoveringSession(
3487
+ input.url,
3488
+ requestInit,
3489
+ input.paymentHeader,
3490
+ () => this.merchantTransport.initialize(input.url, input.init, x402Wallet)
3491
+ );
3492
+ const surfaced = await this.merchantTransport.surfaceResult(response);
4134
3493
  const text = await surfaced.text();
4135
3494
  let body;
4136
3495
  try {
@@ -4138,8 +3497,16 @@ var HavenClient = class {
4138
3497
  } catch {
4139
3498
  body = text;
4140
3499
  }
3500
+ const toolResult = mcpToolResultOf(body);
3501
+ const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
3502
+ const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
3503
+ const headerReceipt = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
3504
+ const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
3505
+ const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
3506
+ const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
3507
+ const merchantAccepted = surfaced.ok && inBandChallenge === void 0 && metaSettlement?.success !== false;
4141
3508
  let evidenceOutcome;
4142
- if (!surfaced.ok) {
3509
+ if (!merchantAccepted) {
4143
3510
  if (!input.noFundingLeg && fundingTxHash) {
4144
3511
  await this.merchantCompletion.recordRetryRejected({
4145
3512
  rail: "x402",
@@ -4169,7 +3536,7 @@ var HavenClient = class {
4169
3536
  merchantStatus: surfaced.status,
4170
3537
  paymentProofHeaderName: x402PaymentHeaderNamesSent(input.paymentHeader),
4171
3538
  paymentProofHeader: input.paymentHeader,
4172
- protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
3539
+ protocolReceiptHeaderName,
4173
3540
  protocolReceiptHeader
4174
3541
  });
4175
3542
  }
@@ -4177,9 +3544,10 @@ var HavenClient = class {
4177
3544
  }
4178
3545
  return {
4179
3546
  status: surfaced.status,
4180
- ok: surfaced.ok,
3547
+ ok: merchantAccepted,
4181
3548
  body,
4182
- settlementTxHash: settlement.settlementTxHash ?? void 0,
3549
+ // #3155 review S3: a transaction beside a rejection is not a settlement.
3550
+ settlementTxHash: merchantAccepted ? settlement.settlementTxHash ?? void 0 : void 0,
4183
3551
  evidenceOutcome
4184
3552
  };
4185
3553
  }
@@ -4369,12 +3737,12 @@ var toolDescriptions = {
4369
3737
  payX402OneShot: {
4370
3738
  summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
4371
3739
  selectionGuidance: "Prefer this over the quote+pay split when the agent just wants the paid resource and does not need to inspect the price first. If you already have a quote from haven_quote_x402, use haven_pay_x402_quote instead. Do not use for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
4372
- behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
3740
+ behavior: "Calls the URL, parses any HTTP 402 x402 challenge (or, since #3118, a native-MCP payment-required tool result under HTTP 200), signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status with no payment-required tool result, returns it unchanged without contacting Haven.",
4373
3741
  nextActionGuidance: "Preserve the returned resume_state or paymentId \u2014 either identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=fund_account_or_raise_allowance, the payment cannot be retried until the account is funded or the agent budget raised \u2014 stop and tell the user the shortfall reported on the response."
4374
3742
  },
4375
3743
  resumeX402: {
4376
3744
  summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
4377
- behavior: "Accepts either resume_state or payment_id, validates the original x402 details against the authorized Haven funding, and retries the merchant request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only). No new Haven payment is created.",
3745
+ behavior: "Accepts either resume_state or payment_id, validates the x402 details against the authorized funding, and retries the merchant request with the signed payment header (PAYMENT-SIGNATURE, plus legacy X-PAYMENT on EIP-3009 only; also params._meta on an MCP tools/call body). No new Haven payment is created.",
4378
3746
  nextActionGuidance: "Only call this after haven_get_payment_status reports nextAction=retry_original_x402_request \u2014 that means Haven's funding leg confirmed but no merchant response was ever recorded, most often because the process crashed between funding and the merchant retry. Any other nextAction reports a conflict instead of retrying, so do not call this speculatively. Do not start a new merchant session and do not pay again \u2014 that would pay twice for one resource."
4379
3747
  },
4380
3748
  // #1328: quoteMpp / payMpp / resumeMpp (the mpp_demo challenge/quote/resume
@@ -4413,8 +3781,8 @@ var toolDescriptions = {
4413
3781
  },
4414
3782
  listReceipts: {
4415
3783
  summary: "List machine-payment receipts, newest first, by page.",
4416
- selectionGuidance: "For transaction history or payment evidence; use the allowance tool instead for remaining allowance or what-can-I-spend questions.",
4417
- behavior: "Page: { receipts, total, hasMore, nextCursor }; total 0 = none exist (no indexing delay); hasMore = cut at limit, send nextCursor as cursor. parties.treasuryAccount is Haven's authoritative payer. protocolReceiptPayload is the merchant's PAYMENT-RESPONSE, relayed verbatim: merchant-controlled, unverified, not Haven's record; payer may differ from payerAddress. Proof header values are omitted.",
3784
+ selectionGuidance: "This agent's payment evidence, not the wallet's transaction history (sweeps, funding legs, other agents); use the allowance tool instead for remaining allowance or what-can-I-spend questions.",
3785
+ behavior: "Page { receipts, total, hasMore, nextCursor }; hasMore: pass nextCursor as cursor. parties.treasuryAccount is Haven's authoritative payer. protocolReceiptPayload is the merchant's PAYMENT-RESPONSE relayed verbatim: merchant-controlled, unverified, not Haven's record; payer may differ from payerAddress. Proof header values omitted.",
4418
3786
  nextActionGuidance: ""
4419
3787
  },
4420
3788
  verifyReceipt: {
@@ -4450,7 +3818,7 @@ var toolDescriptions = {
4450
3818
  send: {
4451
3819
  summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
4452
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.",
4453
- behavior: "Sends the requested 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 and before the agent is asked to sign \u2014 it is never queued for a human to approve later. The agent's signing key signs the account's typed data; 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.",
4454
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."
4455
3823
  },
4456
3824
  reportSettlementEvidence: {
@@ -4783,6 +4151,10 @@ Call \`haven_get_agent\`, one of the Haven MCP tools the connector wires into yo
4783
4151
 
4784
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.
4785
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
+
4786
4158
  ## Vocabulary
4787
4159
 
4788
4160
  | Term | What it is |
@@ -5117,103 +4489,6 @@ for that credential.
5117
4489
  var SKILL_FOLDER_NAME = "haven-pay";
5118
4490
  var HAVEN_SKILL_BODY_MD = HAVEN_SKILL_MD.replace(/^---\n[\s\S]*?\n---\n+/, "");
5119
4491
 
5120
- // src/next-step.ts
5121
- var NEXT_TOOL_SERVER_NAMES = {
5122
- hosted: "haven",
5123
- signer: "haven-signer"
5124
- };
5125
- var NEXT_TOOL_SERVER_ROLES = {
5126
- haven: "hosted",
5127
- "haven-signer": "signer"
5128
- };
5129
- function renderNextTool(role, name) {
5130
- return `mcp__${NEXT_TOOL_SERVER_NAMES[role]}__${name}`;
5131
- }
5132
- function parseNextTool(literal) {
5133
- const m = /^mcp__([a-z0-9-]+)__([a-z0-9_]+)$/.exec(literal);
5134
- if (!m) return null;
5135
- const role = NEXT_TOOL_SERVER_ROLES[m[1]];
5136
- return { server: m[1], name: m[2], ...role ? { role } : {} };
5137
- }
5138
- var DEFAULT_NEXT_TOOL_BY_ACTION = {
5139
- check_status_later: "haven_get_payment_status",
5140
- sweep_stranded_funds: "haven_sweep_delegate"
5141
- // `retry_original_x402_request` is NOT here: its only live emitter
5142
- // (state-direct-recovery.ts, erc7710) names no tool on purpose — the retry
5143
- // is the agent's own HTTP call — so a default would contradict the site.
5144
- };
5145
- function defaultNextToolFor(action) {
5146
- return DEFAULT_NEXT_TOOL_BY_ACTION[action];
5147
- }
5148
- function createNextStepBuilder(targets) {
5149
- return function nextStep(input) {
5150
- const i = input;
5151
- const base = {
5152
- next_action: input.nextAction,
5153
- safe_to_continue: input.safeToContinue,
5154
- reason: input.reason
5155
- };
5156
- if (i.nextTool === null) {
5157
- return { ...base, next_tool_omitted_reason: i.nextToolOmittedReason ?? "no next tool" };
5158
- }
5159
- const target = targets[i.nextTool];
5160
- if (!target) {
5161
- return { ...base, next_tool_omitted_reason: `${i.nextTool} is not a registered next-step target` };
5162
- }
5163
- const problem = target.validate(i.nextArguments);
5164
- if (problem) {
5165
- return { ...base, next_tool_omitted_reason: `next_arguments do not parse under ${i.nextTool}: ${problem}` };
5166
- }
5167
- return {
5168
- ...base,
5169
- next_tool: renderNextTool(target.role, i.nextTool),
5170
- next_tool_server: NEXT_TOOL_SERVER_NAMES[target.role],
5171
- next_tool_name: i.nextTool,
5172
- next_tool_server_role: target.role,
5173
- next_arguments: i.nextArguments
5174
- };
5175
- };
5176
- }
5177
-
5178
- // src/node-version.ts
5179
- var HAVEN_MINIMUM_NODE_VERSION = "22.0.0";
5180
- function parseNodeVersion(value) {
5181
- const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
5182
- if (!match) return [0, 0, 0];
5183
- return [Number(match[1] ?? 0), Number(match[2] ?? 0), Number(match[3] ?? 0)];
5184
- }
5185
- function compareNodeVersions(left, right) {
5186
- const leftParts = parseNodeVersion(left);
5187
- const rightParts = parseNodeVersion(right);
5188
- for (let i = 0; i < 3; i += 1) {
5189
- if (leftParts[i] !== rightParts[i]) return leftParts[i] > rightParts[i] ? 1 : -1;
5190
- }
5191
- return 0;
5192
- }
5193
- function isSupportedNodeVersion(nodeVersion = process.versions.node, minimumNodeVersion = HAVEN_MINIMUM_NODE_VERSION) {
5194
- return compareNodeVersions(nodeVersion, minimumNodeVersion) >= 0;
5195
- }
5196
- function unsupportedNodeVersionMessage(options) {
5197
- const nodeVersion = options.nodeVersion ?? process.versions.node;
5198
- const minimum = options.minimumNodeVersion ?? HAVEN_MINIMUM_NODE_VERSION;
5199
- const lines = [
5200
- `${options.subject} requires Node.js >=${minimum}, but this is Node.js ${nodeVersion}.`,
5201
- "",
5202
- "Upgrade Node, then try again:",
5203
- ` nvm install ${major(minimum)} && nvm use ${major(minimum)} (nvm)`,
5204
- ` fnm install ${major(minimum)} && fnm use ${major(minimum)} (fnm)`,
5205
- ` volta install node@${major(minimum)} (volta)`,
5206
- ` or download Node ${major(minimum)} from https://nodejs.org`,
5207
- "",
5208
- "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."
5209
- ];
5210
- if (options.retryHint) lines.push("", options.retryHint);
5211
- return lines.join("\n");
5212
- }
5213
- function major(version) {
5214
- return String(parseNodeVersion(version)[0]);
5215
- }
5216
-
5217
4492
  // src/merchant-discovery.ts
5218
4493
  var MERCHANT_DISCOVERY_PATHS = ["/.well-known/haven-demo-merchant", "/"];
5219
4494
  var DISCOVERY_MAX_BYTES = 64 * 1024;
@@ -5258,6 +4533,6 @@ function sameUrl(a, b) {
5258
4533
  }
5259
4534
  }
5260
4535
 
5261
- export { AGENT_APPROVAL_RELAY_JSON_SENTENCE, AGENT_APPROVAL_RELAY_PROSE_SENTENCE, AGENT_COMMAND_MODIFICATION_SENTENCE, AGENT_JSON_MODE_SENTENCE, AGENT_LOCAL_KEY_SENTENCE, AGENT_NETWORK_ACCESS_SENTENCE, AGENT_ONBOARDING_PROMPT, AGENT_PAYMENT_FAILURE_CODE_VALUES, AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AGENT_README_SECTION_MD, AGENT_SECRET_HYGIENE_SENTENCE, AGENT_WIRING_COLLISION_RELAY_SENTENCE, AgentPaymentFailureCode, AgentPaymentFailureCodeDescriptions, AgentPaymentFailureCodeSchema, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, AgentPaymentWarningCode, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DEFAULT_NEXT_TOOL_BY_ACTION, DISCOVERY_MAX_BYTES, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_AGENT_RUNBOOK_MD, HAVEN_CONNECTOR_CHANNEL, HAVEN_MINIMUM_NODE_VERSION, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenInsecureRetryTargetError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenZeroSettlementHashError, INSECURE_RETRY_TARGET_CODE, MERCHANT_DISCOVERY_PATHS, MerchantTimeoutError, NEXT_TOOL_SERVER_NAMES, NEXT_TOOL_SERVER_ROLES, RECEIPT_VERSION, SIGNER_UPDATE_FALLBACK, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, SignerRefusalCode, TRANSFER_WITH_AUTHORIZATION_TYPES, X402AlreadySettledError, X402PaymentHeaderValidationError, X402UnexpectedStatusError, 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, addressFromKey, assertSecureX402RetryTarget, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, connectorRerunCommand, connectorSpec, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, discoverMerchantMcpUrl, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenTools, isConnectorChannel, isErc7710Option, isSecureX402RetryTarget, isSupportedNodeVersion, isSweepableChain, isZeroSettlementTxHash, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readX402ReceiptPayer, renderNextTool, resolveConnectorChannel, resolveTokenFromAddress, resolveX402RetryTarget, sameUrl, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, signHash, signUserOpTypedDataForDelegation, signerUpdateFallback, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, verifyPaymentReceipt, verifySignature, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402V2PaymentEnvelope };
4536
+ export { AGENT_APPROVAL_RELAY_JSON_SENTENCE, AGENT_APPROVAL_RELAY_PROSE_SENTENCE, AGENT_COMMAND_MODIFICATION_SENTENCE, AGENT_JSON_MODE_SENTENCE, AGENT_LOCAL_KEY_SENTENCE, AGENT_NETWORK_ACCESS_SENTENCE, AGENT_ONBOARDING_PROMPT, AGENT_README_SECTION_MD, AGENT_SECRET_HYGIENE_SENTENCE, AGENT_WIRING_COLLISION_RELAY_SENTENCE, DISCOVERY_MAX_BYTES, HAVEN_AGENT_RUNBOOK_MD, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenClient, HavenInsecureRetryTargetError, INSECURE_RETRY_TARGET_CODE, MERCHANT_DISCOVERY_PATHS, RECEIPT_VERSION, SKILL_FOLDER_NAME, addressFromKey, assertSecureX402RetryTarget, composeDescription, discoverMerchantMcpUrl, havenTools, isSecureX402RetryTarget, isZeroSettlementTxHash, readX402ReceiptPayer, resolveX402RetryTarget, sameUrl, signHash, signUserOpTypedDataForDelegation, toolDescriptions, verifyPaymentReceipt, verifySignature };
5262
4537
  //# sourceMappingURL=index.js.map
5263
4538
  //# sourceMappingURL=index.js.map