@haven_ai/signer 0.7.0-alpha.0 → 0.8.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
@@ -65,7 +65,7 @@ It exposes four stdio MCP tools, all sign-only:
65
65
 
66
66
  | Tool | Does | Emits |
67
67
  |---|---|---|
68
- | `haven_sign` | Sign one payment, or (#3329) one task-budget open/close. Preferred form is `{ payment_id }` alone for a payment, or `{ task_budget_id }` alone (mutually exclusive with `payment_id`) for a task-budget open/close — the signer fetches the exact payload itself either way. Signs only Haven-prepared payloads (#3272, #3281, #3329): a direct-payment `PackedUserOperation` from this signer's own delegate account whose only call redeems a delegation made to that account EITHER directly OR through a self-delegated task-budget child under it (the two-link `[task child, budget]` chain), or — against a Haven-signed context, which it records and binds — an EIP-3009 funding leg of that same shape paying this signer's own delegate EOA, or an erc7710 settlement child from this signer's own account; other typed data is refused (`TYPED_DATA_NOT_ALLOWED`) | `{ signature }`, `{ signature, x402_binding }`, or `{ signature, task_budget_id, purpose }` |
68
+ | `haven_sign` | Sign one payment, or (#3329) one task-budget open/close, or (#3330, #3506) one sub-budget open/close. Preferred form is `{ payment_id }` alone for a payment, `{ task_budget_id }` alone for a task-budget open/close, or `{ sub_budget_id }` alone for a sub-budget this agent delegates (its own parent-child and the grant to another agent of the same account) — exactly one of the three; the signer fetches the exact payload itself. Signs only Haven-prepared payloads (#3272, #3281, #3329): a direct-payment `PackedUserOperation` from this signer's own delegate account whose only call redeems a delegation made to that account EITHER directly OR through a self-delegated task-budget child under it (the two-link `[task child, budget]` chain), or — against a Haven-signed context, which it records and binds — an EIP-3009 funding leg of that same shape paying this signer's own delegate EOA, or an erc7710 settlement child from this signer's own account; a self-delegated task-budget child `Delegation` from this signer's own account (`assertOwnTaskChild`); a sub-budget child `Delegation` from this signer's own account narrowing its budget for another agent of the same account (`assertOwnSubBudgetChild`); or a task-budget or sub-budget close — a `disableDelegation` UserOp from this signer's own account revoking that child (`assertOwnTaskBudgetCloseUserOp`, `assertOwnSubBudgetCloseUserOp`); other typed data is refused (`TYPED_DATA_NOT_ALLOWED`) | `{ signature }`, `{ signature, x402_binding }`, `{ signature, task_budget_id, purpose }`, or `{ signature, sub_budget_id, purpose }` (then `haven_submit` with that id) |
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 }` |
@@ -74,8 +74,9 @@ The `initialize` handshake advertises which binding versions this signer
74
74
  understands, under `capabilities.experimental['haven/signer-compatibility']`
75
75
  and in the MCP `instructions` string. Both are **derived** from
76
76
  `SUPPORTED_X402_EXPECTED_VERSIONS` / `SUPPORTED_SWEEP_BINDING_VERSIONS` in
77
- `src/core.ts` and `SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS` in
78
- `src/sign-context.ts` (#3271) — the same constants the signing path enforces — so this README
77
+ `src/core.ts` and `SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS` (#3271),
78
+ `SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS` (#3329) and
79
+ `SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS` (#3506) in `src/sign-context.ts` — the same constants the signing path enforces — so this README
79
80
  deliberately does not restate the numbers. Read them from the handshake, or
80
81
  from those constants.
81
82
 
@@ -327,8 +328,14 @@ table.** When the x402 fetch answers `SIGN_CONTEXT_REFUSED` with
327
328
  `GET /payments/:id/sign-context` instead, same auth header, timeout and
328
329
  refusal structuring. That second fetch's own refusals reuse the codes in the
329
330
  table below with direct-payment remedies: no quote to re-run, and the
330
- `typed_data_b64` relay from the `haven_send` / `haven_pay` result is the
331
- fallback. A 409 `sign_context_unavailable` from the direct route too (an
331
+ `typed_data_b64` relay — reached by re-running whichever of `haven_send` /
332
+ `haven_pay` was called, with the SAME `idempotency_key` (echoed on its
333
+ result) plus `include_signing_payload: true` (#3495; superseding #3277's
334
+ "the relay fields stay") — is the fallback, EXCEPT the direct fetch's 404
335
+ (the table row below): that message names BOTH routes, since a 404 there is
336
+ ambiguous with a backend old enough to predate #3495 too, whose result
337
+ already carries the pair unconditionally with no opt-in to re-run.
338
+ A 409 `sign_context_unavailable` from the direct route too (an
332
339
  x402 row the x402 route could not serve, or a direct row with no stored
333
340
  signing payload) surfaces the x402 route's own refusal instead. `haven_sign_x402` never takes this branch: a direct payment
334
341
  carries no x402 context to fund a merchant retry with, so it surfaces the
@@ -353,8 +360,12 @@ no direct route, #3271). For x402 it is **not** in the default quote result
353
360
  since #1272: obtain it by re-running the SAME quote tool with the SAME
354
361
  `idempotency_key` plus `include_signing_payload: true`, then pass
355
362
  `typed_data_b64` (plus `payload_hash` / `x402_expected`) instead of
356
- `payment_id`. A direct payment's `haven_send` / `haven_pay` result always
357
- carries `payload_hash` + `typed_data_b64`. Any other backend REFUSAL
363
+ `payment_id`. A direct payment's `haven_send` / `haven_pay` result is compact
364
+ by default too (#3495) — `payload_hash` is on that result unconditionally (as
365
+ is `idempotency_key`, generated fresh when the caller passed none and always
366
+ echoed); only `typed_data` / `typed_data_b64` are withheld, reached the same
367
+ way — a `include_signing_payload: true` re-run on the SAME `idempotency_key`.
368
+ Any other backend REFUSAL
358
369
  carries no fallback: an expired, executed or unsignable intent cannot be
359
370
  rescued by re-signing its bytes — an expired x402 one is re-quoted (the same
360
371
  `payment_window_expired` + `retry_with_new_quote` the signer emits for
package/dist/cli.cjs CHANGED
@@ -14,7 +14,14 @@ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
14
14
  var accounts = require('viem/accounts');
15
15
 
16
16
  var SIGNER_HOSTED_HANDOFF_SHAPES = {
17
- haven_get_payment_status: { payment_id: zod.z.string().min(1) }
17
+ haven_get_payment_status: { payment_id: zod.z.string().min(1) },
18
+ // #3506: `haven_sign` hands a signed budget row to the hosted `haven_submit`
19
+ // (`signTaskBudget` / `signSubBudget` in tools.ts emit exactly these
20
+ // `next_arguments`). One hosted tool, two argument shapes, so each shape is
21
+ // its own entry keyed `<hosted tool>#<variant>`; the parity test reads the
22
+ // part before `#` as the hosted tool it checks the entry against.
23
+ "haven_submit#task_budget": { task_budget_id: zod.z.string().min(1), signature: zod.z.string().regex(/^0x[0-9a-fA-F]+$/) },
24
+ "haven_submit#sub_budget": { sub_budget_id: zod.z.string().min(1), signature: zod.z.string().regex(/^0x[0-9a-fA-F]+$/) }
18
25
  };
