@haven_ai/signer 0.6.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 +25 -10
- package/dist/cli.cjs +287 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +288 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +288 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -4
- package/dist/index.d.ts +46 -4
- package/dist/index.js +289 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID, createHash } from 'crypto';
|
|
2
2
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
-
import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode,
|
|
4
|
+
import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorUpgradeCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, TASK_SIGN_CONTEXT_VERSION, DIRECT_SIGN_CONTEXT_VERSION, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError, connectorRerunCommand, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, assertOwnSubBudgetChild, assertOwnSubBudgetCloseUserOp, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
5
5
|
export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
|
|
6
6
|
import { stat, lstat, chmod, readFile, mkdir, rename, appendFile, writeFile } from 'fs/promises';
|
|
7
7
|
import { resolve, dirname, join } from 'path';
|
|
@@ -254,7 +254,7 @@ function assertSupportedBindingVersion(received, supported, context) {
|
|
|
254
254
|
const highest = Math.max(...supported);
|
|
255
255
|
const outOfDate = received > highest;
|
|
256
256
|
const code = context === "x402 expected context" ? SignerRefusalCode.UnsupportedExpectedContextVersion : SignerRefusalCode.UnsupportedSweepBindingVersion;
|
|
257
|
-
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014
|
|
257
|
+
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014 run \`${connectorUpgradeCommand()}\`, then the repair line it prints, which reinstalls the pinned MCP runtime.` : `Unsupported ${context} version ${received}: this signer supports ${supported.join(", ")}.`;
|
|
258
258
|
const fallback = outOfDate ? SIGNER_UPDATE_FALLBACK : `This ${context} version (${received}) is older than what this signer enforces (${supported.join(", ")}) \u2014 updating @haven_ai/signer will not restore it. Nothing was signed or spent; stop and tell the user rather than retrying.`;
|
|
259
259
|
throw new HavenUnsupportedSignerVersionError(
|
|
260
260
|
`${ceiling} Nothing was signed. Do not rewrite the version field to a supported value: it is part of the Haven-signed binding message, so changing it invalidates the signature and would misrepresent what Haven declared.`,
|
|
@@ -349,7 +349,14 @@ function sameAddress(a, b) {
|
|
|
349
349
|
return a.toLowerCase() === b.toLowerCase();
|
|
350
350
|
}
|
|
351
351
|
var SIGNER_HOSTED_HANDOFF_SHAPES = {
|
|
352
|
-
haven_get_payment_status: { payment_id: z.string().min(1) }
|
|
352
|
+
haven_get_payment_status: { payment_id: z.string().min(1) },
|
|
353
|
+
// #3506: `haven_sign` hands a signed budget row to the hosted `haven_submit`
|
|
354
|
+
// (`signTaskBudget` / `signSubBudget` in tools.ts emit exactly these
|
|
355
|
+
// `next_arguments`). One hosted tool, two argument shapes, so each shape is
|
|
356
|
+
// its own entry keyed `<hosted tool>#<variant>`; the parity test reads the
|
|
357
|
+
// part before `#` as the hosted tool it checks the entry against.
|
|
358
|
+
"haven_submit#task_budget": { task_budget_id: z.string().min(1), signature: z.string().regex(/^0x[0-9a-fA-F]+$/) },
|
|
359
|
+
"haven_submit#sub_budget": { sub_budget_id: z.string().min(1), signature: z.string().regex(/^0x[0-9a-fA-F]+$/) }
|
|
353
360
|
};
|
|
354
361
|
function target(shape) {
|
|
355
362
|
const schema = z.object(shape).strict();
|
|
@@ -380,7 +387,8 @@ function nextStepWireFields(step) {
|
|
|
380
387
|
}
|
|
381
388
|
|
|
382
389
|
// src/sign-context.ts
|
|
383
|
-
var DIRECT_RELAY_FALLBACK = "call haven_sign with payload_hash and typed_data_b64 from
|
|
390
|
+
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";
|
|
391
|
+
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";
|
|
384
392
|
var HavenSignContextError = class extends HavenSigningError {
|
|
385
393
|
/**
|
|
386
394
|
* #3001 / #3010 review: the recovery path, when signing OTHER bytes is
|
|
@@ -433,7 +441,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
433
441
|
/**
|
|
434
442
|
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
435
443
|
* this signer is below the minimum the deployment set, and
|
|
436
|
-
* `client_update.upgrade_command`
|
|
444
|
+
* `client_update.upgrade_command` starts the update (#3412: for the signer, the doctor, then its repair line).
|
|
437
445
|
*/
|
|
438
446
|
client_update;
|
|
439
447
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
@@ -450,7 +458,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
450
458
|
step = signerRefusalStep({
|
|
451
459
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
452
460
|
nextTool: null,
|
|
453
|
-
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ??
|
|
461
|
+
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? connectorUpgradeCommand()} and the repair line it prints, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
|
|
454
462
|
});
|
|
455
463
|
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
456
464
|
this.fallback = "typed_data_b64";
|
|
@@ -458,7 +466,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
458
466
|
step = signerRefusalStep({
|
|
459
467
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
460
468
|
nextTool: null,
|
|
461
|
-
nextToolOmittedReason:
|
|
469
|
+
nextToolOmittedReason: DIRECT_404_FALLBACK
|
|
462
470
|
});
|
|
463
471
|
} else if (flow === "direct" && refusal?.errorCode === "expired") {
|
|
464
472
|
this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
|
|
@@ -613,7 +621,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
613
621
|
} catch (err) {
|
|
614
622
|
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
615
623
|
throw new HavenSignContextError(
|
|
616
|
-
timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from
|
|
624
|
+
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.`,
|
|
617
625
|
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
618
626
|
void 0,
|
|
619
627
|
paymentId,
|
|
@@ -626,7 +634,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
626
634
|
} catch (err) {
|
|
627
635
|
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
628
636
|
throw new HavenSignContextError(
|
|
629
|
-
`Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from
|
|
637
|
+
`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.`,
|
|
630
638
|
"SIGN_CONTEXT_TIMEOUT",
|
|
631
639
|
void 0,
|
|
632
640
|
paymentId,
|
|
@@ -638,7 +646,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
638
646
|
if (!response.ok) {
|
|
639
647
|
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
640
648
|
throw new HavenSignContextError(
|
|
641
|
-
`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
|
|
649
|
+
`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." : ""),
|
|
642
650
|
"SIGN_CONTEXT_REFUSED",
|
|
643
651
|
refusalOf(response.status, body),
|
|
644
652
|
paymentId,
|
|
@@ -649,7 +657,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
649
657
|
const version = body.direct_sign_context_version;
|
|
650
658
|
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)) {
|
|
651
659
|
throw new HavenSignContextError(
|
|
652
|
-
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.
|
|
660
|
+
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.",
|
|
653
661
|
"SIGN_CONTEXT_MALFORMED",
|
|
654
662
|
void 0,
|
|
655
663
|
paymentId,
|
|
@@ -665,7 +673,120 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
665
673
|
clientUpdate: readClientUpdate(body.client_update)
|
|
666
674
|
};
|
|
667
675
|
}
|
|
668
|
-
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [
|
|
676
|
+
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [TASK_SIGN_CONTEXT_VERSION];
|
|
677
|
+
var SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS = [1];
|
|
678
|
+
async function fetchSubBudgetSignContext(identity, subBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
679
|
+
let response;
|
|
680
|
+
try {
|
|
681
|
+
response = await fetchImpl(
|
|
682
|
+
`${identity.apiUrl}/sub-budgets/${encodeURIComponent(subBudgetId)}/sign-context`,
|
|
683
|
+
{
|
|
684
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
685
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
690
|
+
throw new HavenSignContextError(
|
|
691
|
+
timedOut ? `Haven did not answer the sub-budget signing-context fetch for ${subBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the sub-budget signing context for ${subBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
692
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
693
|
+
void 0,
|
|
694
|
+
subBudgetId,
|
|
695
|
+
"task"
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
let body;
|
|
699
|
+
try {
|
|
700
|
+
body = await response.json();
|
|
701
|
+
} catch (err) {
|
|
702
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
703
|
+
throw new HavenSignContextError(
|
|
704
|
+
`Haven did not finish sending the sub-budget signing context for ${subBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
705
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
706
|
+
void 0,
|
|
707
|
+
subBudgetId,
|
|
708
|
+
"task"
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
body = {};
|
|
712
|
+
}
|
|
713
|
+
if (!response.ok) {
|
|
714
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
715
|
+
throw new HavenSignContextError(
|
|
716
|
+
`Haven refused the sub-budget signing-context fetch for ${subBudgetId}: ${detail}`,
|
|
717
|
+
"SIGN_CONTEXT_REFUSED",
|
|
718
|
+
refusalOf(response.status, body),
|
|
719
|
+
subBudgetId,
|
|
720
|
+
"task"
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const purpose = body.purpose;
|
|
724
|
+
const typedData = body.typed_data;
|
|
725
|
+
const version = body.sub_budget_sign_context_version;
|
|
726
|
+
const expected = body.expected;
|
|
727
|
+
const malformed = (detail) => {
|
|
728
|
+
throw new HavenSignContextError(
|
|
729
|
+
`The Haven sub-budget sign-context response for ${subBudgetId} is malformed: ${detail}.`,
|
|
730
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
731
|
+
void 0,
|
|
732
|
+
subBudgetId,
|
|
733
|
+
"task"
|
|
734
|
+
);
|
|
735
|
+
};
|
|
736
|
+
if (typeof version !== "number" || !SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
737
|
+
throw new HavenSignContextError(
|
|
738
|
+
`The Haven sub-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
|
|
739
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
740
|
+
void 0,
|
|
741
|
+
subBudgetId,
|
|
742
|
+
"task"
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
746
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
747
|
+
const e = expected;
|
|
748
|
+
if (purpose === "open") {
|
|
749
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.period_amount_atomic !== "string" || typeof e.period_duration_seconds !== "number" || typeof e.start_date !== "number" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string" || typeof e.child_delegate_account !== "string") {
|
|
750
|
+
return malformed("expected is missing a required field for purpose open");
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
purpose: "open",
|
|
754
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
755
|
+
typedData,
|
|
756
|
+
expected: {
|
|
757
|
+
delegateAccount: e.delegate_account,
|
|
758
|
+
chainId: e.chain_id,
|
|
759
|
+
tokenAddress: e.token_address,
|
|
760
|
+
periodAmountAtomic: e.period_amount_atomic,
|
|
761
|
+
periodDurationSeconds: e.period_duration_seconds,
|
|
762
|
+
startDate: e.start_date,
|
|
763
|
+
recipientAddress: e.recipient_address ?? null,
|
|
764
|
+
expiresAt: e.expires_at,
|
|
765
|
+
parentDelegationHash: e.parent_delegation_hash,
|
|
766
|
+
childDelegateAccount: e.child_delegate_account
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (purpose === "close") {
|
|
771
|
+
const userOpHash = body.user_op_hash;
|
|
772
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
773
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
774
|
+
return malformed("expected is missing a required field for purpose close");
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
purpose: "close",
|
|
778
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
779
|
+
typedData,
|
|
780
|
+
userOpHash,
|
|
781
|
+
expected: {
|
|
782
|
+
delegateAccount: e.delegate_account,
|
|
783
|
+
chainId: e.chain_id,
|
|
784
|
+
delegationHash: e.delegation_hash
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
789
|
+
}
|
|
669
790
|
async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
670
791
|
let response;
|
|
671
792
|
try {
|
|
@@ -783,7 +904,8 @@ function signerCompatibility() {
|
|
|
783
904
|
x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
|
|
784
905
|
sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
|
|
785
906
|
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
|
|
786
|
-
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
|
|
907
|
+
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS],
|
|
908
|
+
sub_budget_sign_context_versions: [...SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS]
|
|
787
909
|
};
|
|
788
910
|
}
|
|
789
911
|
function signerCapabilityAdvertisement() {
|
|
@@ -803,12 +925,13 @@ function signerInstructions() {
|
|
|
803
925
|
`- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
|
|
804
926
|
`- 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`,
|
|
805
927
|
`- 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`,
|
|
928
|
+
`- 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)`,
|
|
806
929
|
"",
|
|
807
930
|
"Haven quote and prepare results report the expected-context version they will emit",
|
|
808
931
|
"(signer_compatibility.x402_expected_context_version). If that version is not in the list",
|
|
809
932
|
"above, this signer is out of date: STOP before signing, and tell the user to update",
|
|
810
|
-
`@haven_ai/signer
|
|
811
|
-
"MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
933
|
+
`@haven_ai/signer: run \`${connectorUpgradeCommand()}\`, then the repair line it prints,`,
|
|
934
|
+
"which reinstalls the pinned MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
812
935
|
"Haven-signed binding message, so changing it invalidates the signature.",
|
|
813
936
|
"",
|
|
814
937
|
"A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
|
|
@@ -816,7 +939,13 @@ function signerInstructions() {
|
|
|
816
939
|
"fallback fields alongside the message, so you can branch on it directly. Every signer",
|
|
817
940
|
"refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
|
|
818
941
|
"hosted tool follows (resolve the role against your own server names), else",
|
|
819
|
-
"next_tool_omitted_reason saying why not."
|
|
942
|
+
"next_tool_omitted_reason saying why not.",
|
|
943
|
+
"",
|
|
944
|
+
"An undeclared top-level argument is refused, not stripped: haven_sign answers",
|
|
945
|
+
"UNSUPPORTED_ARGUMENT with unknown_arguments, signer_version and fallback (the update",
|
|
946
|
+
"command) instead of dropping the key and answering the generic signing error \u2014 if you see",
|
|
947
|
+
"that refusal, this signer predates the argument form you sent. Update the signer; nothing",
|
|
948
|
+
"was signed, fetched or audited."
|
|
820
949
|
].join("\n");
|
|
821
950
|
}
|
|
822
951
|
var OWNER_ONLY_MODE = 384;
|
|
@@ -1035,6 +1164,9 @@ var HavenBareHashRefusedError = class extends HavenSigningError {
|
|
|
1035
1164
|
};
|
|
1036
1165
|
|
|
1037
1166
|
// src/tools.ts
|
|
1167
|
+
var SIGNER_NAME = "@haven_ai/signer";
|
|
1168
|
+
var SIGNER_VERSION = "0.8.0-alpha.0";
|
|
1169
|
+
var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
|
|
1038
1170
|
var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
|
|
1039
1171
|
var HavenUserOpBindingRefusedError = class extends HavenSigningError {
|
|
1040
1172
|
next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -1047,7 +1179,7 @@ var HavenUserOpBindingRefusedError = class extends HavenSigningError {
|
|
|
1047
1179
|
this.step = signerRefusalStep({
|
|
1048
1180
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1049
1181
|
nextTool: null,
|
|
1050
|
-
nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result"
|
|
1182
|
+
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)"
|
|
1051
1183
|
});
|
|
1052
1184
|
}
|
|
1053
1185
|
};
|
|
@@ -1162,7 +1294,13 @@ var toolSchemas = {
|
|
|
1162
1294
|
// Mutually exclusive with payment_id / payload_hash (checked in the
|
|
1163
1295
|
// handler, since Zod's object-level refine on a raw shape used by
|
|
1164
1296
|
// several call sites is not the shape this schema is composed as).
|
|
1165
|
-
task_budget_id: z$1.string().min(1).optional()
|
|
1297
|
+
task_budget_id: z$1.string().min(1).optional(),
|
|
1298
|
+
// #3330: sign a sub-budget's own child delegation or its early-close
|
|
1299
|
+
// UserOp — same fetch-verify-sign channel as task_budget_id, against
|
|
1300
|
+
// `GET /sub-budgets/:id/sign-context`. Mutually exclusive with
|
|
1301
|
+
// payment_id / payload_hash AND with task_budget_id (checked in the
|
|
1302
|
+
// handler).
|
|
1303
|
+
sub_budget_id: z$1.string().min(1).optional()
|
|
1166
1304
|
},
|
|
1167
1305
|
haven_x402_sign_header: {
|
|
1168
1306
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
@@ -1443,9 +1581,76 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1443
1581
|
next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
|
|
1444
1582
|
};
|
|
1445
1583
|
}
|
|
1584
|
+
async function signSubBudget(subBudgetId) {
|
|
1585
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
1586
|
+
if (!identity) {
|
|
1587
|
+
throw new HavenSigningError(
|
|
1588
|
+
`sub_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.`
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
const ctx = await fetchSubBudgetSignContext(
|
|
1592
|
+
identity,
|
|
1593
|
+
subBudgetId,
|
|
1594
|
+
options.signContext?.fetchImpl,
|
|
1595
|
+
void 0,
|
|
1596
|
+
options.signContext?.clientIdentity
|
|
1597
|
+
);
|
|
1598
|
+
let signature;
|
|
1599
|
+
if (ctx.purpose === "open") {
|
|
1600
|
+
try {
|
|
1601
|
+
assertOwnSubBudgetChild(
|
|
1602
|
+
ctx.typedData,
|
|
1603
|
+
{
|
|
1604
|
+
chainId: ctx.expected.chainId,
|
|
1605
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
1606
|
+
periodAmountAtomic: ctx.expected.periodAmountAtomic,
|
|
1607
|
+
periodDurationSeconds: ctx.expected.periodDurationSeconds,
|
|
1608
|
+
startDate: ctx.expected.startDate,
|
|
1609
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
1610
|
+
expiresAt: ctx.expected.expiresAt,
|
|
1611
|
+
parentDelegationHash: ctx.expected.parentDelegationHash,
|
|
1612
|
+
delegateAccount: ctx.expected.delegateAccount,
|
|
1613
|
+
childDelegateAccount: ctx.expected.childDelegateAccount
|
|
1614
|
+
},
|
|
1615
|
+
signer.delegateAddress
|
|
1616
|
+
);
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1619
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1620
|
+
}
|
|
1621
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1622
|
+
} else {
|
|
1623
|
+
try {
|
|
1624
|
+
assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
1627
|
+
throw err;
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
assertOwnSubBudgetCloseUserOp(
|
|
1631
|
+
ctx.typedData,
|
|
1632
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
1633
|
+
signer.delegateAddress
|
|
1634
|
+
);
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1637
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1638
|
+
}
|
|
1639
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1640
|
+
}
|
|
1641
|
+
await auditSigning("haven_sign", hashTypedData(ctx.typedData));
|
|
1642
|
+
return {
|
|
1643
|
+
signature,
|
|
1644
|
+
sub_budget_id: ctx.subBudgetId,
|
|
1645
|
+
purpose: ctx.purpose,
|
|
1646
|
+
next_tool_name: "haven_submit",
|
|
1647
|
+
next_tool_server_role: "hosted",
|
|
1648
|
+
next_arguments: { sub_budget_id: ctx.subBudgetId, signature }
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1446
1651
|
return {
|
|
1447
1652
|
haven_sign: async (input) => runTool(async () => {
|
|
1448
|
-
const args =
|
|
1653
|
+
const args = parseStrictFor("haven_sign", coerceX402Expected(input));
|
|
1449
1654
|
if (args.task_budget_id) {
|
|
1450
1655
|
if (args.payment_id || args.payload_hash) {
|
|
1451
1656
|
throw new z$1.ZodError([
|
|
@@ -1456,8 +1661,29 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1456
1661
|
}
|
|
1457
1662
|
]);
|
|
1458
1663
|
}
|
|
1664
|
+
if (args.sub_budget_id) {
|
|
1665
|
+
throw new z$1.ZodError([
|
|
1666
|
+
{
|
|
1667
|
+
code: z$1.ZodIssueCode.custom,
|
|
1668
|
+
path: ["sub_budget_id"],
|
|
1669
|
+
message: "task_budget_id is mutually exclusive with sub_budget_id \u2014 pass exactly one."
|
|
1670
|
+
}
|
|
1671
|
+
]);
|
|
1672
|
+
}
|
|
1459
1673
|
return signTaskBudget(args.task_budget_id);
|
|
1460
1674
|
}
|
|
1675
|
+
if (args.sub_budget_id) {
|
|
1676
|
+
if (args.payment_id || args.payload_hash) {
|
|
1677
|
+
throw new z$1.ZodError([
|
|
1678
|
+
{
|
|
1679
|
+
code: z$1.ZodIssueCode.custom,
|
|
1680
|
+
path: ["sub_budget_id"],
|
|
1681
|
+
message: "sub_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
1682
|
+
}
|
|
1683
|
+
]);
|
|
1684
|
+
}
|
|
1685
|
+
return signSubBudget(args.sub_budget_id);
|
|
1686
|
+
}
|
|
1461
1687
|
const resolved = await resolveSignContext(args, { allowDirectFallback: true });
|
|
1462
1688
|
const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
|
|
1463
1689
|
const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
|
|
@@ -1500,7 +1726,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1500
1726
|
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
1501
1727
|
}),
|
|
1502
1728
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
1503
|
-
const args =
|
|
1729
|
+
const args = parseStrictFor("haven_x402_sign_header", coercePaymentRequired(input));
|
|
1504
1730
|
const result = await signer.buildX402PaymentHeader(
|
|
1505
1731
|
args.payment_required,
|
|
1506
1732
|
args.x402_binding
|
|
@@ -1512,7 +1738,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1512
1738
|
return { payment_header: result.paymentHeader, accepted: result.accepted };
|
|
1513
1739
|
}),
|
|
1514
1740
|
haven_sign_x402: async (input) => runTool(async () => {
|
|
1515
|
-
const args =
|
|
1741
|
+
const args = parseStrictFor("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
|
|
1516
1742
|
const resolved = await resolveSignContext(args, { allowDirectFallback: false });
|
|
1517
1743
|
const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
|
|
1518
1744
|
const payloadHash = fetched?.payloadHash ?? args.payload_hash;
|
|
@@ -1548,7 +1774,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1548
1774
|
};
|
|
1549
1775
|
}),
|
|
1550
1776
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
1551
|
-
const args =
|
|
1777
|
+
const args = parseStrictFor("haven_sign_sweep_delegate", input);
|
|
1552
1778
|
const result = await signer.signSweepAuthorization({
|
|
1553
1779
|
authorization: args.authorization,
|
|
1554
1780
|
expectedAuth: args.expected_auth,
|
|
@@ -1581,8 +1807,18 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1581
1807
|
}
|
|
1582
1808
|
}
|
|
1583
1809
|
}
|
|
1584
|
-
function
|
|
1585
|
-
|
|
1810
|
+
function parseStrictFor(name, input) {
|
|
1811
|
+
const result = z$1.object(toolSchemas[name]).strict().safeParse(input ?? {});
|
|
1812
|
+
if (result.success) return result.data;
|
|
1813
|
+
const topLevelUnknown = result.error.issues.flatMap(
|
|
1814
|
+
(issue) => issue.code === "unrecognized_keys" && issue.path.length === 0 ? issue.keys : []
|
|
1815
|
+
);
|
|
1816
|
+
if (topLevelUnknown.length === 0) throw result.error;
|
|
1817
|
+
const unknown = [...new Set(topLevelUnknown)];
|
|
1818
|
+
const err = new Error(
|
|
1819
|
+
`${UNSUPPORTED_ARGUMENT_MARKER} ${name} does not accept ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 this signer predates that argument form. Nothing was signed or fetched.`
|
|
1820
|
+
);
|
|
1821
|
+
throw err;
|
|
1586
1822
|
}
|
|
1587
1823
|
function coerceX402Expected(input) {
|
|
1588
1824
|
if (!input || typeof input !== "object") return input;
|
|
@@ -1621,6 +1857,25 @@ async function runTool(fn) {
|
|
|
1621
1857
|
}
|
|
1622
1858
|
}
|
|
1623
1859
|
function normalizeError(err) {
|
|
1860
|
+
if (err instanceof Error && err.message.startsWith(UNSUPPORTED_ARGUMENT_MARKER)) {
|
|
1861
|
+
const unknown = [...err.message.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
1862
|
+
return {
|
|
1863
|
+
success: false,
|
|
1864
|
+
code: "UNSUPPORTED_ARGUMENT",
|
|
1865
|
+
message: err.message.replace(`${UNSUPPORTED_ARGUMENT_MARKER} `, ""),
|
|
1866
|
+
unknown_arguments: unknown,
|
|
1867
|
+
signer_version: SIGNER_VERSION,
|
|
1868
|
+
fallback: connectorUpgradeCommand(),
|
|
1869
|
+
next_action: AgentPaymentNextAction.StopAndTellUser,
|
|
1870
|
+
...nextStepWireFields(
|
|
1871
|
+
signerRefusalStep({
|
|
1872
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1873
|
+
nextTool: null,
|
|
1874
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1875
|
+
})
|
|
1876
|
+
)
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1624
1879
|
if (err instanceof z$1.ZodError) {
|
|
1625
1880
|
return {
|
|
1626
1881
|
success: false,
|
|
@@ -1642,7 +1897,9 @@ function normalizeError(err) {
|
|
|
1642
1897
|
...nextStepWireFields(signerRefusalStep({
|
|
1643
1898
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1644
1899
|
nextTool: null,
|
|
1645
|
-
|
|
1900
|
+
// #3412: the upgrade command, not "re-run the connector" — a bare re-run
|
|
1901
|
+
// stops at "Missing --setup" on an existing install.
|
|
1902
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1646
1903
|
}))
|
|
1647
1904
|
};
|
|
1648
1905
|
}
|
|
@@ -1728,7 +1985,7 @@ function normalizeError(err) {
|
|
|
1728
1985
|
|
|
1729
1986
|
// src/consent.ts
|
|
1730
1987
|
var toolSummaries = {
|
|
1731
|
-
haven_sign: "signs a Haven-prepared payment, task-budget
|
|
1988
|
+
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)",
|
|
1732
1989
|
haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
|
|
1733
1990
|
haven_sign_x402: "the two above in one call: funding payload + merchant header",
|
|
1734
1991
|
haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
|
|
@@ -1857,8 +2114,6 @@ async function writeAckFile(path, hash) {
|
|
|
1857
2114
|
"utf8"
|
|
1858
2115
|
);
|
|
1859
2116
|
}
|
|
1860
|
-
var SIGNER_NAME = "@haven_ai/signer";
|
|
1861
|
-
var SIGNER_VERSION = "0.6.0-alpha.0";
|
|
1862
2117
|
async function resolveEdgeSigner(options = {}) {
|
|
1863
2118
|
const { signer } = await resolveSignerRuntime(options);
|
|
1864
2119
|
return signer;
|
|
@@ -1913,12 +2168,14 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1913
2168
|
clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1914
2169
|
}
|
|
1915
2170
|
});
|
|
1916
|
-
const registerTool = server.
|
|
2171
|
+
const registerTool = server.registerTool.bind(server);
|
|
1917
2172
|
for (const name of Object.keys(toolSchemas)) {
|
|
1918
2173
|
registerTool(
|
|
1919
2174
|
name,
|
|
1920
|
-
|
|
1921
|
-
|
|
2175
|
+
{
|
|
2176
|
+
description: toolDescriptions[name],
|
|
2177
|
+
inputSchema: z.object(toolSchemas[name]).passthrough()
|
|
2178
|
+
},
|
|
1922
2179
|
async (args) => toMcpResult(await handlers[name](args))
|
|
1923
2180
|
);
|
|
1924
2181
|
}
|
|
@@ -1972,6 +2229,6 @@ function toMcpResult(payload) {
|
|
|
1972
2229
|
};
|
|
1973
2230
|
}
|
|
1974
2231
|
|
|
1975
|
-
export { AUDIT_ROTATE_BYTES, OWNER_ONLY_MODE, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, permissiveMode, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, tightenIfFilePermissive, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive, warnIfFilePermissive };
|
|
2232
|
+
export { AUDIT_ROTATE_BYTES, OWNER_ONLY_MODE, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, UNSUPPORTED_ARGUMENT_MARKER, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, permissiveMode, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, tightenIfFilePermissive, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive, warnIfFilePermissive };
|
|
1976
2233
|
//# sourceMappingURL=index.js.map
|
|
1977
2234
|
//# sourceMappingURL=index.js.map
|