@haven_ai/signer 0.5.0-alpha.1 → 0.6.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 +5 -3
- package/dist/cli.cjs +217 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +218 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +217 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +218 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { randomUUID, createHash } from 'crypto';
|
|
3
3
|
import { readFile, mkdir, writeFile, stat, lstat, rename, appendFile, chmod } from 'fs/promises';
|
|
4
4
|
import { resolve, dirname, join } from 'path';
|
|
5
|
-
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, connectorRerunCommand, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, DIRECT_SIGN_CONTEXT_VERSION, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, HavenUserOpBindingError, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
5
|
+
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, connectorRerunCommand, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, DIRECT_SIGN_CONTEXT_VERSION, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, HavenUserOpBindingError, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
6
6
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
7
7
|
import { z as z$1 } from 'zod/v3';
|
|
8
8
|
import { z } from 'zod';
|
|
@@ -254,6 +254,13 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
254
254
|
nextTool: null,
|
|
255
255
|
nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
|
|
256
256
|
});
|
|
257
|
+
} else if (flow === "task") {
|
|
258
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
259
|
+
step = signerRefusalStep({
|
|
260
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
261
|
+
nextTool: null,
|
|
262
|
+
nextToolOmittedReason: `re-check this task budget's status (id ${paymentId}) with the hosted Haven server before retrying`
|
|
263
|
+
});
|
|
257
264
|
} else {
|
|
258
265
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
259
266
|
step = signerRefusalStep({
|
|
@@ -262,6 +269,13 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
262
269
|
nextArguments: { payment_id: paymentId }
|
|
263
270
|
});
|
|
264
271
|
}
|
|
272
|
+
} else if (flow === "task") {
|
|
273
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
274
|
+
step = signerRefusalStep({
|
|
275
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
276
|
+
nextTool: null,
|
|
277
|
+
nextToolOmittedReason: "retry the same haven_sign call with the same task_budget_id"
|
|
278
|
+
});
|
|
265
279
|
} else {
|
|
266
280
|
this.fallback = "typed_data_b64";
|
|
267
281
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -430,6 +444,116 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
430
444
|
clientUpdate: readClientUpdate(body.client_update)
|
|
431
445
|
};
|
|
432
446
|
}
|
|
447
|
+
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [1];
|
|
448
|
+
async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
449
|
+
let response;
|
|
450
|
+
try {
|
|
451
|
+
response = await fetchImpl(
|
|
452
|
+
`${identity.apiUrl}/task-budgets/${encodeURIComponent(taskBudgetId)}/sign-context`,
|
|
453
|
+
{
|
|
454
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
455
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
460
|
+
throw new HavenSignContextError(
|
|
461
|
+
timedOut ? `Haven did not answer the task-budget signing-context fetch for ${taskBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the task-budget signing context for ${taskBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
462
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
463
|
+
void 0,
|
|
464
|
+
taskBudgetId,
|
|
465
|
+
"task"
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
let body;
|
|
469
|
+
try {
|
|
470
|
+
body = await response.json();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
473
|
+
throw new HavenSignContextError(
|
|
474
|
+
`Haven did not finish sending the task-budget signing context for ${taskBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
475
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
476
|
+
void 0,
|
|
477
|
+
taskBudgetId,
|
|
478
|
+
"task"
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
body = {};
|
|
482
|
+
}
|
|
483
|
+
if (!response.ok) {
|
|
484
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
485
|
+
throw new HavenSignContextError(
|
|
486
|
+
`Haven refused the task-budget signing-context fetch for ${taskBudgetId}: ${detail}`,
|
|
487
|
+
"SIGN_CONTEXT_REFUSED",
|
|
488
|
+
refusalOf(response.status, body),
|
|
489
|
+
taskBudgetId,
|
|
490
|
+
"task"
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
const purpose = body.purpose;
|
|
494
|
+
const typedData = body.typed_data;
|
|
495
|
+
const version = body.task_sign_context_version;
|
|
496
|
+
const expected = body.expected;
|
|
497
|
+
const malformed = (detail) => {
|
|
498
|
+
throw new HavenSignContextError(
|
|
499
|
+
`The Haven task-budget sign-context response for ${taskBudgetId} is malformed: ${detail}.`,
|
|
500
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
501
|
+
void 0,
|
|
502
|
+
taskBudgetId,
|
|
503
|
+
"task"
|
|
504
|
+
);
|
|
505
|
+
};
|
|
506
|
+
if (typeof version !== "number" || !SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
507
|
+
throw new HavenSignContextError(
|
|
508
|
+
`The Haven task-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
|
|
509
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
510
|
+
void 0,
|
|
511
|
+
taskBudgetId,
|
|
512
|
+
"task"
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
516
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
517
|
+
const e = expected;
|
|
518
|
+
if (purpose === "open") {
|
|
519
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.max_amount_atomic !== "string" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string") {
|
|
520
|
+
return malformed("expected is missing a required field for purpose open");
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
purpose: "open",
|
|
524
|
+
taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
|
|
525
|
+
typedData,
|
|
526
|
+
expected: {
|
|
527
|
+
delegateAccount: e.delegate_account,
|
|
528
|
+
chainId: e.chain_id,
|
|
529
|
+
tokenAddress: e.token_address,
|
|
530
|
+
maxAmountAtomic: e.max_amount_atomic,
|
|
531
|
+
recipientAddress: e.recipient_address ?? null,
|
|
532
|
+
expiresAt: e.expires_at,
|
|
533
|
+
parentDelegationHash: e.parent_delegation_hash
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (purpose === "close") {
|
|
538
|
+
const userOpHash = body.user_op_hash;
|
|
539
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
540
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
541
|
+
return malformed("expected is missing a required field for purpose close");
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
purpose: "close",
|
|
545
|
+
taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
|
|
546
|
+
typedData,
|
|
547
|
+
userOpHash,
|
|
548
|
+
expected: {
|
|
549
|
+
delegateAccount: e.delegate_account,
|
|
550
|
+
chainId: e.chain_id,
|
|
551
|
+
delegationHash: e.delegation_hash
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
556
|
+
}
|
|
433
557
|
|
|
434
558
|
// src/tools.ts
|
|
435
559
|
var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
|
|
@@ -552,7 +676,14 @@ var toolSchemas = {
|
|
|
552
676
|
// Bounded: a realistic redemption payload is ~10KB encoded; 256KB is
|
|
553
677
|
// generous headroom while keeping the offline signer from materializing
|
|
554
678
|
// arbitrarily large caller input.
|
|
555
|
-
typed_data_b64: z$1.string().min(1).max(262144).optional()
|
|
679
|
+
typed_data_b64: z$1.string().min(1).max(262144).optional(),
|
|
680
|
+
// #3329: sign a task budget's own child delegation (purpose `open`) or
|
|
681
|
+
// its early-close UserOp (purpose `close`) — the signer fetches the
|
|
682
|
+
// exact typed data and expectation itself, exactly like payment_id.
|
|
683
|
+
// Mutually exclusive with payment_id / payload_hash (checked in the
|
|
684
|
+
// handler, since Zod's object-level refine on a raw shape used by
|
|
685
|
+
// several call sites is not the shape this schema is composed as).
|
|
686
|
+
task_budget_id: z$1.string().min(1).optional()
|
|
556
687
|
},
|
|
557
688
|
haven_x402_sign_header: {
|
|
558
689
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
@@ -602,7 +733,11 @@ var SIGN_DESCRIPTION = [
|
|
|
602
733
|
"intent against a Haven-signed context \u2014 never an arbitrary typed_data payload, however it is shaped.",
|
|
603
734
|
"Next: call mcp__haven__haven_submit with signature, then pass x402_binding",
|
|
604
735
|
"to mcp__haven-signer__haven_x402_sign_header. A bare payload_hash with no payment_id, typed_data",
|
|
605
|
-
"or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify."
|
|
736
|
+
"or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify.",
|
|
737
|
+
"TASK BUDGETS (#3329): pass task_budget_id ALONE \u2014 mutually exclusive with payment_id and",
|
|
738
|
+
"payload_hash \u2014 to sign a task budget\u2019s own child delegation (opening it) or its early-close",
|
|
739
|
+
"UserOp; this signer fetches the exact typed data itself. Result then carries next_tool_name",
|
|
740
|
+
"(haven_submit) and next_arguments { task_budget_id, signature } instead of x402_binding."
|
|
606
741
|
].join(" ");
|
|
607
742
|
var X402_SIGN_HEADER_DESCRIPTION = [
|
|
608
743
|
"Build and sign the EIP-3009 merchant payment header for the merchant leg of an x402 payment.",
|
|
@@ -766,9 +901,84 @@ function createToolHandlers(signer, options = {}) {
|
|
|
766
901
|
throw err;
|
|
767
902
|
}
|
|
768
903
|
}
|
|
904
|
+
async function signTaskBudget(taskBudgetId) {
|
|
905
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
906
|
+
if (!identity) {
|
|
907
|
+
throw new HavenSigningError(
|
|
908
|
+
`task_budget_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${connectorRerunCommand()}\` to restore it.`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
const ctx = await fetchTaskBudgetSignContext(
|
|
912
|
+
identity,
|
|
913
|
+
taskBudgetId,
|
|
914
|
+
options.signContext?.fetchImpl,
|
|
915
|
+
void 0,
|
|
916
|
+
options.signContext?.clientIdentity
|
|
917
|
+
);
|
|
918
|
+
let signature;
|
|
919
|
+
if (ctx.purpose === "open") {
|
|
920
|
+
try {
|
|
921
|
+
assertOwnTaskChild(
|
|
922
|
+
ctx.typedData,
|
|
923
|
+
{
|
|
924
|
+
chainId: ctx.expected.chainId,
|
|
925
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
926
|
+
maxAmountAtomic: ctx.expected.maxAmountAtomic,
|
|
927
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
928
|
+
expiresAt: ctx.expected.expiresAt,
|
|
929
|
+
parentDelegationHash: ctx.expected.parentDelegationHash
|
|
930
|
+
},
|
|
931
|
+
signer.delegateAddress
|
|
932
|
+
);
|
|
933
|
+
} catch (err) {
|
|
934
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
935
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
936
|
+
}
|
|
937
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
938
|
+
} else {
|
|
939
|
+
try {
|
|
940
|
+
assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
941
|
+
} catch (err) {
|
|
942
|
+
if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
943
|
+
throw err;
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
assertOwnTaskBudgetCloseUserOp(
|
|
947
|
+
ctx.typedData,
|
|
948
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
949
|
+
signer.delegateAddress
|
|
950
|
+
);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
953
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
954
|
+
}
|
|
955
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
956
|
+
}
|
|
957
|
+
await auditSigning("haven_sign", hashTypedData(ctx.typedData));
|
|
958
|
+
return {
|
|
959
|
+
signature,
|
|
960
|
+
task_budget_id: ctx.taskBudgetId,
|
|
961
|
+
purpose: ctx.purpose,
|
|
962
|
+
next_tool_name: "haven_submit",
|
|
963
|
+
next_tool_server_role: "hosted",
|
|
964
|
+
next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
|
|
965
|
+
};
|
|
966
|
+
}
|
|
769
967
|
return {
|
|
770
968
|
haven_sign: async (input) => runTool(async () => {
|
|
771
969
|
const args = parse("haven_sign", coerceX402Expected(input));
|
|
970
|
+
if (args.task_budget_id) {
|
|
971
|
+
if (args.payment_id || args.payload_hash) {
|
|
972
|
+
throw new z$1.ZodError([
|
|
973
|
+
{
|
|
974
|
+
code: z$1.ZodIssueCode.custom,
|
|
975
|
+
path: ["task_budget_id"],
|
|
976
|
+
message: "task_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
977
|
+
}
|
|
978
|
+
]);
|
|
979
|
+
}
|
|
980
|
+
return signTaskBudget(args.task_budget_id);
|
|
981
|
+
}
|
|
772
982
|
const resolved = await resolveSignContext(args, { allowDirectFallback: true });
|
|
773
983
|
const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
|
|
774
984
|
const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
|
|
@@ -1039,7 +1249,7 @@ function normalizeError(err) {
|
|
|
1039
1249
|
|
|
1040
1250
|
// src/consent.ts
|
|
1041
1251
|
var toolSummaries = {
|
|
1042
|
-
haven_sign: "signs
|
|
1252
|
+
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)",
|
|
1043
1253
|
haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
|
|
1044
1254
|
haven_sign_x402: "the two above in one call: funding payload + merchant header",
|
|
1045
1255
|
haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
|
|
@@ -1571,7 +1781,8 @@ function signerCompatibility() {
|
|
|
1571
1781
|
return {
|
|
1572
1782
|
x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
|
|
1573
1783
|
sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
|
|
1574
|
-
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS]
|
|
1784
|
+
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
|
|
1785
|
+
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
|
|
1575
1786
|
};
|
|
1576
1787
|
}
|
|
1577
1788
|
function signerCapabilityAdvertisement() {
|
|
@@ -1590,6 +1801,7 @@ function signerInstructions() {
|
|
|
1590
1801
|
`- x402 expected-context versions supported: ${compatibility.x402_expected_context_versions.join(", ")}`,
|
|
1591
1802
|
`- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
|
|
1592
1803
|
`- 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`,
|
|
1804
|
+
`- 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`,
|
|
1593
1805
|
"",
|
|
1594
1806
|
"Haven quote and prepare results report the expected-context version they will emit",
|
|
1595
1807
|
"(signer_compatibility.x402_expected_context_version). If that version is not in the list",
|
|
@@ -1707,7 +1919,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1707
1919
|
|
|
1708
1920
|
// src/server.ts
|
|
1709
1921
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1710
|
-
var SIGNER_VERSION = "0.
|
|
1922
|
+
var SIGNER_VERSION = "0.6.0-alpha.0";
|
|
1711
1923
|
async function resolveSignerRuntime(options = {}) {
|
|
1712
1924
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1713
1925
|
if (options.delegateKey) {
|