19
26
  function target(shape) {
20
27
  const schema = zod.z.object(shape).strict();
@@ -161,7 +168,8 @@ function stableStringify(value) {
161
168
  const object = value;
162
169
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
163
170
  }
164
- var DIRECT_RELAY_FALLBACK = "call haven_sign with payload_hash and typed_data_b64 from the haven_send / haven_pay result, passed through unchanged";
171
+ var DIRECT_RELAY_FALLBACK = "re-run whichever of haven_send / haven_pay you called, with the SAME idempotency_key (echoed on its result) plus include_signing_payload: true, then call haven_sign with payload_hash and typed_data_b64 from THAT re-run's result, passed through unchanged";
172
+ var DIRECT_404_FALLBACK = "either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271 (which also predates #3495, so its result already carries payload_hash and typed_data_b64 unconditionally): pass those fields from the ORIGINAL haven_send / haven_pay result, unchanged, if it already carries them \u2014 or, on a newer server, re-run haven_send / haven_pay with the SAME idempotency_key plus include_signing_payload: true and pass them from THAT result instead";
165
173
  var HavenSignContextError = class extends edge.HavenSigningError {
166
174
  /**
167
175
  * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
@@ -239,7 +247,7 @@ var HavenSignContextError = class extends edge.HavenSigningError {
239
247
  step = signerRefusalStep({
240
248
  nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
241
249
  nextTool: null,
242
- nextToolOmittedReason: DIRECT_RELAY_FALLBACK
250
+ nextToolOmittedReason: DIRECT_404_FALLBACK
243
251
  });
244
252
  } else if (flow === "direct" && refusal?.errorCode === "expired") {
245
253
  this.next_action = edge.AgentPaymentNextAction.PaymentWindowExpired;
@@ -394,7 +402,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
394
402
  } catch (err) {
395
403
  const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
396
404
  throw new HavenSignContextError(
397
- timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the payment result instead.` : `Could not reach Haven to fetch the direct-payment signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the payment result instead.`,
405
+ timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.` : `Could not reach Haven to fetch the direct-payment signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.`,
398
406
  timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
399
407
  void 0,
400
408
  paymentId,
@@ -407,7 +415,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
407
415
  } catch (err) {
408
416
  if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
409
417
  throw new HavenSignContextError(
410
- `Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the payment result instead.`,
418
+ `Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.`,
411
419
  "SIGN_CONTEXT_TIMEOUT",
412
420
  void 0,
413
421
  paymentId,
@@ -419,7 +427,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
419
427
  if (!response.ok) {
420
428
  const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
421
429
  throw new HavenSignContextError(
422
- `Haven refused the direct-payment signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271. Pass payload_hash and typed_data_b64 from the haven_send / haven_pay result, unchanged, instead." : body.error_code === "expired" ? " The payment window has expired; call haven_send / haven_pay again with the same idempotency_key." : ""),
430
+ `Haven refused the direct-payment signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271 (which also predates #3495, so its haven_send / haven_pay result already carries payload_hash and typed_data_b64 unconditionally). Pass payload_hash and typed_data_b64, unchanged, from the original result \u2014 or, on a newer server, from a same-key re-run with include_signing_payload: true." : body.error_code === "expired" ? " The payment window has expired; call haven_send / haven_pay again with the same idempotency_key." : ""),
423
431
  "SIGN_CONTEXT_REFUSED",
424
432
  refusalOf(response.status, body),
425
433
  paymentId,
@@ -430,7 +438,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
430
438
  const version = body.direct_sign_context_version;
431
439
  if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || signData.signature_scheme !== "eip712_userop" || typeof version !== "number" || !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version)) {
432
440
  throw new HavenSignContextError(
433
- typeof version === "number" && !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version) ? `The Haven direct-payment sign-context response is version ${version}, which this signer does not support (supported: ${SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.` : "The Haven direct-payment sign-context response is missing sign_data.typed_data, or its signature_scheme is not 'eip712_userop' \u2014 the backend may predate #3271. Pass typed_data_b64 from the payment result instead.",
441
+ typeof version === "number" && !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version) ? `The Haven direct-payment sign-context response is version ${version}, which this signer does not support (supported: ${SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.` : "The Haven direct-payment sign-context response is missing sign_data.typed_data, or its signature_scheme is not 'eip712_userop' \u2014 the backend may predate #3271. Re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.",
434
442
  "SIGN_CONTEXT_MALFORMED",
435
443
  void 0,
436
444
  paymentId,
@@ -672,7 +680,7 @@ async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fe
672
680
 
673
681
  // src/tools.ts
674
682
  var SIGNER_NAME = "@haven_ai/signer";
675
- var SIGNER_VERSION = "0.7.0-alpha.0";
683
+ var SIGNER_VERSION = "0.8.0-alpha.0";
676
684
  var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
677
685
  var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
678
686
  var HavenUserOpBindingRefusedError = class extends edge.HavenSigningError {
@@ -686,7 +694,7 @@ var HavenUserOpBindingRefusedError = class extends edge.HavenSigningError {
686
694
  this.step = signerRefusalStep({
687
695
  nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
688
696
  nextTool: null,
689
- nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result"
697
+ nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result (or, on a compact direct-payment result, from a same-key re-run with include_signing_payload: true)"
690
698
  });
691
699
  }
692
700
  };
@@ -1492,7 +1500,7 @@ function normalizeError(err) {
1492
1500
 
1493
1501
  // src/consent.ts
1494
1502
  var toolSummaries = {
1495
- haven_sign: "signs a Haven-prepared payment, task-budget open, or task-budget close (a UserOp from your own account whose only call redeems a delegation made to it, directly or through a self-delegated task-budget child, or an x402 funding intent against a Haven-signed context)",
1503
+ haven_sign: "signs a Haven-prepared payment, task-budget or sub-budget open or close (a UserOp from your own account redeeming a delegation made to it, directly or via a task-budget child; a sub-budget child narrowing your budget for another agent of yours; or an x402 funding intent against a Haven-signed context)",
1496
1504
  haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
1497
1505
  haven_sign_x402: "the two above in one call: funding payload + merchant header",
1498
1506
  haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
@@ -2025,7 +2033,8 @@ function signerCompatibility() {
2025
2033
  x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
2026
2034
  sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
2027
2035
  direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
2028
- task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
2036
+ task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS],
2037
+ sub_budget_sign_context_versions: [...SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS]
2029
2038
  };
2030
2039
  }
2031
2040
  function signerCapabilityAdvertisement() {
@@ -2045,6 +2054,7 @@ function signerInstructions() {
2045
2054
  `- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
2046
2055
  `- direct-payment (haven_send / haven_pay) sign-context versions supported: ${compatibility.direct_sign_context_versions.join(", ")} \u2014 pass payment_id alone to haven_sign; this signer fetches the exact bytes`,
2047
2056
  `- task-budget sign-context versions supported: ${compatibility.task_sign_context_versions.join(", ")} \u2014 pass task_budget_id alone to haven_sign; this signer fetches the exact bytes`,
2057
+ `- sub-budget sign-context versions supported: ${compatibility.sub_budget_sign_context_versions.join(", ")} \u2014 pass sub_budget_id alone to haven_sign; this signer fetches the exact bytes (a sub-budget your owner issued narrows your own budget for another agent of your account)`,
2048
2058
  "",
2049
2059
  "Haven quote and prepare results report the expected-context version they will emit",
2050
2060
  "(signer_compatibility.x402_expected_context_version). If that version is not in the list",