@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 +18 -7
- package/dist/cli.cjs +21 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +21 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +21 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +21 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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,
|
|
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`
|
|
78
|
-
`
|
|
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
|
|
331
|
-
|
|
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
|
|
357
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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",
|