@haven_ai/sdk 0.1.2 → 0.1.4

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 CHANGED
@@ -97,8 +97,13 @@ Production merchant acceptance, facilitator, settlement, fiat, or acquiring func
97
97
  Haven natively supports the [x402](https://x402.org) payment protocol. When an API returns HTTP 402, the SDK evaluates the challenge against the agent's approved limits, uses the configured delegate key for the required signature, and retries automatically:
98
98
 
99
99
  ```typescript
100
- // Automatic — fetch() intercepts 402, pays, and retries
101
- const response = await haven.fetch('https://paid-api.example.com/data')
100
+ // Automatic — fetch() intercepts 402, pays, and retries.
101
+ // Use a stable idempotencyKey when one user intent may need manual approval.
102
+ const response = await haven.fetch(
103
+ 'https://paid-api.example.com/data',
104
+ undefined,
105
+ { idempotencyKey: 'paid-api-data-2026-05-22' },
106
+ )
102
107
  const data = await response.json()
103
108
 
104
109
  // Manual — parse and authorize the 402 yourself
@@ -107,12 +112,34 @@ import { parsePaymentRequiredResponse } from '@haven_ai/sdk'
107
112
  const apiResponse = await fetch('https://paid-api.example.com/data')
108
113
  if (apiResponse.status === 402) {
109
114
  const paymentRequired = await parsePaymentRequiredResponse(apiResponse)
110
- const receipt = await haven.authorizeX402(paymentRequired)
115
+ const receipt = await haven.authorizeX402(paymentRequired, {
116
+ idempotencyKey: 'paid-api-data-2026-05-22',
117
+ })
111
118
  // Retry with { 'X-PAYMENT': receipt.paymentHeader }
112
119
  console.log(receipt.explorerUrl)
113
120
  }
114
121
  ```
115
122
 
123
+ For agents that need to inspect the price before paying, use the quote-first
124
+ path. `quoteX402()` probes the merchant and parses the HTTP 402 response, but it
125
+ does not create a Haven payment, approval request, signature, or on-chain
126
+ transaction.
127
+
128
+ ```typescript
129
+ const quote = await haven.quoteX402(
130
+ 'https://paid-api.example.com/data',
131
+ undefined,
132
+ { idempotencyKey: 'paid-api-data-2026-05-22' },
133
+ )
134
+
135
+ if (Number(quote.amount) > 0.05) {
136
+ throw new Error(`Price ${quote.amount} ${quote.token} is above the user cap`)
137
+ }
138
+
139
+ const response = await haven.payX402Quote(quote)
140
+ const data = await response.json()
141
+ ```
142
+
116
143
  Merchant-verified x402 retries use the official EIP-3009 `exact` scheme on Base USDC (`base` / `eip155:8453`) and send the payment as `X-PAYMENT`. Haven's older tx-hash proof helper remains exported for Haven-native integrations, but `haven.fetch()` does not send `PAYMENT-SIGNATURE`.
117
144
 
118
145
  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.
@@ -152,6 +179,7 @@ for (const block of response.content) {
152
179
  | `make_payment` | Request and sign a payment from the user-controlled Safe within approved limits |
153
180
  | `get_payment_status` | Check the status of a payment intent or approval request |
154
181
  | `authorize_x402_payment` | Authorize a policy-limited x402 payment and return a payment header for an HTTP 402 resource |
182
+ | `resume_x402_payment` | Resume an approved x402 payment and return a merchant payment header without creating a duplicate approval |
155
183
 
156
184
  ## Configuration
157
185
 
@@ -167,6 +195,62 @@ const haven = new HavenClient({
167
195
  })
168
196
  ```
169
197
 
198
+ ## OpenAPI
199
+
200
+ The backend serves an OpenAPI 3.1 contract at:
201
+
202
+ - Production: `https://havenbackend-production-8a00.up.railway.app/openapi.json`
203
+ - Local development: `http://localhost:3001/openapi.json`
204
+
205
+ The spec covers the agent-facing payment surface: agents, direct payments,
206
+ payment status, x402 authorization, MPP demo authorization, resume-state
207
+ rehydration, machine-payment receipts, and transactions. Its security scheme is
208
+ deliberate: the Haven API key identifies the agent, but payment authority still
209
+ requires an agent-held delegate signature and on-chain Safe allowance state.
210
+
211
+ ## Agent payment state machine
212
+
213
+ Every payment or approval state returned by Haven includes:
214
+
215
+ - `phase`: where the Haven-side payment currently is.
216
+ - `nextAction`: the stable action an agent should take next.
217
+ - `rail`: which payment rail produced the state, such as `direct`, `x402`, or `mpp`.
218
+ - `message`: human-readable guidance for the same state.
219
+
220
+ The enum values and JSON Schema fragments are exported from `@haven_ai/sdk`:
221
+
222
+ ```typescript
223
+ import {
224
+ AgentPaymentNextAction,
225
+ AgentPaymentNextActionSchema,
226
+ AgentPaymentPhase,
227
+ AgentPaymentPhaseSchema,
228
+ AgentPaymentRail,
229
+ AgentPaymentRailSchema,
230
+ } from '@haven_ai/sdk'
231
+ ```
232
+
233
+ ```text
234
+ agent_signature_required -> payment_submitted -> payment_confirmed
235
+ |
236
+ v
237
+ user_approval_required -> user_execution_required -> funding_sent
238
+ |
239
+ v
240
+ waiting_for_additional_approvals
241
+ ```
242
+
243
+ | `nextAction` | What the agent should do |
244
+ |--------------|--------------------------|
245
+ | `sign_and_submit_payment` | Sign with the delegate key and submit the payment to Haven. |
246
+ | `check_status_later` | Poll `getPaymentStatus(payment_id)` later. |
247
+ | `none` | Stop polling; no more action is needed for this payment id. |
248
+ | `wait_for_user_approval` | Tell the user the payment is waiting in Haven, then poll later. Do not create a duplicate payment. |
249
+ | `wait_for_user_to_complete_payment` | The user approved the request; wait for them to finish the funding payment. |
250
+ | `retry_original_x402_request` | Resume this payment id and retry the original x402 request with the merchant payment header. Do not start a new merchant session. |
251
+ | `stop_and_tell_user` | Stop retrying and tell the user the payment failed or was rejected. |
252
+ | `request_again_if_user_still_wants_it` | The request expired; ask again only if the user still wants the payment. |
253
+
170
254
  ## Payments above the on-chain allowance
171
255
 
172
256
  Haven's policy lives on the Safe AllowanceModule (token, amount, reset period).
@@ -179,28 +263,85 @@ Surface that to the user: the payment isn't dead, it's waiting for a human to
179
263
  sign off. Check `getPaymentStatus(payment_id)` or the `get_payment_status`
180
264
  tool later instead of retrying in a tight loop.
181
265
 
182
- For x402, the manual approval path has two steps. First, the user approves the
183
- funding movement from the Haven wallet to the agent's delegate wallet. Once
184
- Haven reports `next_action: 'retry_original_x402_request'`, retry the original
185
- paid API request so the SDK can attach the merchant x402 payment header. Do not
186
- rewrite the SDK while waiting for approval.
266
+ For x402, approval resume is explicit. If `authorizeX402()` or `haven.fetch()`
267
+ throws `HavenPaymentStateError` with `nextAction: 'wait_for_user_approval'`,
268
+ stop and tell the user the Haven funding leg is waiting in Haven. Do not loop
269
+ and do not start a new merchant or MCP session. Pending x402 states include the
270
+ resource URL, merchant address, chain id, asset, network, atomic amount, and
271
+ idempotency key so agents can explain what is waiting for approval. After the
272
+ user approves, call `getPaymentStatus(payment_id)`. When Haven reports
273
+ `nextAction: 'retry_original_x402_request'`, call `resumeX402Payment()` with the
274
+ same user-intent idempotency key and the original x402 details.
275
+
276
+ When the agent used `quoteX402()` / `payX402Quote()`, pending approval errors
277
+ include a serializable `resumeState`. Persist it with the MCP session details
278
+ and pass it back to `resumeX402Payment()` after approval.
279
+
280
+ If the agent process restarts and only kept the `payment_id`, call
281
+ `getResumeState(payment_id)` after approval to rehydrate the stored x402/MPP
282
+ context from Haven, then pass that state to the matching resume helper. For
283
+ POST-based merchant or MCP calls, rebuild the live request details before
284
+ retrying; Haven stores payment context, not the agent's local request stream.
187
285
 
188
286
  ```typescript
287
+ let resumeState
189
288
  try {
190
- await haven.pay({ token: 'USDC', amount: '500', to: '0xabc...' })
289
+ await haven.payX402Quote(quote)
191
290
  } catch (err) {
192
- if (err instanceof HavenPaymentStateError && err.nextAction === 'wait_for_user_approval') {
291
+ if (
292
+ err instanceof HavenPaymentStateError &&
293
+ err.nextAction === AgentPaymentNextAction.WaitForUserApproval &&
294
+ err.resumeState
295
+ ) {
296
+ resumeState = err.resumeState
193
297
  console.log(err.paymentId, err.phase, err.nextAction)
194
- console.log('Queued for owner approval visible in the Haven dashboard.')
298
+ console.log('Queued for owner approval. Save resumeState and wait.')
195
299
  }
196
300
  }
197
301
 
198
302
  const status = await haven.getPaymentStatus('approval-or-payment-id')
199
- if (status.nextAction === 'retry_original_x402_request') {
200
- // Retry the original paid API request.
303
+ if (status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
304
+ resumeState ??= await haven.getResumeState(status.paymentId)
305
+ const response = await haven.resumeX402Payment(resumeState)
306
+ const data = await response.json()
201
307
  }
202
308
  ```
203
309
 
310
+ Think of manual approval x402 as two separate legs:
311
+
312
+ - Haven funding leg: the user may need to approve a Safe AllowanceModule transfer
313
+ to the agent delegate wallet. Status fields such as `phase`,
314
+ `nextAction`, and `txHash` describe this leg.
315
+ - Merchant x402 leg: after the funding leg is complete, the agent resumes the
316
+ same payment id and retries the original merchant request with `X-PAYMENT`.
317
+ Do not treat a new 402 probe or a new MCP session as a resume.
318
+
319
+ For manual HTTP stacks, use `resumeAuthorizedX402()` to get the merchant header
320
+ without retrying the request for you:
321
+
322
+ ```typescript
323
+ const receipt = await haven.resumeAuthorizedX402({
324
+ paymentId: status.paymentId,
325
+ paymentRequired,
326
+ idempotencyKey: 'paid-api-data-2026-05-22',
327
+ })
328
+
329
+ await fetch('https://paid-api.example.com/data', {
330
+ headers: { 'X-PAYMENT': receipt.paymentHeader! },
331
+ })
332
+ ```
333
+
334
+ For MCP/SSE x402 tools, keep the same MCP session and JSON-RPC payload where the
335
+ merchant requires it: initialize, retain `mcp-session-id`, send the original
336
+ `tools/call`, parse the 402 challenge, wait for approval if needed, then resume
337
+ with the same `payment_id` and retry the original `tools/call` with
338
+ `X-PAYMENT`. Use a stable `idempotencyKey` for the user intent so fresh merchant
339
+ quotes or sessions do not become duplicate Haven approval requests.
340
+
341
+ See [`examples/mcp-x402-sse.ts`](./examples/mcp-x402-sse.ts) for a complete
342
+ MCP flow with initialize, `mcp-session-id`, JSON-RPC `tools/call`, quote
343
+ inspection, user approval, saved resume state, and final retry.
344
+
204
345
  ## Error Handling
205
346
 
206
347
  ```typescript