@haven_ai/signer 0.8.0-alpha.0 → 0.9.0-alpha.0

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
@@ -61,7 +61,7 @@ metadata found in the credential file, and the sign-only tool list. It refuses
61
61
  to start until acknowledged with either `HAVEN_SIGNER_ACK=<hash>` or
62
62
  `npx @haven_ai/signer@alpha --credentials /path/to/haven-agent.json --ack`.
63
63
 
64
- It exposes four stdio MCP tools, all sign-only:
64
+ It exposes five stdio MCP tools, all sign-only:
65
65
 
66
66
  | Tool | Does | Emits |
67
67
  |---|---|---|
@@ -69,6 +69,7 @@ It exposes four stdio MCP tools, all sign-only:
69
69
  | `haven_sign_x402` | One-shot x402: funding signature **and** the merchant header in a single local call (`haven_sign` + `haven_x402_sign_header`). `{ payment_id }` alone is the preferred call | `{ signature, x402_binding, payment_header, accepted }` |
70
70
  | `haven_x402_sign_header` | Build + sign the EIP-3009 merchant payment header, only when the fresh merchant `payment_required` matches the recorded `x402_binding` | `{ payment_header, accepted }` |
71
71
  | `haven_sign_sweep_delegate` | Sign a Haven-prepared gasless EIP-3009 sweep that recovers stranded funds from the delegate wallet back to your own account. Never broadcasts | `{ signature }` |
