@haven_ai/signer 0.2.0-alpha.0 → 0.3.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 +45 -3
- package/dist/cli.cjs +107 -19
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +107 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +107 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -23
- package/dist/index.d.ts +29 -23
- package/dist/index.js +107 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -508,7 +508,6 @@ async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
|
508
508
|
delegateKey: envKey,
|
|
509
509
|
agentId: stringField(process.env.HAVEN_AGENT_ID),
|
|
510
510
|
accountAddress,
|
|
511
|
-
safeAddress: accountAddress,
|
|
512
511
|
chainId: chainIdField(process.env.HAVEN_CHAIN_ID, "HAVEN_CHAIN_ID"),
|
|
513
512
|
network: stringField(process.env.HAVEN_NETWORK),
|
|
514
513
|
x402BindingSigner: stringField(process.env.HAVEN_X402_BINDING_SIGNER)
|
|
@@ -543,7 +542,6 @@ async function loadFromFile(path) {
|
|
|
543
542
|
delegateKey,
|
|
544
543
|
agentId: stringField(raw.agent_id ?? raw.agentId),
|
|
545
544
|
accountAddress,
|
|
546
|
-
safeAddress: accountAddress,
|
|
547
545
|
chainId: chainIdField(raw.chain_id ?? raw.chainId, "chain_id"),
|
|
548
546
|
network: stringField(raw.network),
|
|
549
547
|
x402BindingSigner: stringField(
|
|
@@ -556,7 +554,22 @@ function readAccountAddressField(raw) {
|
|
|
556
554
|
return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
|
|
557
555
|
}
|
|
558
556
|
function readAccountAddressEnv(env) {
|
|
559
|
-
|
|
557
|
+
const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
|
|
558
|
+
for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
|
|
559
|
+
const retired = stringField(env[name]);
|
|
560
|
+
if (!retired) continue;
|
|
561
|
+
if (!current) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
`${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.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (retired.toLowerCase() !== current.toLowerCase()) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
`${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.`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return current;
|
|
560
573
|
}
|
|
561
574
|
function stringField(value) {
|
|
562
575
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -613,7 +626,7 @@ function createSigningAuditEntry(tool, payloadHash, context, now = /* @__PURE__
|
|
|
613
626
|
payload_hash: payloadHash,
|
|
614
627
|
delegate_address: context.delegateAddress
|
|
615
628
|
};
|
|
616
|
-
if (context.
|
|
629
|
+
if (context.accountAddress) entry.safe_address = context.accountAddress;
|
|
617
630
|
if (typeof context.chainId === "number") entry.chain_id = context.chainId;
|
|
618
631
|
return entry;
|
|
619
632
|
}
|
|
@@ -629,6 +642,49 @@ function stableStringify(value) {
|
|
|
629
642
|
const object = value;
|
|
630
643
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
|
|
631
644
|
}
|
|
645
|
+
var HavenSignContextError = class extends sdk.HavenSigningError {
|
|
646
|
+
/**
|
|
647
|
+
* #3001 / #3010 review: the recovery path, when signing OTHER bytes is
|
|
648
|
+
* actually a remedy — a transport failure (timeout, unreachable) or a
|
|
649
|
+
* body this signer could not read. `typed_data_b64` is NOT in the default
|
|
650
|
+
* quote result since #1272: obtain it by re-running the SAME quote tool
|
|
651
|
+
* with the SAME idempotency_key plus `include_signing_payload: true`.
|
|
652
|
+
* Absent on a backend REFUSAL: an expired, executed or unsignable intent
|
|
653
|
+
* cannot be rescued by re-signing its bytes.
|
|
654
|
+
*/
|
|
655
|
+
fallback;
|
|
656
|
+
/**
|
|
657
|
+
* #3001: `AgentPaymentNextAction` values the signer already emits — the
|
|
658
|
+
* version-mismatch refusal's `stop_and_tell_user` for the classes where
|
|
659
|
+
* retrying the same call cannot help, and `payment_window_expired` (with
|
|
660
|
+
* `retry_with_new_quote`) for the backend's 410 `expired`, exactly as the
|
|
661
|
+
* plain `HavenError` branch already does for that code.
|
|
662
|
+
*/
|
|
663
|
+
next_action;
|
|
664
|
+
retry_with_new_quote;
|
|
665
|
+
/** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
|
|
666
|
+
http_status;
|
|
667
|
+
/** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
|
|
668
|
+
backend_error_code;
|
|
669
|
+
constructor(message, code, refusal) {
|
|
670
|
+
super(message);
|
|
671
|
+
this.code = code;
|
|
672
|
+
this.name = "HavenSignContextError";
|
|
673
|
+
if (code === "SIGN_CONTEXT_REFUSED") {
|
|
674
|
+
this.http_status = refusal?.httpStatus;
|
|
675
|
+
this.backend_error_code = refusal?.errorCode;
|
|
676
|
+
if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
|
|
677
|
+
this.next_action = sdk.AgentPaymentNextAction.PaymentWindowExpired;
|
|
678
|
+
this.retry_with_new_quote = true;
|
|
679
|
+
} else {
|
|
680
|
+
this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
this.fallback = "typed_data_b64";
|
|
684
|
+
this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
632
688
|
async function loadHavenIdentity(credentialsPath) {
|
|
633
689
|
if (!credentialsPath) return null;
|
|
634
690
|
try {
|
|
@@ -643,30 +699,50 @@ async function loadHavenIdentity(credentialsPath) {
|
|
|
643
699
|
return null;
|
|
644
700
|
}
|
|
645
701
|
}
|
|
646
|
-
|
|
702
|
+
var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
|
|
703
|
+
async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
|
|
647
704
|
let response;
|
|
648
705
|
try {
|
|
649
706
|
response = await fetchImpl(
|
|
650
707
|
`${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
|
|
651
|
-
{
|
|
708
|
+
{
|
|
709
|
+
headers: { Authorization: `Bearer ${identity.apiKey}` },
|
|
710
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
711
|
+
}
|
|
652
712
|
);
|
|
653
713
|
} catch (err) {
|
|
654
|
-
|
|
655
|
-
|
|
714
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
715
|
+
throw new HavenSignContextError(
|
|
716
|
+
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.`,
|
|
717
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
|
|
656
718
|
);
|
|
657
719
|
}
|
|
658
|
-
|
|
720
|
+
let body;
|
|
721
|
+
try {
|
|
722
|
+
body = await response.json();
|
|
723
|
+
} catch (err) {
|
|
724
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
725
|
+
throw new HavenSignContextError(
|
|
726
|
+
`Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
|
|
727
|
+
"SIGN_CONTEXT_TIMEOUT"
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
body = {};
|
|
731
|
+
}
|
|
659
732
|
if (!response.ok) {
|
|
660
733
|
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
661
|
-
throw new
|
|
662
|
-
`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." : "")
|
|
734
|
+
throw new HavenSignContextError(
|
|
735
|
+
`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." : ""),
|
|
736
|
+
"SIGN_CONTEXT_REFUSED",
|
|
737
|
+
{ httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
|
|
663
738
|
);
|
|
664
739
|
}
|
|
665
740
|
const signData = body.sign_data;
|
|
666
741
|
const x402Expected = body.x402_expected;
|
|
667
742
|
if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
|
|
668
|
-
throw new
|
|
669
|
-
"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."
|
|
743
|
+
throw new HavenSignContextError(
|
|
744
|
+
"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.",
|
|
745
|
+
"SIGN_CONTEXT_MALFORMED"
|
|
670
746
|
);
|
|
671
747
|
}
|
|
672
748
|
const paymentRequired = body.payment_required;
|
|
@@ -1017,7 +1093,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1017
1093
|
authorization: args.authorization,
|
|
1018
1094
|
expectedAuth: args.expected_auth,
|
|
1019
1095
|
// Cross-check `to` against the Safe in the local credential when present.
|
|
1020
|
-
expectedSafe: options.audit?.
|
|
1096
|
+
expectedSafe: options.audit?.accountAddress
|
|
1021
1097
|
});
|
|
1022
1098
|
await auditSigning(
|
|
1023
1099
|
"haven_sign_sweep_delegate",
|
|
@@ -1097,6 +1173,18 @@ function normalizeError(err) {
|
|
|
1097
1173
|
next_action: sdk.AgentPaymentNextAction.StopAndTellUser
|
|
1098
1174
|
};
|
|
1099
1175
|
}
|
|
1176
|
+
if (err instanceof HavenSignContextError) {
|
|
1177
|
+
return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
code: err.code,
|
|
1180
|
+
message: err.message,
|
|
1181
|
+
next_action: err.next_action,
|
|
1182
|
+
...err.fallback !== void 0 ? { fallback: err.fallback } : {},
|
|
1183
|
+
...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
|
|
1184
|
+
...err.http_status !== void 0 ? { http_status: err.http_status } : {},
|
|
1185
|
+
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1100
1188
|
if (err instanceof sdk.HavenSigningError) {
|
|
1101
1189
|
return { success: false, code: err.code, message: err.message };
|
|
1102
1190
|
}
|
|
@@ -1130,7 +1218,7 @@ var SIGNER_CONSENT_SURFACE_VERSION = 2;
|
|
|
1130
1218
|
function computeSignerConsentHash(input) {
|
|
1131
1219
|
const identity = [
|
|
1132
1220
|
input.delegateAddress.toLowerCase(),
|
|
1133
|
-
(input.
|
|
1221
|
+
(input.accountAddress ?? "").toLowerCase(),
|
|
1134
1222
|
input.agentId ?? "",
|
|
1135
1223
|
input.chainId ?? "",
|
|
1136
1224
|
input.network ?? ""
|
|
@@ -1149,7 +1237,7 @@ function renderSignerConsentBlock(input, hash) {
|
|
|
1149
1237
|
"",
|
|
1150
1238
|
`Delegate address: ${input.delegateAddress}`
|
|
1151
1239
|
];
|
|
1152
|
-
lines.push(`Haven wallet: ${input.
|
|
1240
|
+
lines.push(`Haven wallet: ${input.accountAddress ?? "not provided to this signer"}`);
|
|
1153
1241
|
if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
|
|
1154
1242
|
if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
|
|
1155
1243
|
if (input.network) lines.push(`Network: ${input.network}`);
|
|
@@ -1243,7 +1331,7 @@ async function writeAckFile(path$1, hash) {
|
|
|
1243
1331
|
);
|
|
1244
1332
|
}
|
|
1245
1333
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1246
|
-
var SIGNER_VERSION = "0.
|
|
1334
|
+
var SIGNER_VERSION = "0.3.0-alpha.0";
|
|
1247
1335
|
async function resolveEdgeSigner(options = {}) {
|
|
1248
1336
|
const { signer } = await resolveSignerRuntime(options);
|
|
1249
1337
|
return signer;
|
|
@@ -1282,7 +1370,7 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1282
1370
|
audit: {
|
|
1283
1371
|
auditPath: options.auditPath ?? defaultSigningAuditPath(credentialsPath),
|
|
1284
1372
|
delegateAddress: signer.delegateAddress,
|
|
1285
|
-
|
|
1373
|
+
accountAddress: options.credentials?.accountAddress,
|
|
1286
1374
|
chainId: options.credentials?.chainId
|
|
1287
1375
|
},
|
|
1288
1376
|
// #1263: the payment_id signing path — the ONLY network call this server
|
|
@@ -1332,7 +1420,7 @@ async function runSignerConsentGate(signer, credentials, options) {
|
|
|
1332
1420
|
return ensureSignerConsent(
|
|
1333
1421
|
{
|
|
1334
1422
|
delegateAddress: signer.delegateAddress,
|
|
1335
|
-
|
|
1423
|
+
accountAddress: credentials?.accountAddress,
|
|
1336
1424
|
agentId: credentials?.agentId,
|
|
1337
1425
|
chainId: credentials?.chainId,
|
|
1338
1426
|
network: credentials?.network,
|