@haven_ai/sdk 0.1.1 → 0.1.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript SDK for [Haven](https://github.com/d-hinders/Haven-AI) — agent wallet infrastructure for the autonomous economy.
4
4
 
5
- Haven gives AI agents the ability to hold, send, and receive money within strict, user-defined guardrails. This SDK makes it trivial to integrate Haven payments into any agent.
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
6
 
7
7
  ## Install
8
8
 
@@ -92,11 +92,18 @@ const result = await haven.waitForConfirmation(intent.paymentId)
92
92
 
93
93
  ## x402 Protocol Support
94
94
 
95
- Haven natively supports the [x402](https://x402.org) payment protocol. When an API returns HTTP 402, Haven evaluates the payment against policy, funds the agent delegate wallet from the Haven wallet, signs the merchant's standard x402 payment payload, and retries automatically:
95
+ 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.
96
+
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:
96
98
 
97
99
  ```typescript
98
- // Automatic — fetch() intercepts 402, pays, and retries
99
- 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
+ )
100
107
  const data = await response.json()
101
108
 
102
109
  // Manual — parse and authorize the 402 yourself
@@ -105,7 +112,9 @@ import { parsePaymentRequiredResponse } from '@haven_ai/sdk'
105
112
  const apiResponse = await fetch('https://paid-api.example.com/data')
106
113
  if (apiResponse.status === 402) {
107
114
  const paymentRequired = await parsePaymentRequiredResponse(apiResponse)
108
- const receipt = await haven.authorizeX402(paymentRequired)
115
+ const receipt = await haven.authorizeX402(paymentRequired, {
116
+ idempotencyKey: 'paid-api-data-2026-05-22',
117
+ })
109
118
  // Retry with { 'X-PAYMENT': receipt.paymentHeader }
110
119
  console.log(receipt.explorerUrl)
111
120
  }
@@ -147,9 +156,10 @@ for (const block of response.content) {
147
156
 
148
157
  | Tool | Description |
149
158
  |------|-------------|
150
- | `make_payment` | Send a payment from the Haven-managed Safe wallet |
151
- | `get_payment_status` | Check the status of a previously initiated payment |
152
- | `authorize_x402_payment` | Fund the agent wallet and return a payment header for an HTTP 402 resource |
159
+ | `make_payment` | Request and sign a payment from the user-controlled Safe within approved limits |
160
+ | `get_payment_status` | Check the status of a payment intent or approval request |
161
+ | `authorize_x402_payment` | Authorize a policy-limited x402 payment and return a payment header for an HTTP 402 resource |
162
+ | `resume_x402_payment` | Resume an approved x402 payment and return a merchant payment header without creating a duplicate approval |
153
163
 
154
164
  ## Configuration
155
165
 
@@ -167,33 +177,78 @@ const haven = new HavenClient({
167
177
 
168
178
  ## Payments above the on-chain allowance
169
179
 
170
- Haven's policy lives entirely on the Safe AllowanceModule (token, amount,
171
- reset period). If an agent requests a payment above the remaining allowance,
172
- Haven does **not** reject it — it returns HTTP 202 with `status: 'pending_approval'`
173
- and queues it for the wallet owner to approve in the dashboard.
180
+ Haven's policy lives on the Safe AllowanceModule (token, amount, reset period).
181
+ If an agent requests a payment above the remaining allowance, Haven does **not**
182
+ reject it — it returns HTTP 202 with `status: 'pending_approval'`, a
183
+ `payment_id`, `phase`, and `next_action`, then queues it for the wallet owner
184
+ to approve in the dashboard.
174
185
 
175
186
  Surface that to the user: the payment isn't dead, it's waiting for a human to
176
- sign off. Don't retry the same request would just queue another approval.
187
+ sign off. Check `getPaymentStatus(payment_id)` or the `get_payment_status`
188
+ tool later instead of retrying in a tight loop.
189
+
190
+ For x402, approval resume is explicit. If `authorizeX402()` or `haven.fetch()`
191
+ throws `HavenPaymentStateError` with `nextAction: 'wait_for_user_approval'`,
192
+ stop and tell the user the request is waiting in Haven. Do not loop. After the
193
+ user approves, call `getPaymentStatus(payment_id)`. When Haven reports
194
+ `nextAction: 'retry_original_x402_request'`, call `resumeX402Payment()` with the
195
+ same user-intent idempotency key and the original x402 details.
177
196
 
178
197
  ```typescript
179
198
  try {
180
199
  await haven.pay({ token: 'USDC', amount: '500', to: '0xabc...' })
181
200
  } catch (err) {
182
- if (err instanceof HavenApiError && err.statusCode === 202) {
183
- // err.body.payment_id, err.body.remaining, err.body.requested
201
+ if (err instanceof HavenPaymentStateError && err.nextAction === 'wait_for_user_approval') {
202
+ console.log(err.paymentId, err.phase, err.nextAction)
184
203
  console.log('Queued for owner approval — visible in the Haven dashboard.')
185
204
  }
186
205
  }
206
+
207
+ const status = await haven.getPaymentStatus('approval-or-payment-id')
208
+ if (status.nextAction === 'retry_original_x402_request') {
209
+ const response = await haven.resumeX402Payment({
210
+ paymentId: status.paymentId,
211
+ url: 'https://paid-api.example.com/data',
212
+ paymentRequired,
213
+ idempotencyKey: 'paid-api-data-2026-05-22',
214
+ })
215
+ const data = await response.json()
216
+ }
187
217
  ```
188
218
 
219
+ For manual HTTP stacks, use `resumeAuthorizedX402()` to get the merchant header
220
+ without retrying the request for you:
221
+
222
+ ```typescript
223
+ const receipt = await haven.resumeAuthorizedX402({
224
+ paymentId: status.paymentId,
225
+ paymentRequired,
226
+ idempotencyKey: 'paid-api-data-2026-05-22',
227
+ })
228
+
229
+ await fetch('https://paid-api.example.com/data', {
230
+ headers: { 'X-PAYMENT': receipt.paymentHeader! },
231
+ })
232
+ ```
233
+
234
+ For MCP/SSE x402 tools, keep the same MCP session and JSON-RPC payload where the
235
+ merchant requires it: initialize, retain `mcp-session-id`, send the original
236
+ `tools/call`, parse the 402 challenge, wait for approval if needed, then resume
237
+ with the same `payment_id` and retry the original `tools/call` with
238
+ `X-PAYMENT`. Use a stable `idempotencyKey` for the user intent so fresh merchant
239
+ quotes or sessions do not become duplicate Haven approval requests.
240
+
189
241
  ## Error Handling
190
242
 
191
243
  ```typescript
192
- import { HavenApiError, HavenSigningError, HavenTimeoutError } from '@haven_ai/sdk'
244
+ import { HavenApiError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError } from '@haven_ai/sdk'
193
245
 
194
246
  try {
195
247
  await haven.pay({ token: 'EURe', amount: '5.00', to: '0xabc...' })
196
248
  } catch (err) {
249
+ if (err instanceof HavenPaymentStateError) {
250
+ console.log(err.paymentId, err.phase, err.nextAction)
251
+ }
197
252
  if (err instanceof HavenApiError) {
198
253
  console.log(err.statusCode, err.message) // API returned an error
199
254
  }