@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 +154 -13
- package/dist/index.cjs +1111 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +405 -8
- package/dist/index.d.ts +405 -8
- package/dist/index.js +1100 -93
- package/dist/index.js.map +1 -1
- package/examples/mcp-x402-sse.ts +149 -0
- package/examples/x402_openapi_python.py +114 -0
- package/package.json +4 -3
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
|
-
|
|
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,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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.
|
|
289
|
+
await haven.payX402Quote(quote)
|
|
191
290
|
} catch (err) {
|
|
192
|
-
if (
|
|
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
|
|
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 ===
|
|
200
|
-
|
|
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
|