@haven_ai/signer 0.0.0-dev.202609250737.3bd5a51 → 0.0.0-dev.202609251409.98ed67a
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 +1 -0
- package/dist/cli.cjs +65 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +66 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +65 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +66 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -335,6 +335,7 @@ carries no x402 context to fund a merchant retry with, so it surfaces the
|
|
|
335
335
|
| `SIGN_CONTEXT_MALFORMED` | The response body was missing `sign_data.typed_data` / `x402_expected` (a pre-#1263 backend), or — on the direct-payment fetch — an unsupported `direct_sign_context_version` or a `signature_scheme` other than `eip712_userop` | `stop_and_tell_user` | `typed_data_b64` | — |
|
|
336
336
|
| `SIGN_CONTEXT_REFUSED` (x402: 410 or `expired`; direct: `expired` only) | x402: the quote's window closed. Direct: the payment's window closed — call `haven_send` / `haven_pay` again with the same `idempotency_key` | `payment_window_expired` | — | x402 only: `retry_with_new_quote: true`; both: `http_status`, and `backend_error_code: 'expired'` when the backend sent one |
|
|
337
337
|
| `SIGN_CONTEXT_REFUSED` (404, direct fetch) | The `payment_id` is not this agent's, or the backend predates #3271 and has no direct route | `stop_and_tell_user` | `typed_data_b64` | `http_status`, `backend_error_code` |
|
|
338
|
+
| `SIGN_CONTEXT_REFUSED` (426 `client_outdated`, #3303) | This signer is below the minimum version the Haven deployment has set (only when one is set). Nothing is signed; the prepared payment is left unsigned. Update with `client_update.upgrade_command`, restart the agent runtime, then retry the same call | `stop_and_tell_user` | — | `http_status: 426`, `backend_error_code: 'client_outdated'`, `client_update` (the update command as data); carries `next_tool_omitted_reason`, not the status read |
|
|
338
339
|
| `SIGN_CONTEXT_REFUSED` (other) | Unknown `payment_id` (404, x402 fetch), `already_executed` / `not_signable` (409), a bare 410 retired-rail tombstone (direct fetch) — or `sign_context_unavailable` (409): always on `haven_sign_x402`; on `haven_sign` only when neither route can serve the row | `stop_and_tell_user` | — | `http_status`, `backend_error_code` |
|
|
339
340
|
| `USEROP_BINDING_MISMATCH` | A direct payment's `PackedUserOperation` typed data (from the direct fetch, or a tool argument) does not recompute to its own `payload_hash`, or a fetched direct context is not a `PackedUserOperation` — see [Two ways to use it](#two-ways-to-use-it) above | `stop_and_tell_user` | — | no `http_status` — this is a local recomputation, not a backend refusal |
|
|
340
341
|
| `TYPED_DATA_NOT_ALLOWED` | Typed data without an x402 context that is not a bound direct-payment `PackedUserOperation` (#3272): wrong chain, not this signer's own account, a call other than `execute` → DelegationManager → `redeemDelegations`, or a redemption whose delegations are empty, not made to this account, or in a non-default mode — see the allowlist paragraph above. Reachable by `payment_id` when Haven serves such a payload | `stop_and_tell_user` | — | no `http_status` — a local shape check, not a backend refusal |
|
package/dist/cli.cjs
CHANGED
|
@@ -211,6 +211,12 @@ var HavenSignContextError = class extends edge.HavenSigningError {
|
|
|
211
211
|
http_status;
|
|
212
212
|
/** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
|
|
213
213
|
backend_error_code;
|
|
214
|
+
/**
|
|
215
|
+
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
216
|
+
* this signer is below the minimum the deployment set, and
|
|
217
|
+
* `client_update.upgrade_command` is what updates it.
|
|
218
|
+
*/
|
|
219
|
+
client_update;
|
|
214
220
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
215
221
|
super(message);
|
|
216
222
|
this.code = code;
|
|
@@ -219,7 +225,15 @@ var HavenSignContextError = class extends edge.HavenSigningError {
|
|
|
219
225
|
if (code === "SIGN_CONTEXT_REFUSED") {
|
|
220
226
|
this.http_status = refusal?.httpStatus;
|
|
221
227
|
this.backend_error_code = refusal?.errorCode;
|
|
222
|
-
if (
|
|
228
|
+
if (refusal?.errorCode === "client_outdated") {
|
|
229
|
+
this.client_update = refusal.clientUpdate;
|
|
230
|
+
this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
|
|
231
|
+
step = signerRefusalStep({
|
|
232
|
+
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
233
|
+
nextTool: null,
|
|
234
|
+
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? "the connector again"}, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
|
|
235
|
+
});
|
|
236
|
+
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
223
237
|
this.fallback = "typed_data_b64";
|
|
224
238
|
this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
|
|
225
239
|
step = signerRefusalStep({
|
|
@@ -277,13 +291,26 @@ async function loadHavenIdentity(credentialsPath) {
|
|
|
277
291
|
}
|
|
278
292
|
}
|
|
279
293
|
var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
|
|
280
|
-
|
|
294
|
+
function signContextHeaders(identity, clientIdentity) {
|
|
295
|
+
return {
|
|
296
|
+
Authorization: `Bearer ${identity.apiKey}`,
|
|
297
|
+
...clientIdentity ? { [edge.HAVEN_CLIENT_HEADER]: clientIdentity } : {}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function refusalOf(status, body) {
|
|
301
|
+
return {
|
|
302
|
+
httpStatus: status,
|
|
303
|
+
errorCode: typeof body.error_code === "string" ? body.error_code : void 0,
|
|
304
|
+
clientUpdate: edge.readClientUpdate(body.client_update)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
281
308
|
let response;
|
|
282
309
|
try {
|
|
283
310
|
response = await fetchImpl(
|
|
284
311
|
`${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
|
|
285
312
|
{
|
|
286
|
-
headers:
|
|
313
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
287
314
|
signal: AbortSignal.timeout(timeoutMs)
|
|
288
315
|
}
|
|
289
316
|
);
|
|
@@ -315,7 +342,7 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
315
342
|
throw new HavenSignContextError(
|
|
316
343
|
`Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : ""),
|
|
317
344
|
"SIGN_CONTEXT_REFUSED",
|
|
318
|
-
|
|
345
|
+
refusalOf(response.status, body),
|
|
319
346
|
paymentId
|
|
320
347
|
);
|
|
321
348
|
}
|
|
@@ -335,17 +362,18 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
335
362
|
payloadHash: signData.hash,
|
|
336
363
|
typedData: signData.typed_data,
|
|
337
364
|
x402Expected,
|
|
338
|
-
paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null
|
|
365
|
+
paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null,
|
|
366
|
+
clientUpdate: edge.readClientUpdate(body.client_update)
|
|
339
367
|
};
|
|
340
368
|
}
|
|
341
369
|
var SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS = [edge.DIRECT_SIGN_CONTEXT_VERSION];
|
|
342
|
-
async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
|
|
370
|
+
async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
343
371
|
let response;
|
|
344
372
|
try {
|
|
345
373
|
response = await fetchImpl(
|
|
346
374
|
`${identity.apiUrl}/payments/${encodeURIComponent(paymentId)}/sign-context`,
|
|
347
375
|
{
|
|
348
|
-
headers:
|
|
376
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
349
377
|
signal: AbortSignal.timeout(timeoutMs)
|
|
350
378
|
}
|
|
351
379
|
);
|
|
@@ -379,7 +407,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
379
407
|
throw new HavenSignContextError(
|
|
380
408
|
`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." : ""),
|
|
381
409
|
"SIGN_CONTEXT_REFUSED",
|
|
382
|
-
|
|
410
|
+
refusalOf(response.status, body),
|
|
383
411
|
paymentId,
|
|
384
412
|
"direct"
|
|
385
413
|
);
|
|
@@ -400,7 +428,8 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
400
428
|
payloadHash: signData.hash,
|
|
401
429
|
typedData: signData.typed_data,
|
|
402
430
|
status: typeof body.status === "string" ? body.status : "pending_signature",
|
|
403
|
-
expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0
|
|
431
|
+
expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0,
|
|
432
|
+
clientUpdate: edge.readClientUpdate(body.client_update)
|
|
404
433
|
};
|
|
405
434
|
}
|
|
406
435
|
|
|
@@ -671,6 +700,9 @@ function parseFetchedExpected(fetched) {
|
|
|
671
700
|
}
|
|
672
701
|
return parsed.data;
|
|
673
702
|
}
|
|
703
|
+
function clientUpdateField(resolved) {
|
|
704
|
+
return resolved?.ctx.clientUpdate ? { client_update: resolved.ctx.clientUpdate } : {};
|
|
705
|
+
}
|
|
674
706
|
function resolveTypedData(args) {
|
|
675
707
|
if (!args.typed_data_b64) return args.typed_data;
|
|
676
708
|
try {
|
|
@@ -704,14 +736,26 @@ function createToolHandlers(signer, options = {}) {
|
|
|
704
736
|
);
|
|
705
737
|
}
|
|
706
738
|
try {
|
|
707
|
-
const ctx = await fetchX402SignContext(
|
|
739
|
+
const ctx = await fetchX402SignContext(
|
|
740
|
+
identity,
|
|
741
|
+
args.payment_id,
|
|
742
|
+
options.signContext?.fetchImpl,
|
|
743
|
+
void 0,
|
|
744
|
+
options.signContext?.clientIdentity
|
|
745
|
+
);
|
|
708
746
|
checkPayloadHashMatch(args.payload_hash, ctx.payloadHash, args.payment_id);
|
|
709
747
|
return { kind: "x402", ctx };
|
|
710
748
|
} catch (err) {
|
|
711
749
|
if (opts.allowDirectFallback && err instanceof HavenSignContextError && err.http_status === 409 && err.backend_error_code === "sign_context_unavailable") {
|
|
712
750
|
let ctx;
|
|
713
751
|
try {
|
|
714
|
-
ctx = await fetchDirectSignContext(
|
|
752
|
+
ctx = await fetchDirectSignContext(
|
|
753
|
+
identity,
|
|
754
|
+
args.payment_id,
|
|
755
|
+
options.signContext?.fetchImpl,
|
|
756
|
+
void 0,
|
|
757
|
+
options.signContext?.clientIdentity
|
|
758
|
+
);
|
|
715
759
|
} catch (directErr) {
|
|
716
760
|
if (directErr instanceof HavenSignContextError && directErr.http_status === 409 && directErr.backend_error_code === "sign_context_unavailable") {
|
|
717
761
|
throw err;
|
|
@@ -761,12 +805,12 @@ function createToolHandlers(signer, options = {}) {
|
|
|
761
805
|
assertBoundDirectPaymentUserOp(typedData, signer.delegateAddress);
|
|
762
806
|
const signature = await signer.signDelegationTypedData(typedData);
|
|
763
807
|
await auditSigning("haven_sign", viem.hashTypedData(typedData));
|
|
764
|
-
return { signature };
|
|
808
|
+
return { signature, ...clientUpdateField(resolved) };
|
|
765
809
|
}
|
|
766
810
|
throw new HavenBareHashRefusedError();
|
|
767
811
|
}
|
|
768
812
|
await auditSigning("haven_sign", viem.hashTypedData(typedData));
|
|
769
|
-
return { signature: result.signature, x402_binding: result.x402Binding };
|
|
813
|
+
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
770
814
|
}),
|
|
771
815
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
772
816
|
const args = parse("haven_x402_sign_header", coercePaymentRequired(input));
|
|
@@ -812,7 +856,8 @@ function createToolHandlers(signer, options = {}) {
|
|
|
812
856
|
signature: funding.signature,
|
|
813
857
|
x402_binding: funding.x402Binding,
|
|
814
858
|
payment_header: header.paymentHeader,
|
|
815
|
-
accepted: header.accepted
|
|
859
|
+
accepted: header.accepted,
|
|
860
|
+
...clientUpdateField(resolved)
|
|
816
861
|
};
|
|
817
862
|
}),
|
|
818
863
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
@@ -951,6 +996,7 @@ function normalizeError(err) {
|
|
|
951
996
|
...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
|
|
952
997
|
...err.http_status !== void 0 ? { http_status: err.http_status } : {},
|
|
953
998
|
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
|
|
999
|
+
...err.client_update ? { client_update: err.client_update } : {},
|
|
954
1000
|
// #3103: the typed step the error decided beside its action.
|
|
955
1001
|
...err.next_tool ? { next_tool: err.next_tool } : {},
|
|
956
1002
|
...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
|
|
@@ -1663,7 +1709,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1663
1709
|
|
|
1664
1710
|
// src/server.ts
|
|
1665
1711
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1666
|
-
var SIGNER_VERSION = "0.0.0-dev.
|
|
1712
|
+
var SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
|
|
1667
1713
|
async function resolveSignerRuntime(options = {}) {
|
|
1668
1714
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1669
1715
|
if (options.delegateKey) {
|
|
@@ -1708,7 +1754,10 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1708
1754
|
// The signer CORE stays network-free; fetched bytes still pass the same
|
|
1709
1755
|
// binding verification + digest re-derivation as tool-argument bytes.
|
|
1710
1756
|
signContext: {
|
|
1711
|
-
loadIdentity: () => loadHavenIdentity(credentialsPath)
|
|
1757
|
+
loadIdentity: () => loadHavenIdentity(credentialsPath),
|
|
1758
|
+
// #3303: the backend refuses a signer below a minimum it has set, at
|
|
1759
|
+
// sign-context — the one place this signer meets it.
|
|
1760
|
+
clientIdentity: edge.havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1712
1761
|
}
|
|
1713
1762
|
});
|
|
1714
1763
|
const registerTool = server.tool.bind(server);
|