72
+ | `haven_sign_siwx` | Answer a merchant x402 Sign-In-With-X (SIWX) challenge (#3728): the signer composes the EIP-4361 message ITSELF from the grammar-validated `sign-in-with-x` challenge and the delegate address read from the key — never a caller-supplied message — signs it EIP-191, and returns the finished header. Refuses (`SIWX_REFUSED`) unless the challenge domain matches the `url` host (the FINAL URL after redirects — never follow a redirect with the header or a resulting session token attached), the resource uri origin matches, the URL is https, the nonce is well-formed, the expiry window is ≤ 5 minutes (plus a small clock-skew tolerance), and `supportedChains` offers the credential chain with `eip191`. Moves no funds and grants no spend authority | `{ sign_in_with_x_header }` — retry the merchant with the `SIGN-IN-WITH-X` header set to that value |
72
73
 
73
74
  The `initialize` handshake advertises which binding versions this signer
74
75
  understands, under `capabilities.experimental['haven/signer-compatibility']`
@@ -99,8 +100,11 @@ const { paymentHeader } = await signer.buildX402PaymentHeader(
99
100
  )
100
101
  ```
101
102
 
102
- The signer also exposes `signSweepAuthorization(input)` for the gasless sweep.
103
- All four are methods on the object `createEdgeSigner` returns, not standalone
103
+ The signer also exposes `signSweepAuthorization(input)` for the gasless sweep and
104
+ `signSiwxMessage(message)` for #3728's composed EIP-4361 sign-in — the message
105
+ always reaches it composed in-package from grammar-validated challenge fields,
106
+ with the address taken from the key.
107
+ All five are methods on the object `createEdgeSigner` returns, not standalone
104
108
  exports. There is NO raw-hash primitive: `signPaymentHash(hash)` (raw ECDSA over
105
109
  the retired AllowanceModule rail's hash) was removed in #3169, and
106
110
  `signX402FundingHash` (the expected-context v1 bare-hash path) in #3272 — the
@@ -202,6 +206,26 @@ relays ("Connection closed" plus this process's exit message) names the same
202
206
  command. The consent hash covers identity, tool names and the surface version —
203
207
  not the block's prose — so neither change re-prompts an acknowledged install.
204
208
 
209
+ ## Several Haven pairs on one machine (#3738)
210
+
211
+ One harness can carry several Haven pairs — `haven-research` +
212
+ `haven-signer-research`, `haven-ops` + `haven-signer-ops`, alongside a bare
213
+ `haven` + `haven-signer` (Codex: `haven` + `haven_signer`). A model must act as
214
+ one agent per task and keep hosted and signer calls inside that pair. Server
215
+ names are easy to cross, so the signer's `initialize` instructions state what
216
+ it is bound to:
217
+
218
+ ```
219
+ This signer is bound to agent id <agent_id> and delegate address <0x…>.
220
+ ```
221
+
222
+ The agent id comes from the credential file's `agent_id` (or `HAVEN_AGENT_ID`
223
+ alongside `HAVEN_DELEGATE_KEY`); when neither is set the line says "no recorded
224
+ agent id" and still gives the delegate address, which is then compared alone.
225
+ The model compares it with `haven_get_agent`'s `id` and `delegateAddress` from
226
+ the hosted server it called, and signs nothing on a mismatch. Neither value is secret (the consent screen prints both), and
227
+ the line is advisory: what the signer refuses to sign is unchanged.
228
+
205
229
  ## Orchestration
206
230
 
207
231
  Direct payment:
@@ -289,6 +313,17 @@ what a payload means; they re-derive it.
289
313
  - **A binding version it does not understand.** The refusal is machine-readable
290
314
  — `code`, `supported_versions`, `received_version`, `fallback`, and (#3103)
291
315
  `next_tool_omitted_reason` — and names updating the signer as the fix.
316
+ - **A sign-in challenge that does not describe the origin it is sent to (#3728).**
317
+ `haven_sign_siwx` composes the EIP-4361 message itself from the challenge's
318
+ grammar-validated fields and the delegate address from the key; a challenge
319
+ whose domain is not the host of the `url` being called, whose resource uri
320
+ origin differs, that is not https, that carries a malformed or missing nonce,
321
+ a `version` other than `"1"`, an expiry window beyond the spec's 5-minute max
322
+ age (plus a small clock-skew tolerance), or that does not offer the credential
323
+ chain with an `eip191` signature type is refused with no signature and no
324
+ audit entry. A caller-supplied message, hash or byte string is never signed —
325
+ a SIWX signature therefore cannot double as a payment authorization, a
326
+ UserOp signature or any other Haven signature.
292
327
  - **A sweep that does not move funds out of this delegate's own key** — the
293
328
  `from` check is unconditional. The **destination** check is not, and this is
294
329
  the one asymmetry in this list: the signer compares the sweep's `to` against
@@ -318,7 +353,7 @@ refusal class — `fallback`, `retry_with_new_quote`, `http_status`,
318
353
  `SIGN_CONTEXT_REFUSED` (other) row names the hosted status read
319
354
  (`next_tool_server_role: hosted`, `next_tool_name: haven_get_payment_status`,
320
355
  `next_arguments: { payment_id }` — resolve the role against your own server
321
- names); every other row carries `next_tool_omitted_reason` with the exact
356
+ names, and with several Haven pairs against this signer's own pair); every other row carries `next_tool_omitted_reason` with the exact
322
357
  remedy. `message` is unchanged.
323
358
 
324
359
  **#3271: `haven_sign` (never `haven_sign_x402`) has one escape from this
@@ -381,7 +416,7 @@ The delegate key is read from `HAVEN_DELEGATE_KEY` or a `--credentials` file's
381
416
  `delegate_key` (with a permissive-file warning). It stays in this process, and
382
417
  is never transmitted.
383
418
 
384
- **The signer makes at most two kinds of network call, both reads.** Since
419
+ **The signer's network calls are all reads of a signing context.** Since
385
420
  [#1263](https://github.com/d-hinders/Haven-AI/issues/1263) the `{ payment_id }`
386
421
  form of `haven_sign` and `haven_sign_x402` performs an authenticated,
387
422
  read-only `GET /x402/:payment_id/sign-context` against Haven, so that agents
@@ -389,13 +424,16 @@ never have to relay multi-KB EIP-712 payloads through a model's context
389
424
  window. Since #3271, `haven_sign` (never `haven_sign_x402`) falls back to a
390
425
  second read — `GET /payments/:payment_id/sign-context` — only when that first
391
426
  fetch answers the backend's 409 `sign_context_unavailable`, i.e. this
392
- `payment_id` names a direct payment rather than an x402 intent. **Only the
393
- Bearer API key goes out on either read; the delegate key is never part of
394
- either request or response.** Since #2985 both reads are bounded: each aborts
427
+ `payment_id` names a direct payment rather than an x402 intent. Since
428
+ #3329/#3330, `haven_sign` given a `task_budget_id` or `sub_budget_id` reads
429
+ `GET /task-budgets/:id/sign-context` or `GET /sub-budgets/:id/sign-context`.
430
+ **Only the Bearer API key and the signer's `X-Haven-Client` version header go
431
+ out on any read; the delegate key is never part of a request or response.** Since #2985 the two payment reads are bounded (the budget reads use the same
432
+ 15 s bound): each payment read aborts
395
433
  after `SIGN_CONTEXT_TIMEOUT_MS` (15 s) and reports a `HavenSignContextError`
396
434
  naming the timeout and the `typed_data_b64` fallback, so a hung backend cannot
397
435
  hang the signer — and the agent — past the funding window. Every refusal on
398
- either fetch (timeout, unreachable host, a non-ok backend response, a
436
+ any of these fetches (timeout, unreachable host, a non-ok backend response, a
399
437
  malformed body) is structured the same way, not just prose — see
400
438
  [Sign-context refusal codes](#sign-context-refusal-codes) below.
401
439
  Nothing else in the package reaches the