@haven_ai/signer 0.2.1-alpha.0 → 0.4.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 +12 -5
- package/dist/cli.cjs +130 -19
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +176 -65
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +131 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +244 -24
- package/dist/index.d.ts +244 -24
- package/dist/index.js +177 -66
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -2,13 +2,14 @@ import { randomUUID, createHash } from 'crypto';
|
|
|
2
2
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
4
|
import { exact } from 'x402/schemes';
|
|
5
|
-
import { addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, isSupportedNodeVersion, unsupportedNodeVersionMessage, signHash } from '@haven_ai/sdk';
|
|
5
|
+
import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, isSupportedNodeVersion, unsupportedNodeVersionMessage, signHash } from '@haven_ai/sdk';
|
|
6
6
|
import { readFile, stat, mkdir, appendFile, writeFile } from 'fs/promises';
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { resolve, dirname, join } from 'path';
|
|
11
|
-
import { z } from 'zod/v3';
|
|
11
|
+
import { z as z$1 } from 'zod/v3';
|
|
12
|
+
import { z } from 'zod';
|
|
12
13
|
|
|
13
14
|
// src/core.ts
|
|
14
15
|
var DELEGATION_MANAGER = "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3";
|
|
@@ -494,7 +495,10 @@ function signerInstructions() {
|
|
|
494
495
|
"",
|
|
495
496
|
"A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
|
|
496
497
|
"machine-readable, not just prose: it carries code, supported_versions, received_version, and",
|
|
497
|
-
"fallback fields alongside the message, so you can branch on it directly."
|
|
498
|
+
"fallback fields alongside the message, so you can branch on it directly. Every signer",
|
|
499
|
+
"refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
|
|
500
|
+
"hosted tool follows (resolve the role against your own server names), else",
|
|
501
|
+
"next_tool_omitted_reason saying why not."
|
|
498
502
|
].join("\n");
|
|
499
503
|
}
|
|
500
504
|
async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
@@ -506,7 +510,6 @@ async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
|
506
510
|
delegateKey: envKey,
|
|
507
511
|
agentId: stringField(process.env.HAVEN_AGENT_ID),
|
|
508
512
|
accountAddress,
|
|
509
|
-
safeAddress: accountAddress,
|
|
510
513
|
chainId: chainIdField(process.env.HAVEN_CHAIN_ID, "HAVEN_CHAIN_ID"),
|
|
511
514
|
network: stringField(process.env.HAVEN_NETWORK),
|
|
512
515
|
x402BindingSigner: stringField(process.env.HAVEN_X402_BINDING_SIGNER)
|
|
@@ -541,7 +544,6 @@ async function loadFromFile(path) {
|
|
|
541
544
|
delegateKey,
|
|
542
545
|
agentId: stringField(raw.agent_id ?? raw.agentId),
|
|
543
546
|
accountAddress,
|
|
544
|
-
safeAddress: accountAddress,
|
|
545
547
|
chainId: chainIdField(raw.chain_id ?? raw.chainId, "chain_id"),
|
|
546
548
|
network: stringField(raw.network),
|
|
547
549
|
x402BindingSigner: stringField(
|
|
@@ -554,7 +556,22 @@ function readAccountAddressField(raw) {
|
|
|
554
556
|
return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
|
|
555
557
|
}
|
|
556
558
|
function readAccountAddressEnv(env) {
|
|
557
|
-
|
|
559
|
+
const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
|
|
560
|
+
for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
|
|
561
|
+
const retired = stringField(env[name]);
|
|
562
|
+
if (!retired) continue;
|
|
563
|
+
if (!current) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`${name} is retired (#2906) \u2014 Haven accounts are addressed as accounts, not Safes. Set HAVEN_ACCOUNT_ADDRESS to the same value. Refusing rather than ignoring it, because an unset account address silently skips the sweep-destination check.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (retired.toLowerCase() !== current.toLowerCase()) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`${name} and HAVEN_ACCOUNT_ADDRESS were both set to different addresses. Remove the retired name, or make them match \u2014 picking one silently would hide the mismatch.`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return current;
|
|
558
575
|
}
|
|
559
576
|
function stringField(value) {
|
|
560
577
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -611,7 +628,7 @@ function createSigningAuditEntry(tool, payloadHash, context, now = /* @__PURE__
|
|
|
611
628
|
payload_hash: payloadHash,
|
|
612
629
|
delegate_address: context.delegateAddress
|
|
613
630
|
};
|
|
614
|
-
if (context.
|
|
631
|
+
if (context.accountAddress) entry.safe_address = context.accountAddress;
|
|
615
632
|
if (typeof context.chainId === "number") entry.chain_id = context.chainId;
|
|
616
633
|
return entry;
|
|
617
634
|
}
|
|
@@ -627,6 +644,38 @@ function stableStringify(value) {
|
|
|
627
644
|
const object = value;
|
|
628
645
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
|
|
629
646
|
}
|
|
647
|
+
var SIGNER_HOSTED_HANDOFF_SHAPES = {
|
|
648
|
+
haven_get_payment_status: { payment_id: z.string().min(1) }
|
|
649
|
+
};
|
|
650
|
+
function target(shape) {
|
|
651
|
+
const schema = z.object(shape).strict();
|
|
652
|
+
return {
|
|
653
|
+
role: "hosted",
|
|
654
|
+
validate: (input) => {
|
|
655
|
+
const r = schema.safeParse(input ?? {});
|
|
656
|
+
return r.success ? null : r.error.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join("; ");
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
var SIGNER_NEXT_STEP_TARGETS = {
|
|
661
|
+
haven_get_payment_status: target(SIGNER_HOSTED_HANDOFF_SHAPES.haven_get_payment_status)
|
|
662
|
+
};
|
|
663
|
+
var nextStep = createNextStepBuilder(SIGNER_NEXT_STEP_TARGETS);
|
|
664
|
+
function signerRefusalStep(input) {
|
|
665
|
+
return nextStep({ ...input, safeToContinue: false, reason: "" });
|
|
666
|
+
}
|
|
667
|
+
function nextStepWireFields(step) {
|
|
668
|
+
return {
|
|
669
|
+
...step.next_tool ? { next_tool: step.next_tool } : {},
|
|
670
|
+
...step.next_tool_server ? { next_tool_server: step.next_tool_server } : {},
|
|
671
|
+
...step.next_tool_name ? { next_tool_name: step.next_tool_name } : {},
|
|
672
|
+
...step.next_tool_server_role ? { next_tool_server_role: step.next_tool_server_role } : {},
|
|
673
|
+
...step.next_arguments ? { next_arguments: step.next_arguments } : {},
|
|
674
|
+
...step.next_tool_omitted_reason ? { next_tool_omitted_reason: step.next_tool_omitted_reason } : {}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/sign-context.ts
|
|
630
679
|
var HavenSignContextError = class extends HavenSigningError {
|
|
631
680
|
/**
|
|
632
681
|
* #3001 / #3010 review: the recovery path, when signing OTHER bytes is
|
|
@@ -646,28 +695,63 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
646
695
|
* plain `HavenError` branch already does for that code.
|
|
647
696
|
*/
|
|
648
697
|
next_action;
|
|
698
|
+
/**
|
|
699
|
+
* #3103 (epic #3105, decisions 1 and 3): the typed next step beside the
|
|
700
|
+
* action. A refusal Haven made (`SIGN_CONTEXT_REFUSED`, not expired) names
|
|
701
|
+
* the hosted status read with the payment id; a transport failure or a
|
|
702
|
+
* malformed body names no tool — the remedy is re-running the SAME quote
|
|
703
|
+
* tool with `include_signing_payload: true` — and an expired window names
|
|
704
|
+
* none either (which quote tool depends on the flow). Never null: a step
|
|
705
|
+
* with no tool carries `next_tool_omitted_reason`. Additive: the class is
|
|
706
|
+
* not exported from the package index — the published surface is the
|
|
707
|
+
* `ToolFailure` envelope `tools.ts` builds from these fields — and every
|
|
708
|
+
* constructor call site is in this file.
|
|
709
|
+
*/
|
|
710
|
+
next_tool;
|
|
711
|
+
next_tool_server;
|
|
712
|
+
next_tool_name;
|
|
713
|
+
next_tool_server_role;
|
|
714
|
+
next_arguments;
|
|
715
|
+
next_tool_omitted_reason;
|
|
649
716
|
retry_with_new_quote;
|
|
650
717
|
/** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
|
|
651
718
|
http_status;
|
|
652
719
|
/** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
|
|
653
720
|
backend_error_code;
|
|
654
|
-
constructor(message, code, refusal) {
|
|
721
|
+
constructor(message, code, refusal, paymentId) {
|
|
655
722
|
super(message);
|
|
656
723
|
this.code = code;
|
|
657
724
|
this.name = "HavenSignContextError";
|
|
725
|
+
let step;
|
|
658
726
|
if (code === "SIGN_CONTEXT_REFUSED") {
|
|
659
727
|
this.http_status = refusal?.httpStatus;
|
|
660
728
|
this.backend_error_code = refusal?.errorCode;
|
|
661
729
|
if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
|
|
662
730
|
this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
|
|
663
731
|
this.retry_with_new_quote = true;
|
|
732
|
+
step = signerRefusalStep({
|
|
733
|
+
nextAction: AgentPaymentNextAction.PaymentWindowExpired,
|
|
734
|
+
nextTool: null,
|
|
735
|
+
nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
|
|
736
|
+
});
|
|
664
737
|
} else {
|
|
665
738
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
739
|
+
step = signerRefusalStep({
|
|
740
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
741
|
+
nextTool: "haven_get_payment_status",
|
|
742
|
+
nextArguments: { payment_id: paymentId }
|
|
743
|
+
});
|
|
666
744
|
}
|
|
667
745
|
} else {
|
|
668
746
|
this.fallback = "typed_data_b64";
|
|
669
747
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
748
|
+
step = signerRefusalStep({
|
|
749
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
750
|
+
nextTool: null,
|
|
751
|
+
nextToolOmittedReason: "re-run the SAME hosted quote tool with the same idempotency_key and include_signing_payload: true, then pass its typed_data_b64 to this signer"
|
|
752
|
+
});
|
|
670
753
|
}
|
|
754
|
+
Object.assign(this, nextStepWireFields(step));
|
|
671
755
|
}
|
|
672
756
|
};
|
|
673
757
|
async function loadHavenIdentity(credentialsPath) {
|
|
@@ -699,7 +783,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
699
783
|
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
700
784
|
throw new HavenSignContextError(
|
|
701
785
|
timedOut ? `Haven did not answer the signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.` : `Could not reach Haven to fetch the signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the quote result instead.`,
|
|
702
|
-
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
|
|
786
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
787
|
+
void 0,
|
|
788
|
+
paymentId
|
|
703
789
|
);
|
|
704
790
|
}
|
|
705
791
|
let body;
|
|
@@ -709,7 +795,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
709
795
|
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
710
796
|
throw new HavenSignContextError(
|
|
711
797
|
`Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
|
|
712
|
-
"SIGN_CONTEXT_TIMEOUT"
|
|
798
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
799
|
+
void 0,
|
|
800
|
+
paymentId
|
|
713
801
|
);
|
|
714
802
|
}
|
|
715
803
|
body = {};
|
|
@@ -719,7 +807,8 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
719
807
|
throw new HavenSignContextError(
|
|
720
808
|
`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." : ""),
|
|
721
809
|
"SIGN_CONTEXT_REFUSED",
|
|
722
|
-
{ httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
|
|
810
|
+
{ httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 },
|
|
811
|
+
paymentId
|
|
723
812
|
);
|
|
724
813
|
}
|
|
725
814
|
const signData = body.sign_data;
|
|
@@ -727,7 +816,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
727
816
|
if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
|
|
728
817
|
throw new HavenSignContextError(
|
|
729
818
|
"The Haven sign-context response is missing sign_data.typed_data or x402_expected \u2014 the backend may predate #1263. Pass typed_data_b64 from the quote result instead.",
|
|
730
|
-
"SIGN_CONTEXT_MALFORMED"
|
|
819
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
820
|
+
void 0,
|
|
821
|
+
paymentId
|
|
731
822
|
);
|
|
732
823
|
}
|
|
733
824
|
const paymentRequired = body.payment_required;
|
|
@@ -741,54 +832,54 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
741
832
|
}
|
|
742
833
|
|
|
743
834
|
// src/tools.ts
|
|
744
|
-
var sweepAuthorizationSchema = z.object({
|
|
745
|
-
from: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "from must be a 0x address"),
|
|
746
|
-
to: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "to must be a 0x address"),
|
|
747
|
-
value: z.string().regex(/^[0-9]+$/, "value must be a decimal atomic amount"),
|
|
748
|
-
validAfter: z.string().regex(/^[0-9]+$/, "validAfter must be a decimal unix time"),
|
|
749
|
-
validBefore: z.string().regex(/^[0-9]+$/, "validBefore must be a decimal unix time"),
|
|
750
|
-
nonce: z.string().regex(/^0x[0-9a-fA-F]{64}$/, "nonce must be a 0x-prefixed 32-byte hex string"),
|
|
751
|
-
token: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "token must be a 0x address"),
|
|
752
|
-
chainId: z.number().int().positive()
|
|
835
|
+
var sweepAuthorizationSchema = z$1.object({
|
|
836
|
+
from: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "from must be a 0x address"),
|
|
837
|
+
to: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "to must be a 0x address"),
|
|
838
|
+
value: z$1.string().regex(/^[0-9]+$/, "value must be a decimal atomic amount"),
|
|
839
|
+
validAfter: z$1.string().regex(/^[0-9]+$/, "validAfter must be a decimal unix time"),
|
|
840
|
+
validBefore: z$1.string().regex(/^[0-9]+$/, "validBefore must be a decimal unix time"),
|
|
841
|
+
nonce: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "nonce must be a 0x-prefixed 32-byte hex string"),
|
|
842
|
+
token: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "token must be a 0x address"),
|
|
843
|
+
chainId: z$1.number().int().positive()
|
|
753
844
|
});
|
|
754
|
-
var bindingVersionSchema = z.number().int().positive();
|
|
755
|
-
var sweepExpectedAuthSchema = z.object({
|
|
845
|
+
var bindingVersionSchema = z$1.number().int().positive();
|
|
846
|
+
var sweepExpectedAuthSchema = z$1.object({
|
|
756
847
|
version: bindingVersionSchema,
|
|
757
|
-
message: z.string().min(1),
|
|
758
|
-
signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
|
|
759
|
-
signer: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "signer must be a 0x address")
|
|
848
|
+
message: z$1.string().min(1),
|
|
849
|
+
signature: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
|
|
850
|
+
signer: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "signer must be a 0x address")
|
|
760
851
|
});
|
|
761
852
|
var x402ExpectedShape = {
|
|
762
|
-
payment_id: z.string().min(1),
|
|
763
|
-
payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
|
|
764
|
-
resource_url: z.string().url(),
|
|
765
|
-
merchant_to: z.string().min(1),
|
|
766
|
-
amount: z.string().min(1),
|
|
767
|
-
asset: z.string().min(1),
|
|
768
|
-
network: z.string().min(1),
|
|
853
|
+
payment_id: z$1.string().min(1),
|
|
854
|
+
payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
|
|
855
|
+
resource_url: z$1.string().url(),
|
|
856
|
+
merchant_to: z$1.string().min(1),
|
|
857
|
+
amount: z$1.string().min(1),
|
|
858
|
+
asset: z$1.string().min(1),
|
|
859
|
+
network: z$1.string().min(1),
|
|
769
860
|
// Required: expires_at is folded into the Haven-signed binding message, so the
|
|
770
861
|
// signer must receive the exact same value to reconstruct a matching message.
|
|
771
862
|
// Omitting it used to fail downstream with a cryptic "authentication message is
|
|
772
863
|
// invalid" — making it required surfaces a clear INVALID_INPUT at the boundary.
|
|
773
|
-
expires_at: z.string().min(1),
|
|
864
|
+
expires_at: z$1.string().min(1),
|
|
774
865
|
// #1138: present on a delegation-rail (v2) context. It commits to the EIP-712
|
|
775
866
|
// typed data the account validates; the signer refuses to raw-sign the bare
|
|
776
867
|
// hash when it is present, and refuses to sign typed data when it is absent.
|
|
777
|
-
typed_data_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "typed_data_hash must be a 0x-prefixed hex string").optional(),
|
|
868
|
+
typed_data_hash: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "typed_data_hash must be a 0x-prefixed hex string").optional(),
|
|
778
869
|
// #1690: present on a v3 context. The delegate this quote was created FOR —
|
|
779
870
|
// the signer refuses to sign when it is not its own. Inside the Haven-signed
|
|
780
871
|
// message, so stripping it here would only break the binding signature.
|
|
781
|
-
payer_delegate: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "payer_delegate must be a 0x-prefixed address").optional(),
|
|
782
|
-
payer_agent_id: z.string().min(1).optional(),
|
|
783
|
-
auth: z.object({
|
|
872
|
+
payer_delegate: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "payer_delegate must be a 0x-prefixed address").optional(),
|
|
873
|
+
payer_agent_id: z$1.string().min(1).optional(),
|
|
874
|
+
auth: z$1.object({
|
|
784
875
|
// Open at the boundary, enforced in the signer — see bindingVersionSchema.
|
|
785
876
|
version: bindingVersionSchema,
|
|
786
|
-
message: z.string().min(1),
|
|
787
|
-
signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
|
|
788
|
-
signer: z.string().min(1)
|
|
877
|
+
message: z$1.string().min(1),
|
|
878
|
+
signature: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
|
|
879
|
+
signer: z$1.string().min(1)
|
|
789
880
|
})
|
|
790
881
|
};
|
|
791
|
-
var x402ExpectedSchema = z.object(x402ExpectedShape);
|
|
882
|
+
var x402ExpectedSchema = z$1.object(x402ExpectedShape);
|
|
792
883
|
var toolSchemas = {
|
|
793
884
|
haven_sign_sweep_delegate: {
|
|
794
885
|
// The authorization fields prepared by Haven's POST /sweep/prepare. Passed
|
|
@@ -803,17 +894,17 @@ var toolSchemas = {
|
|
|
803
894
|
// payload + expected context from Haven itself (authenticated with the
|
|
804
895
|
// locally-stored agent credential), so no bulky bytes ever cross the
|
|
805
896
|
// model. Pass payment_id ALONE for a delegation-rail x402 funding intent.
|
|
806
|
-
payment_id: z.string().min(1).optional(),
|
|
897
|
+
payment_id: z$1.string().min(1).optional(),
|
|
807
898
|
// The unsigned hash from haven_pay / haven_pay_x402_quote (payload_hash).
|
|
808
899
|
// Optional when payment_id is supplied (the fetch carries it).
|
|
809
|
-
payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
|
|
900
|
+
payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
|
|
810
901
|
// Pass x402.expected from hosted haven_pay_x402_quote when this hash funds
|
|
811
902
|
// a standard x402 merchant retry. The signer records it locally and returns
|
|
812
903
|
// an opaque x402_binding for the later header-signing step.
|
|
813
904
|
x402_expected: x402ExpectedSchema.optional(),
|
|
814
905
|
// #1138: delegation-rail intents sign THIS, not payload_hash. Object-typed
|
|
815
906
|
// (not z.unknown()) so MCP clients embed it as JSON rather than a string.
|
|
816
|
-
typed_data: z.record(z.string(), z.unknown()).optional(),
|
|
907
|
+
typed_data: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
817
908
|
// #1255: the same payload as ONE opaque base64 string, exactly as returned
|
|
818
909
|
// by the hosted tools. Preferred over typed_data when both are present —
|
|
819
910
|
// an agent re-emitting multi-KB nested JSON between tool calls is the
|
|
@@ -822,35 +913,36 @@ var toolSchemas = {
|
|
|
822
913
|
// Bounded: a realistic redemption payload is ~10KB encoded; 256KB is
|
|
823
914
|
// generous headroom while keeping the offline signer from materializing
|
|
824
915
|
// arbitrarily large caller input.
|
|
825
|
-
typed_data_b64: z.string().min(1).max(262144).optional()
|
|
916
|
+
typed_data_b64: z$1.string().min(1).max(262144).optional()
|
|
826
917
|
},
|
|
827
918
|
haven_x402_sign_header: {
|
|
828
919
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
829
920
|
// (not z.unknown(), which becomes empty JSON Schema `{}`) so MCP clients
|
|
830
921
|
// embed it as JSON rather than serialising the object to a string.
|
|
831
|
-
payment_required: z.record(z.string(), z.unknown()),
|
|
922
|
+
payment_required: z$1.record(z$1.string(), z$1.unknown()),
|
|
832
923
|
// Opaque binding returned by haven_sign when x402_expected was supplied.
|
|
833
|
-
x402_binding: z.string().min(1)
|
|
924
|
+
x402_binding: z$1.string().min(1)
|
|
834
925
|
},
|
|
835
926
|
haven_sign_x402: {
|
|
836
927
|
// #1263: preferred — pass payment_id (plus payment_required) and the
|
|
837
928
|
// signer fetches the exact signing payload + expected context itself.
|
|
838
|
-
payment_id: z.string().min(1).optional(),
|
|
929
|
+
payment_id: z$1.string().min(1).optional(),
|
|
839
930
|
// One-shot x402 signing: funding hash + merchant header in one local call.
|
|
840
931
|
// Both optional when payment_id is supplied (the fetch carries them).
|
|
841
|
-
payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
|
|
932
|
+
payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
|
|
842
933
|
x402_expected: x402ExpectedSchema.optional(),
|
|
843
934
|
// #1355: optional when payment_id is supplied — the signer's context fetch
|
|
844
935
|
// carries the stored 402 PaymentRequired, so `{ payment_id }` alone is the
|
|
845
936
|
// preferred call. Still required (via the runtime check in the handler)
|
|
846
937
|
// for the quote-based fallback path without a payment_id.
|
|
847
|
-
payment_required: z.record(z.string(), z.unknown()).optional(),
|
|
938
|
+
payment_required: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
848
939
|
// #1138: delegation-rail intents sign THIS, not payload_hash. Object-typed
|
|
849
940
|
// (not z.unknown()) so MCP clients embed it as JSON rather than a string.
|
|
850
|
-
typed_data: z.record(z.string(), z.unknown()).optional(),
|
|
941
|
+
typed_data: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
851
942
|
// #1255: see haven_sign.typed_data_b64 — the copy-through-safe form.
|
|
852
|
-
typed_data_b64: z.string().min(1).max(262144).optional()
|
|
943
|
+
typed_data_b64: z$1.string().min(1).max(262144).optional()
|
|
853
944
|
}
|
|
945
|
+
// #3101: keys survive on the type (see the hosted server's contracts.ts).
|
|
854
946
|
};
|
|
855
947
|
var SIGN_DESCRIPTION = [
|
|
856
948
|
"Sign an unsigned Haven payment hash with the local delegate key. The delegate key never leaves",
|
|
@@ -948,7 +1040,7 @@ function toExpectedX402(raw) {
|
|
|
948
1040
|
};
|
|
949
1041
|
}
|
|
950
1042
|
function parseFetchedExpected(fetched) {
|
|
951
|
-
const parsed = z.object(x402ExpectedShape).safeParse(fetched.x402Expected);
|
|
1043
|
+
const parsed = z$1.object(x402ExpectedShape).safeParse(fetched.x402Expected);
|
|
952
1044
|
if (!parsed.success) {
|
|
953
1045
|
throw new HavenSigningError(
|
|
954
1046
|
`The Haven sign-context response carried a malformed x402_expected \u2014 the backend may predate #1263. Pass payload_hash + x402_expected from the quote result instead. Underlying: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`
|
|
@@ -1078,7 +1170,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1078
1170
|
authorization: args.authorization,
|
|
1079
1171
|
expectedAuth: args.expected_auth,
|
|
1080
1172
|
// Cross-check `to` against the Safe in the local credential when present.
|
|
1081
|
-
expectedSafe: options.audit?.
|
|
1173
|
+
expectedSafe: options.audit?.accountAddress
|
|
1082
1174
|
});
|
|
1083
1175
|
await auditSigning(
|
|
1084
1176
|
"haven_sign_sweep_delegate",
|
|
@@ -1100,7 +1192,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1100
1192
|
}
|
|
1101
1193
|
}
|
|
1102
1194
|
function parse(name, input) {
|
|
1103
|
-
return z.object(toolSchemas[name]).parse(input ?? {});
|
|
1195
|
+
return z$1.object(toolSchemas[name]).parse(input ?? {});
|
|
1104
1196
|
}
|
|
1105
1197
|
function coerceX402Expected(input) {
|
|
1106
1198
|
if (!input || typeof input !== "object") return input;
|
|
@@ -1139,7 +1231,7 @@ async function runTool(fn) {
|
|
|
1139
1231
|
}
|
|
1140
1232
|
}
|
|
1141
1233
|
function normalizeError(err) {
|
|
1142
|
-
if (err instanceof z.ZodError) {
|
|
1234
|
+
if (err instanceof z$1.ZodError) {
|
|
1143
1235
|
return {
|
|
1144
1236
|
success: false,
|
|
1145
1237
|
code: "INVALID_INPUT",
|
|
@@ -1155,7 +1247,13 @@ function normalizeError(err) {
|
|
|
1155
1247
|
supported_versions: [...err.supportedVersions],
|
|
1156
1248
|
received_version: err.receivedVersion,
|
|
1157
1249
|
fallback: err.fallback,
|
|
1158
|
-
next_action: AgentPaymentNextAction.StopAndTellUser
|
|
1250
|
+
next_action: AgentPaymentNextAction.StopAndTellUser,
|
|
1251
|
+
// #3103: no tool can fix a version skew from inside the call.
|
|
1252
|
+
...nextStepWireFields(signerRefusalStep({
|
|
1253
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1254
|
+
nextTool: null,
|
|
1255
|
+
nextToolOmittedReason: "update @haven_ai/signer by re-running the connector, then repeat the same call"
|
|
1256
|
+
}))
|
|
1159
1257
|
};
|
|
1160
1258
|
}
|
|
1161
1259
|
if (err instanceof HavenSignContextError) {
|
|
@@ -1167,7 +1265,14 @@ function normalizeError(err) {
|
|
|
1167
1265
|
...err.fallback !== void 0 ? { fallback: err.fallback } : {},
|
|
1168
1266
|
...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
|
|
1169
1267
|
...err.http_status !== void 0 ? { http_status: err.http_status } : {},
|
|
1170
|
-
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
|
|
1268
|
+
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
|
|
1269
|
+
// #3103: the typed step the error decided beside its action.
|
|
1270
|
+
...err.next_tool ? { next_tool: err.next_tool } : {},
|
|
1271
|
+
...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
|
|
1272
|
+
...err.next_tool_name ? { next_tool_name: err.next_tool_name } : {},
|
|
1273
|
+
...err.next_tool_server_role ? { next_tool_server_role: err.next_tool_server_role } : {},
|
|
1274
|
+
...err.next_arguments ? { next_arguments: err.next_arguments } : {},
|
|
1275
|
+
...err.next_tool_omitted_reason ? { next_tool_omitted_reason: err.next_tool_omitted_reason } : {}
|
|
1171
1276
|
};
|
|
1172
1277
|
}
|
|
1173
1278
|
if (err instanceof HavenSigningError) {
|
|
@@ -1186,7 +1291,13 @@ function normalizeError(err) {
|
|
|
1186
1291
|
...err.code === AgentPaymentFailureCode.PaymentWindowExpired ? {
|
|
1187
1292
|
next_action: AgentPaymentNextAction.PaymentWindowExpired,
|
|
1188
1293
|
retry_with_new_quote: true,
|
|
1189
|
-
suggested_tool: "haven_pay_mcp_tool"
|
|
1294
|
+
suggested_tool: "haven_pay_mcp_tool",
|
|
1295
|
+
// #3103: same omission as the hosted window-expired helper.
|
|
1296
|
+
...nextStepWireFields(signerRefusalStep({
|
|
1297
|
+
nextAction: AgentPaymentNextAction.PaymentWindowExpired,
|
|
1298
|
+
nextTool: null,
|
|
1299
|
+
nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow (suggested_tool names the MCP one)"
|
|
1300
|
+
}))
|
|
1190
1301
|
} : {}
|
|
1191
1302
|
};
|
|
1192
1303
|
}
|
|
@@ -1203,7 +1314,7 @@ var SIGNER_CONSENT_SURFACE_VERSION = 2;
|
|
|
1203
1314
|
function computeSignerConsentHash(input) {
|
|
1204
1315
|
const identity = [
|
|
1205
1316
|
input.delegateAddress.toLowerCase(),
|
|
1206
|
-
(input.
|
|
1317
|
+
(input.accountAddress ?? "").toLowerCase(),
|
|
1207
1318
|
input.agentId ?? "",
|
|
1208
1319
|
input.chainId ?? "",
|
|
1209
1320
|
input.network ?? ""
|
|
@@ -1222,7 +1333,7 @@ function renderSignerConsentBlock(input, hash) {
|
|
|
1222
1333
|
"",
|
|
1223
1334
|
`Delegate address: ${input.delegateAddress}`
|
|
1224
1335
|
];
|
|
1225
|
-
lines.push(`Haven wallet: ${input.
|
|
1336
|
+
lines.push(`Haven wallet: ${input.accountAddress ?? "not provided to this signer"}`);
|
|
1226
1337
|
if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
|
|
1227
1338
|
if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
|
|
1228
1339
|
if (input.network) lines.push(`Network: ${input.network}`);
|
|
@@ -1316,7 +1427,7 @@ async function writeAckFile(path, hash) {
|
|
|
1316
1427
|
);
|
|
1317
1428
|
}
|
|
1318
1429
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1319
|
-
var SIGNER_VERSION = "0.
|
|
1430
|
+
var SIGNER_VERSION = "0.4.0-alpha.0";
|
|
1320
1431
|
async function resolveEdgeSigner(options = {}) {
|
|
1321
1432
|
const { signer } = await resolveSignerRuntime(options);
|
|
1322
1433
|
return signer;
|
|
@@ -1355,7 +1466,7 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1355
1466
|
audit: {
|
|
1356
1467
|
auditPath: options.auditPath ?? defaultSigningAuditPath(credentialsPath),
|
|
1357
1468
|
delegateAddress: signer.delegateAddress,
|
|
1358
|
-
|
|
1469
|
+
accountAddress: options.credentials?.accountAddress,
|
|
1359
1470
|
chainId: options.credentials?.chainId
|
|
1360
1471
|
},
|
|
1361
1472
|
// #1263: the payment_id signing path — the ONLY network call this server
|
|
@@ -1405,7 +1516,7 @@ async function runSignerConsentGate(signer, credentials, options) {
|
|
|
1405
1516
|
return ensureSignerConsent(
|
|
1406
1517
|
{
|
|
1407
1518
|
delegateAddress: signer.delegateAddress,
|
|
1408
|
-
|
|
1519
|
+
accountAddress: credentials?.accountAddress,
|
|
1409
1520
|
agentId: credentials?.agentId,
|
|
1410
1521
|
chainId: credentials?.chainId,
|
|
1411
1522
|
network: credentials?.network,
|
|
@@ -1426,6 +1537,6 @@ function toMcpResult(payload) {
|
|
|
1426
1537
|
};
|
|
1427
1538
|
}
|
|
1428
1539
|
|
|
1429
|
-
export { SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, 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, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive };
|
|
1540
|
+
export { 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, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive };
|
|
1430
1541
|
//# sourceMappingURL=index.js.map
|
|
1431
1542
|
//# sourceMappingURL=index.js.map
|