@haven_ai/sdk 0.0.0-dev.202609031523.fd49e1a

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/README.md ADDED
@@ -0,0 +1,581 @@
1
+ # @haven_ai/sdk
2
+
3
+ TypeScript SDK for [Haven](https://github.com/d-hinders/Haven-AI) — agent wallet infrastructure for the autonomous economy.
4
+
5
+ Haven lets AI agents request and sign payments within strict, user-approved on-chain guardrails. This SDK makes it straightforward to integrate Haven payment requests into any agent without giving Haven custody of user or agent keys.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @haven_ai/sdk@alpha
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { HavenClient } from '@haven_ai/sdk'
17
+
18
+ const haven = new HavenClient({
19
+ apiKey: 'sk_agent_xxx', // from Haven dashboard
20
+ delegateKey: '0x...', // agent's delegate EOA private key
21
+ baseUrl: 'http://localhost:3001', // Haven API URL
22
+ })
23
+
24
+ // One-liner payment — handles intent, signing, submission, and confirmation
25
+ const result = await haven.pay({
26
+ token: 'EURe',
27
+ amount: '5.00',
28
+ to: '0xabc...',
29
+ })
30
+
31
+ console.log(result.txHash) // 0x...
32
+ console.log(result.explorerUrl) // https://gnosisscan.io/tx/0x... (or basescan.org for Base)
33
+ ```
34
+
35
+ ## Pay for an x402 resource
36
+
37
+ Point `haven.fetch` at any HTTP resource gated behind `402 Payment Required` —
38
+ the SDK detects the 402, pays through Haven, and retries automatically:
39
+
40
+ ```typescript
41
+ import { HavenClient } from '@haven_ai/sdk'
42
+
43
+ const haven = new HavenClient({
44
+ apiKey: process.env.HAVEN_API_KEY!, // from Haven dashboard
45
+ delegateKey: process.env.DELEGATE_KEY!, // agent's delegate private key
46
+ baseUrl: 'https://havenbackend-production-8a00.up.railway.app', // hosted Haven, or your self-hosted URL
47
+ })
48
+
49
+ // haven.fetch handles 402 → pay → retry automatically
50
+ const response = await haven.fetch('https://your-x402-endpoint.example/resource')
51
+ const data = await response.json()
52
+ ```
53
+
54
+ The payment fits within the agent's on-chain budget — anything outside it is
55
+ declined before any money moves, never queued for you to approve later — and it
56
+ shows up in your Haven dashboard activity feed.
57
+
58
+ ## Supported Networks & Tokens
59
+
60
+ | Network | CAIP-2 | Tokens |
61
+ |---------|--------|--------|
62
+ | Gnosis Chain | `eip155:100` | EURe, USDC.e, xDAI |
63
+ | Base | `eip155:8453` | USDC, ETH |
64
+
65
+ ## Credential Lifecycle
66
+
67
+ - The Haven API key identifies the agent. It is not payment authority.
68
+ - The delegate key signs payment payloads locally. Haven's backend never receives it.
69
+ - The agent's on-chain budget delegation enforces the agent budget: budget, recipient and expiry are checked by audited caveat enforcers at redemption, not by an off-chain rules engine.
70
+ - `getAllowances()` / `get_allowances` is the right path for budget, remaining amount, reset period, or "what can I spend?" questions.
71
+ - If an API key is exposed or lost, rotate it from the Haven agent detail page. The new key is shown once and the old key stops working.
72
+ - If a delegate key is exposed or lost, a delegation-rail agent is **re-keyed** rather than replaced — same agent, new signing key, budget remainder carried. See [Replacing an agent's signing key](../../docs/product/agent-key-rotation.md).
73
+
74
+ ## Step-by-Step API
75
+
76
+ Discovery and listing: `discoverTools({ verified?: 'any' | 'verified' | 'operator' })` returns the merged
77
+ catalog — operator-curated plus `verified_payable` directory entries (epic #1717), each with `source`,
78
+ `domainVerified` and `verifiedPayable`. `submitCatalogEntry(resourceUrl)` submits a merchant endpoint to
79
+ the Verified Payable Directory (queue-only; the seller still must prove domain ownership before listing),
80
+ and `getCatalogSubmissionStatus(id)` returns coarse status plus the ownership-proof instructions while
81
+ the submission can still prove ownership.
82
+
83
+ For agents that need control over each step (e.g., external signing):
84
+
85
+ ```typescript
86
+ // Step 1: Create a payment intent
87
+ const intent = await haven.createIntent({
88
+ token: 'USDC',
89
+ amount: '5.00',
90
+ to: '0xabc...',
91
+ })
92
+
93
+ // Step 2: Sign the hash (or sign externally)
94
+ const signature = haven.sign(intent.signData.hash)
95
+
96
+ // Step 3: Submit the signature
97
+ await haven.submitSignature(intent.paymentId, signature)
98
+
99
+ // Step 4: Wait for on-chain confirmation
100
+ const result = await haven.waitForConfirmation(intent.paymentId)
101
+ ```
102
+
103
+ ## x402 Protocol Support
104
+
105
+ Production merchant acceptance, facilitator, settlement, fiat, or acquiring functionality needs separate product and legal review under the repo's [CASP / MiCA guardrails](../../docs/regulatory/casp-risk-guardrails.md). The hosted x402 endpoint is an internal technical demo, not a merchant settlement product.
106
+
107
+ The SDK supports [x402](https://x402.org) client flows. When an API returns HTTP 402, the SDK evaluates the challenge against the agent's on-chain budget, uses the configured delegate key for the required signature, and retries automatically:
108
+
109
+ ```typescript
110
+ // Automatic — fetch() intercepts 402, pays, and retries.
111
+ // Use a stable idempotencyKey so one user intent stays one Haven payment.
112
+ const response = await haven.fetch(
113
+ 'https://paid-api.example.com/data',
114
+ undefined,
115
+ { idempotencyKey: 'paid-api-data-2026-05-22' },
116
+ )
117
+ const data = await response.json()
118
+
119
+ // Manual — parse and authorize the 402 yourself
120
+ import { parsePaymentRequiredResponse } from '@haven_ai/sdk'
121
+
122
+ const apiResponse = await fetch('https://paid-api.example.com/data')
123
+ if (apiResponse.status === 402) {
124
+ const paymentRequired = await parsePaymentRequiredResponse(apiResponse)
125
+ const receipt = await haven.authorizeX402(paymentRequired, {
126
+ idempotencyKey: 'paid-api-data-2026-05-22',
127
+ })
128
+ // Retry with { 'PAYMENT-SIGNATURE': receipt.paymentHeader }, and on the
129
+ // EIP-3009 bridge 'X-PAYMENT' too. NEVER both on erc7710 — see below.
130
+ console.log(receipt.explorerUrl)
131
+ }
132
+ ```
133
+
134
+ ### Idempotency: what the key guarantees, and what it costs
135
+
136
+ **Omit `idempotencyKey` and the SDK synthesises one** from the merchant's
137
+ resource URL and description, the payee, asset, amount, network, and a
138
+ **5-minute time bucket**. Every one of those inputs except the bucket
139
+ describes *the product*, so the guarantee is:
140
+
141
+ > Repeated calls for the same product, at the same price, within the same
142
+ > 5-minute bucket are **one payment**.
143
+
144
+ That is what makes a retried HTTP request safe by default — a dropped
145
+ connection or a re-run tool call cannot pay twice.
146
+
147
+ **The cost is the flip side of the same rule.** The SDK cannot tell a retry
148
+ from a *deliberate* second purchase of the same item: both re-fetch the 402
149
+ and produce identical key material. So a genuine second purchase inside the
150
+ window collapses onto the first payment. When that happens the SDK does not
151
+ hand you a fresh authorization — the funds are already spent, and any
152
+ authorization it minted would be unfundable. It throws
153
+ `X402AlreadySettledError`, carrying the original receipt:
154
+
155
+ ```typescript
156
+ import { X402AlreadySettledError } from '@haven_ai/sdk'
157
+
158
+ try {
159
+ await haven.fetch('https://paid-api.example.com/data')
160
+ } catch (err) {
161
+ if (err instanceof X402AlreadySettledError) {
162
+ // The FIRST payment's receipt — real, settled funds.
163
+ console.log(err.receipt.paymentId, err.receipt.txHash)
164
+ // 'settled' — the delegate was checked on-chain and cannot fund again.
165
+ // 'unverifiable' — no chainRpcs entry for the chain, so it was not checked.
166
+ console.log(err.basis)
167
+ }
168
+ }
169
+ ```
170
+
171
+ **To buy the same item twice, pass distinct keys** — that is the supported
172
+ way to say "this is a new purchase, not a retry":
173
+
174
+ ```typescript
175
+ await haven.fetch(url, init, { idempotencyKey: `vpn-renewal:${orderId}` })
176
+ ```
177
+
178
+ Configure `chainRpcs` for your chain. Without it the SDK cannot check the
179
+ delegate's balance, so an accidental key collision refuses on the weaker
180
+ `unverifiable` basis rather than risk issuing an authorization it cannot vouch
181
+ for.
182
+
183
+ **Resuming is not affected by that.** When you are following the documented
184
+ resume flow — re-calling after a funding leg confirms, or calling
185
+ `resumeAuthorizedX402({ paymentId })` — you named the payment, so an
186
+ unverifiable balance lets the resume proceed as before. Only a balance
187
+ verified *absent* refuses there. The stricter default applies solely to the
188
+ case where two purchases collided on a key you did not choose.
189
+
190
+ This applies to the **EIP-3009 funding-leg** scheme, which routes money
191
+ through the delegate EOA. **erc7710 direct settlement is unaffected**: it has
192
+ no funding leg and no delegate balance to exhaust.
193
+
194
+ For agents that need to inspect the price before paying, use the quote-first
195
+ path. `quoteX402()` probes the merchant and parses the HTTP 402 response, but it
196
+ does not create a Haven payment, signature, or on-chain transaction.
197
+
198
+ ```typescript
199
+ const quote = await haven.quoteX402(
200
+ 'https://paid-api.example.com/data',
201
+ undefined,
202
+ { idempotencyKey: 'paid-api-data-2026-05-22' },
203
+ )
204
+
205
+ if (Number(quote.amount) > 0.05) {
206
+ throw new Error(`Price ${quote.amount} ${quote.token} is above the user cap`)
207
+ }
208
+
209
+ const response = await haven.payX402Quote(quote)
210
+ const data = await response.json()
211
+ ```
212
+
213
+ Merchant-verified x402 retries use the official EIP-3009 `exact` scheme on Base USDC (`base` / `eip155:8453`). `haven.fetch()` sends the payment under `PAYMENT-SIGNATURE` (the x402 v2 name), and on the EIP-3009 bridge also under `X-PAYMENT` (v1), so a merchant on either version reads it. **On erc7710 it sends the v2 name ALONE**: that payload is always x402 v2, and its header carries a whole delegation chain, so duplicating it overflows the merchant's header limit and the request is refused with HTTP 431. If you build the retry yourself, follow the same rule. Haven's older tx-hash proof helper remains exported for Haven-native integrations; it is a different payload that happens to have shared the v2 name, and it is not what `haven.fetch()` sends.
214
+
215
+ For standard x402, the `x402-wallet` identity is the agent delegate wallet, because that is the wallet that signs and settles the merchant payment. Integrations that scope access by Haven wallet/Safe address should use a Haven-native flow instead of standard merchant x402.
216
+
217
+ ## AI Agent Integration
218
+
219
+ ### Pre-built Tool Definitions
220
+
221
+ The SDK ships with ready-made tool schemas for Claude and OpenAI:
222
+
223
+ ```typescript
224
+ import { HavenClient, havenTools } from '@haven_ai/sdk'
225
+ import Anthropic from '@anthropic-ai/sdk'
226
+
227
+ const haven = new HavenClient({ apiKey, delegateKey })
228
+ const anthropic = new Anthropic()
229
+
230
+ const response = await anthropic.messages.create({
231
+ model: 'claude-opus-4-7',
232
+ tools: havenTools.claude(), // or havenTools.openai() for OpenAI
233
+ messages: [{ role: 'user', content: 'Pay 5 EURe to 0xabc for API access' }],
234
+ })
235
+
236
+ // Handle tool calls
237
+ for (const block of response.content) {
238
+ if (block.type === 'tool_use') {
239
+ const result = await haven.executeTool(block.name, block.input)
240
+ // send result back to the model
241
+ }
242
+ }
243
+ ```
244
+
245
+ ### Available Tools
246
+
247
+ | Tool | Description |
248
+ |------|-------------|
249
+ | `make_payment` | Request and sign a payment from the user-controlled account within its on-chain budget |
250
+ | `get_payment_status` | Check the status of a payment intent |
251
+ | `get_allowances` | Read configured and on-chain budget state, including spent and remaining budget |
252
+ | `authorize_x402_payment` | Authorize a policy-limited x402 payment and return a payment header for an HTTP 402 resource |
253
+ | `resume_x402_payment` | Resume an authorized x402 payment and return a merchant payment header without creating a duplicate payment |
254
+
255
+ Use `get_allowances` for allowance, budget, spend-limit, remaining amount, reset-period, or "what can I spend?" questions. Payment tools still require the agent-held delegate key and the on-chain budget delegation; the Haven API key identifies the agent but does not authorize spending by itself.
256
+
257
+ ## Configuration
258
+
259
+ ```typescript
260
+ const haven = new HavenClient({
261
+ apiKey: 'sk_agent_xxx', // required — Haven agent API key
262
+ delegateKey: '0x...', // optional — enables .pay() and .sign()
263
+ baseUrl: 'http://localhost:3001', // default
264
+ x402Wallet: '0x...', // optional fallback when no delegate key is configured
265
+ requestTimeout: 30000, // per-request timeout (ms)
266
+ confirmationTimeout: 90000, // polling timeout (ms)
267
+ pollingInterval: 3000, // polling interval (ms)
268
+ })
269
+ ```
270
+
271
+ ## OpenAPI
272
+
273
+ The backend serves an OpenAPI 3.1 contract at:
274
+
275
+ - Production: `https://havenbackend-production-8a00.up.railway.app/openapi.json`
276
+ - Local development: `http://localhost:3001/openapi.json`
277
+
278
+ The spec covers the agent-facing payment surface: agents, direct payments,
279
+ payment status, x402 authorization, resume-state rehydration, machine-payment
280
+ receipts, and transactions. `POST /machine-payments/authorize` (the legacy
281
+ internal MPP demo challenge flow) is retired — it now refuses unconditionally
282
+ with HTTP 410; use the x402 flow for agent-to-merchant payments. Its security
283
+ scheme is deliberate: the Haven API key identifies the agent, but payment
284
+ authority still requires an agent-held delegate signature and an on-chain
285
+ budget delegation.
286
+
287
+ ## Agent payment state machine
288
+
289
+ Every payment state returned by Haven includes:
290
+
291
+ - `phase`: where the Haven-side payment currently is.
292
+ - `nextAction`: the stable action an agent should take next.
293
+ - `rail`: which payment rail produced the state. Categorical values (`direct`, `x402`, `mpp`) appear on resume-state discriminators; granular values (`mpp_demo`, `mpp_crypto`, `stripe_deposit`, `spt`) appear on response bodies. The `mpp` resume-state shape is a historical read only — the SDK no longer exposes a client method that acts on it (`mpp_demo` is retired, #1328).
294
+ - `message`: human-readable guidance for the same state.
295
+
296
+ The enum values and JSON Schema fragments are exported from `@haven_ai/sdk`:
297
+
298
+ ```typescript
299
+ import {
300
+ AgentPaymentNextAction,
301
+ AgentPaymentNextActionSchema,
302
+ AgentPaymentFailureCode,
303
+ AgentPaymentFailureCodeSchema,
304
+ AgentPaymentPhase,
305
+ AgentPaymentPhaseSchema,
306
+ AgentPaymentRail,
307
+ AgentPaymentRailSchema,
308
+ } from '@haven_ai/sdk'
309
+ ```
310
+
311
+ ### Flow diagram
312
+
313
+ ```text
314
+ ┌─────────────────────────┐
315
+ │ agent_signature_required│
316
+ └──────────┬──────────────┘
317
+ │ sign_and_submit_payment
318
+
319
+ ┌─────────────────────────┐
320
+ │ payment_submitted │
321
+ └──────────┬──────────────┘
322
+ │ check_status_later
323
+
324
+ ┌─────────────────────────┐
325
+ │ payment_confirmed (✔) │
326
+ └─────────────────────────┘
327
+
328
+ (EIP-3009 bridge only. `funding_sent` is Haven's funding leg confirming —
329
+ value left the treasury and sits on the delegate EOA. `executed` is the
330
+ agent's own merchant retry succeeding; Haven has no phase for the merchant
331
+ leg itself. See the `retry_original_x402_request` row below.)
332
+ ┌───────────────────────┐
333
+ │ funding_sent │
334
+ └──────────┬────────────┘
335
+ │ retry_original_x402_request (x402, after the
336
+ │ merchant-report grace window — #2145)
337
+ │ none (direct / erc7710)
338
+
339
+ ┌───────────────────────┐
340
+ │ executed (✔) │
341
+ └───────────────────────┘
342
+
343
+ Terminal from any non-confirmed phase:
344
+ rejected → stop_and_tell_user
345
+ failed → stop_and_tell_user
346
+ expired → request_again_if_user_still_wants_it
347
+
348
+ x402 tool-window failures:
349
+ expired funding/quote window → PAYMENT_WINDOW_EXPIRED → re-quote with same idempotency_key
350
+ merchant rejection after funding → MERCHANT_REJECTED_AFTER_FUNDING → haven_sweep_delegate
351
+ ```
352
+
353
+ ### `phase` reference
354
+
355
+ | `phase` | Meaning | Terminal? |
356
+ |---------|---------|-----------|
357
+ | `agent_signature_required` | Haven prepared a payment intent; the agent must sign and submit. | no |
358
+ | `payment_submitted` | Haven received the signed payment; the agent should poll for confirmation. | no |
359
+ | `payment_confirmed` | Direct payment is confirmed on chain. | yes |
360
+ | `user_approval_required` | **No live rail produces it.** Described the retired Safe rail's approval queue; kept in the exported enum for wire compatibility only. A payment outside the budget is now declined outright — see [Payments outside the agent's budget](#payments-outside-the-agents-budget). | n/a |
361
+ | `user_execution_required` | **No live rail produces it.** Same retirement as above. | n/a |
362
+ | `waiting_for_additional_approvals` | **No live rail produces it.** Same retirement as above. | n/a |
363
+ | `funding_sent` | Haven funding leg landed; the agent can continue the merchant/protocol leg. Only the EIP-3009 bridge has a funding leg; erc7710 direct settlement has none. | no |
364
+ | `rejected` | The payment was rejected and cannot proceed. | yes |
365
+ | `expired` | Payment expired before completion. | yes |
366
+ | `failed` | Haven could not complete the payment. | yes |
367
+
368
+ The merchant settlement leg of x402 (and the MPP retry) is the agent's own request to the merchant — it does not have a Haven `phase`. The payment is `funding_sent` until the agent retries with the payment header (`PAYMENT-SIGNATURE`, plus `X-PAYMENT` on this bridged path) (x402) or the MPP proof header; from Haven's perspective the payment becomes `executed` only after the agent successfully resumes.
369
+
370
+ ### `nextAction` reference
371
+
372
+ | `nextAction` | What the agent should do |
373
+ |--------------|--------------------------|
374
+ | `sign_and_submit_payment` | Sign with the delegate key and submit the payment to Haven. |
375
+ | `check_status_later` | Poll `getPaymentStatus(payment_id)` later. |
376
+ | `none` | Stop polling; no more action is needed for this payment id. |
377
+ | `wait_for_user_approval` | **No longer produced — nothing maps to it.** Retired with the Safe rail's approval queue; kept in the exported enum for wire compatibility. The SDK's own status mapping now answers `stop_and_tell_user` for the statuses that used to yield this. |
378
+ | `wait_for_user_to_complete_payment` | **No longer produced — nothing maps to it.** Same retirement as above. |
379
+ | `retry_original_x402_request` | Haven's funding leg confirmed but no merchant response was ever recorded — most often because the process crashed between the funding confirmation and the merchant retry (a 15-minute grace window applies before this fires; a client-reported merchant rejection instead yields `sweep_stranded_funds`). Call `resumeX402Payment()` with the preserved `resumeState`, or rehydrate it first with `getResumeState(payment_id)`. Do not start a new payment for the same purchase. |
380
+ | `stop_and_tell_user` | Stop retrying and tell the user the payment failed or was rejected. |
381
+ | `request_again_if_user_still_wants_it` | The request expired; ask again only if the user still wants the payment. |
382
+ | `payment_window_expired` | The x402 funding/quote window expired. Re-quote the same paid MCP tool call with the same `idempotency_key`, then sign the fresh `payload_hash`. |
383
+ | `sweep_stranded_funds` | A funding leg succeeded but the merchant/protocol leg did not settle. Stop retrying and use `haven_sweep_delegate` to recover stranded delegate funds. |
384
+
385
+ ### Machine-readable recovery codes
386
+
387
+ Hosted MCP and signer tools also return stable `code` values on recoverable x402 failures:
388
+
389
+ | `code` | Meaning | Agent recovery |
390
+ |--------|---------|----------------|
391
+ | `PRICE_EXCEEDS_MAX` | The merchant-authoritative x402 price is above the caller's spending cap. No funding transfer was created. | Tell the user the live price exceeded the cap and retry only after they confirm a higher one. |
392
+ | `AMBIGUOUS_MAX_AMOUNT` | Both `max_amount` (atomic units) and `max_amount_human` (whole tokens) were sent 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. |
393
+ | `MAX_AMOUNT_UNCONVERTIBLE` | `max_amount_human` could not be converted against this quote's asset — its decimals are unknown to Haven, or the cap has more decimal places than the asset supports. Nothing was spent. | Round the cap to the asset's decimals, or re-send it as an exact atomic `max_amount`. |
394
+ | `PAYMENT_WINDOW_EXPIRED` | The funding/quote window closed before `haven_x402_sign_header`, `haven_submit`, or `haven_complete_mcp_tool` could finish. | Re-run `haven_pay_mcp_tool` with the same `idempotency_key`, then sign and complete the fresh quote. Payloads include `retry_with_new_quote: true`. |
395
+ | `MERCHANT_REJECTED_AFTER_FUNDING` | Haven's funding leg succeeded, but the merchant rejected the paid retry. | Stop retrying the merchant and call `haven_sweep_delegate` so the user can recover stranded delegate USDC. |
396
+
397
+ ## Payments outside the agent's budget
398
+
399
+ Haven's policy is the agent's on-chain budget delegation — a period budget, an
400
+ optional recipient pin, and an expiry, each enforced by an audited caveat
401
+ enforcer at redemption. There is no off-chain rules engine and **no approval
402
+ queue**: the queue-and-approve path belonged to the retired Safe rail, which now
403
+ answers HTTP 410 at every agent-payment entry point.
404
+
405
+ If an agent requests a payment outside that policy, Haven **declines it before
406
+ any money moves** — during prepare, before anything is written and before the
407
+ agent is asked to sign. `POST /payments` answers `403` when no active delegation
408
+ authorizes that token and recipient, and `502` when the on-chain caveat check
409
+ rejects the amount, recipient or expiry; the x402 authorize path answers `403
410
+ delegation_budget_exceeded`. In every case the SDK raises `HavenApiError` and no
411
+ `payment_id` exists to poll.
412
+
413
+ Surface that to the user as a decline, not a wait: **nothing will arrive later.**
414
+ The fix is for the wallet owner to grant or raise the budget in Haven, after
415
+ which the agent can request the payment again. Do not retry in a loop, and do
416
+ not poll `getPaymentStatus()` hoping for an approval.
417
+
418
+ ### Resuming an x402 payment
419
+
420
+ Resume was triggered by *funding confirmation*, not by an approval, and applied
421
+ only to the EIP-3009 bridge — erc7710 direct settlement has no funding leg and
422
+ nothing to resume.
423
+
424
+ > **Resume is reachable again (#2145).** If the agent process crashes after
425
+ > Haven's funding leg confirms but before the merchant retry is recorded, a
426
+ > later `getPaymentStatus(payment_id)` reports
427
+ > `nextAction: 'retry_original_x402_request'` — Haven's funding confirmed but
428
+ > the merchant has likely not been paid. Gate on that structured field, not on
429
+ > message prose: call `resumeX402Payment()` with the preserved `resumeState`, or
430
+ > rehydrate it first via `getResumeState(payment_id)`. Any other `nextAction`
431
+ > means the payment is not ready to resume — do not call it speculatively.
432
+ >
433
+ > Meanwhile: the `payX402*` helpers perform the merchant retry themselves, so
434
+ > the ordinary in-flight path never needs resume. Only reach for
435
+ > `resumeX402Payment()` after seeing the trigger on a later status check —
436
+ > never speculatively, and never as a substitute for a fresh payment.
437
+
438
+ When the agent used `quoteX402()` / `payX402Quote()`, the thrown
439
+ `HavenPaymentStateError` includes a serializable `resumeState`. Persist it with
440
+ the MCP session details and pass it back to `resumeX402Payment()`.
441
+
442
+ If the agent process restarts and only kept the `payment_id`, call
443
+ `getResumeState(payment_id)` to rehydrate the stored x402/MPP context from
444
+ Haven, then pass that state to the matching resume helper. For POST-based
445
+ merchant or MCP calls, rebuild the live request details before retrying; Haven
446
+ stores payment context, not the agent's local request stream.
447
+
448
+ ```typescript
449
+ let resumeState
450
+ try {
451
+ await haven.payX402Quote(quote)
452
+ } catch (err) {
453
+ if (err instanceof HavenPaymentStateError && err.resumeState) {
454
+ resumeState = err.resumeState
455
+ console.log(err.paymentId, err.phase, err.nextAction)
456
+ console.log('Funding has not confirmed yet. Save resumeState and poll.')
457
+ }
458
+ }
459
+
460
+ const status = await haven.getPaymentStatus('payment-id')
461
+ if (status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
462
+ resumeState ??= await haven.getResumeState(status.paymentId)
463
+ const response = await haven.resumeX402Payment(resumeState)
464
+ const data = await response.json()
465
+ }
466
+ ```
467
+
468
+ Think of bridged x402 as two separate legs:
469
+
470
+ - Haven funding leg: the account funds the agent delegate wallet by redeeming
471
+ the budget delegation. Status fields such as `phase`, `nextAction`, and
472
+ `txHash` describe this leg. It is automatic and bounded by the budget — no
473
+ human step.
474
+ - Merchant x402 leg: after the funding leg is complete, the agent resumes the
475
+ same payment id and retries the original merchant request with the payment
476
+ header, under `PAYMENT-SIGNATURE` and — on this bridged path only —
477
+ `X-PAYMENT`.
478
+ Do not treat a new 402 probe or a new MCP session as a resume.
479
+
480
+ For manual HTTP stacks, use `resumeAuthorizedX402()` to get the merchant header
481
+ without retrying the request for you:
482
+
483
+ ```typescript
484
+ const receipt = await haven.resumeAuthorizedX402({
485
+ paymentId: status.paymentId,
486
+ paymentRequired,
487
+ idempotencyKey: 'paid-api-data-2026-05-22',
488
+ })
489
+
490
+ await fetch('https://paid-api.example.com/data', {
491
+ headers: {
492
+ 'PAYMENT-SIGNATURE': receipt.paymentHeader!,
493
+ // Bridged (EIP-3009) resume only. On erc7710 send the v2 name ALONE —
494
+ // that header carries a delegation chain and duplicating it is refused
495
+ // with HTTP 431.
496
+ 'X-PAYMENT': receipt.paymentHeader!,
497
+ },
498
+ })
499
+ ```
500
+
501
+ **When YOU make the retry, report the outcome (#2292).** `haven.fetch()` and
502
+ the `payX402*` helpers call the merchant themselves and write the evidence or
503
+ reconciliation record from what they observed. `resumeAuthorizedX402()` and the
504
+ raw MCP/SSE flow below deliberately do not — you hold the header and make the
505
+ call — so Haven cannot learn what happened unless you tell it:
506
+
507
+ ```typescript
508
+ const response = await fetch('https://paid-api.example.com/data', {
509
+ headers: {
510
+ 'PAYMENT-SIGNATURE': receipt.paymentHeader!,
511
+ // Bridged (EIP-3009) resume only — see the erc7710 note above.
512
+ 'X-PAYMENT': receipt.paymentHeader!,
513
+ },
514
+ })
515
+
516
+ await haven.reportX402MerchantOutcome({
517
+ paymentId: status.paymentId,
518
+ outcome: response.ok ? 'accepted' : 'rejected',
519
+ merchantStatus: response.status,
520
+ })
521
+ ```
522
+
523
+ A `rejected` report writes the same open `merchant_retry_rejected_after_payment`
524
+ reconciliation event the built-in retry writes, so the next
525
+ `getPaymentStatus()` answers `phase: funded_but_unsettled` /
526
+ `nextAction: sweep_stranded_funds` instead of reading as complete for the
527
+ fifteen-minute merchant-report grace window. An `accepted` report records the
528
+ merchant response so a delivered payment never enters that window at all.
529
+
530
+ It is evidence, not authority. The funding transaction hash and resource URL are
531
+ read from the payment's own record rather than taken from you — they are not
532
+ parameters — the call is scoped to your own agent's payments, and it changes no
533
+ amount, recipient or status. `outcome` must agree with `merchantStatus`
534
+ (`accepted` only for a 2xx), and an acceptance is terminal: a rejection reported
535
+ after a recorded merchant response is refused rather than re-flagging a
536
+ delivered payment as stranded.
537
+
538
+ For MCP/SSE x402 tools, keep the same MCP session and JSON-RPC payload where the
539
+ merchant requires it: initialize, retain `mcp-session-id`, send the original
540
+ `tools/call`, parse the 402 challenge, wait for the funding leg to confirm if
541
+ the payment is bridged, then resume with the same `payment_id` and retry the
542
+ original `tools/call`, setting BOTH `PAYMENT-SIGNATURE` (x402 v2) and `X-PAYMENT` (v1) to the header. Use a stable `idempotencyKey` for the
543
+ user intent so fresh merchant quotes or sessions do not become duplicate Haven
544
+ payments.
545
+
546
+ See [`examples/mcp-x402-sse.ts`](./examples/mcp-x402-sse.ts) for a complete
547
+ MCP flow with initialize, `mcp-session-id`, JSON-RPC `tools/call`, quote
548
+ inspection, saved resume state, and final retry.
549
+
550
+ ## Error Handling
551
+
552
+ ```typescript
553
+ import { HavenApiError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError } from '@haven_ai/sdk'
554
+
555
+ try {
556
+ await haven.pay({ token: 'EURe', amount: '5.00', to: '0xabc...' })
557
+ } catch (err) {
558
+ if (err instanceof HavenPaymentStateError) {
559
+ console.log(err.paymentId, err.phase, err.nextAction)
560
+ }
561
+ if (err instanceof HavenApiError) {
562
+ console.log(err.statusCode, err.message) // API returned an error
563
+ }
564
+ if (err instanceof HavenSigningError) {
565
+ console.log(err.message) // Signing failed
566
+ }
567
+ if (err instanceof HavenTimeoutError) {
568
+ console.log(err.paymentId) // Confirmation timed out
569
+ }
570
+ }
571
+ ```
572
+
573
+ `X402AlreadySettledError` extends `HavenApiError` (status 409) and is the one
574
+ error above that is **not** a failure to pay — it reports that the payment it
575
+ describes *succeeded*, earlier. Handle it before the generic `HavenApiError`
576
+ branch, and treat `err.receipt` as proof of purchase rather than retrying. See
577
+ [Idempotency](#idempotency-what-the-key-guarantees-and-what-it-costs).
578
+
579
+ ## License
580
+
581
+ MIT