@haven_ai/sdk 0.4.0-alpha.0 → 0.5.0-alpha.1

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
@@ -21,6 +21,17 @@ and managing the account from the shell with `@haven_ai/cli`.
21
21
  npm install @haven_ai/sdk@alpha
22
22
  ```
23
23
 
24
+ The package has three entries. `@haven_ai/sdk` is the full client. `@haven_ai/sdk/edge`
25
+ (#3173) is the ethers-free subset the local signer imports — error classes, the
26
+ typed-next-step builder, the x402 message builders, viem-based key helpers, and
27
+ (#3283) the signing-surface guard: `assertBoundDirectPaymentUserOp`, the
28
+ redemption guard, `deriveDelegateAccountAddress` and `verifySettlementChild` —
29
+ and loads in about a third of the time; use it when you need those helpers
30
+ without the HTTP client. A class imported from either entry is the same class.
31
+ `@haven_ai/sdk/test-support` (#3283) holds test fixture builders shared by
32
+ Haven's own packages; it is not a signing API, nothing at runtime imports it,
33
+ and it carries no semver guarantee.
34
+
24
35
  ## Quick Start
25
36
 
26
37
  ```typescript
@@ -106,8 +117,18 @@ const intent = await haven.createIntent({
106
117
  to: '0xabc...',
107
118
  })
108
119
 
109
- // Step 2: Sign the hash (or sign externally)
110
- const signature = haven.sign(intent.signData.hash)
120
+ // Step 2: Sign externally. On the delegation rail (the only rail that pays)
121
+ // the account validates the EIP-712 typed data in intent.signData.typed_data,
122
+ // never the bare hash — a signature over signData.hash is rejected on-chain
123
+ // (AA24). Sign that typed data with your delegate key (viem signTypedData),
124
+ // after checking it with assertUserOpTypedDataBinding(typed_data, hash) AND
125
+ // assertBoundDirectPaymentUserOp(typed_data, yourDelegateAddress) — the
126
+ // first proves the typed data matches the hash, the second that it is your
127
+ // own account redeeming your budget delegation, not a self-call (#3283).
128
+ // There is no public HavenClient method for this step yet; pay() does both
129
+ // for you. HavenClient.sign(hash) and signUserOpTypedDataForDelegation are
130
+ // verbatim primitives: they check nothing.
131
+ const signature = await signTypedDataWithYourDelegateKey(intent.signData.typed_data)
111
132
 
112
133
  // Step 3: Submit the signature
113
134
  await haven.submitSignature(intent.paymentId, signature)
@@ -147,6 +168,23 @@ if (apiResponse.status === 402) {
147
168
  }
148
169
  ```
149
170
 
171
+ **MCP merchants, both dialects (#3118).** A merchant may serve x402 over MCP
172
+ in Haven's HTTP layering (HTTP 402 with the `PAYMENT-REQUIRED` header or JSON
173
+ body, payment in `PAYMENT-SIGNATURE` / `X-PAYMENT`, settlement in
174
+ `PAYMENT-RESPONSE`) or in the official x402 MCP transport profile (a tool
175
+ result with `isError: true` and the `PaymentRequired` as `structuredContent`,
176
+ payment in `params._meta["x402/payment"]`, settlement in
177
+ `result._meta["x402/payment-response"]`). `fetch()`, `quoteX402()`,
178
+ `quoteMcpX402()` and `completeX402MerchantCall()` handle both: a payment-
179
+ required tool result quotes like a 402, the paid retry carries the header and
180
+ — for a `tools/call` body — the `_meta` object, and settlement is read from the
181
+ header first, the `_meta` second. An in-band refusal (an `isError` challenge on
182
+ the paid retry, or `success: false`) is a rejection even under HTTP 200. An
183
+ ordinary tool error, or a successful result that merely resembles a challenge,
184
+ is never paid for. The tool-result challenge is read only from a response
185
+ declaring `application/json` or `text/event-stream`; any other content type is
186
+ passed through untouched rather than buffered.
187
+
150
188
  ### Idempotency: what the key guarantees, and what it costs
151
189
 
152
190
  **Omit `idempotencyKey` and the SDK synthesises one** from the merchant's
@@ -228,7 +266,7 @@ const data = await response.json()
228
266
 
229
267
  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.
230
268
 
231
- 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.
269
+ 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 address should use a Haven-native flow instead of standard merchant x402.
232
270
 
233
271
  ## AI Agent Integration
234
272
 
@@ -562,6 +600,18 @@ original `tools/call`, setting BOTH `PAYMENT-SIGNATURE` (x402 v2) and `X-PAYMENT
562
600
  user intent so fresh merchant quotes or sessions do not become duplicate Haven
563
601
  payments.
564
602
 
603
+ If the merchant's session is gone by the time the paid retry arrives (it
604
+ expired, or the merchant restarted), a merchant that answers HTTP 404 with
605
+ JSON-RPC `-32001` **and** `error.data = { settled: false, next_action:
606
+ 'reinitialize_then_retry_same_payment_header' }` is stating its own guarantee
607
+ that nothing was settled; since #3171 `fetch()`, `payX402Quote()`,
608
+ `resumeX402Payment()` and `completeX402MerchantCall()` act on exactly that
609
+ shape — re-initialize once and resend the SAME payment header on the new
610
+ session. A bare `-32001` without the data, any other 404, a failed
611
+ re-initialize, or a second 404 is returned to you unchanged and, after
612
+ funding, recorded as a rejection as before: the SDK never resends on an
613
+ inference, only on the merchant's stated guarantee.
614
+
565
615
  See [`examples/mcp-x402-sse.ts`](./examples/mcp-x402-sse.ts) for a complete
566
616
  MCP flow with initialize, `mcp-session-id`, JSON-RPC `tools/call`, quote
567
617
  inspection, saved resume state, and final retry.