@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/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ function createSigningAuditEntry(tool, payloadHash, context, now = /* @__PURE__
|
|
|
28
28
|
payload_hash: payloadHash,
|
|
29
29
|
delegate_address: context.delegateAddress
|
|
30
30
|
};
|
|
31
|
-
if (context.
|
|
31
|
+
if (context.accountAddress) entry.safe_address = context.accountAddress;
|
|
32
32
|
if (typeof context.chainId === "number") entry.chain_id = context.chainId;
|
|
33
33
|
return entry;
|
|
34
34
|
}
|
|
@@ -532,6 +532,49 @@ function signerInstructions() {
|
|
|
532
532
|
"fallback fields alongside the message, so you can branch on it directly."
|
|
533
533
|
].join("\n");
|
|
534
534
|
}
|
|
535
|
+
var HavenSignContextError = class extends HavenSigningError {
|
|
536
|
+
/**
|
|
537
|
+
* #3001 / #3010 review: the recovery path, when signing OTHER bytes is
|
|
538
|
+
* actually a remedy — a transport failure (timeout, unreachable) or a
|
|
539
|
+
* body this signer could not read. `typed_data_b64` is NOT in the default
|
|
540
|
+
* quote result since #1272: obtain it by re-running the SAME quote tool
|
|
541
|
+
* with the SAME idempotency_key plus `include_signing_payload: true`.
|
|
542
|
+
* Absent on a backend REFUSAL: an expired, executed or unsignable intent
|
|
543
|
+
* cannot be rescued by re-signing its bytes.
|
|
544
|
+
*/
|
|
545
|
+
fallback;
|
|
546
|
+
/**
|
|
547
|
+
* #3001: `AgentPaymentNextAction` values the signer already emits — the
|
|
548
|
+
* version-mismatch refusal's `stop_and_tell_user` for the classes where
|
|
549
|
+
* retrying the same call cannot help, and `payment_window_expired` (with
|
|
550
|
+
* `retry_with_new_quote`) for the backend's 410 `expired`, exactly as the
|
|
551
|
+
* plain `HavenError` branch already does for that code.
|
|
552
|
+
*/
|
|
553
|
+
next_action;
|
|
554
|
+
retry_with_new_quote;
|
|
555
|
+
/** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
|
|
556
|
+
http_status;
|
|
557
|
+
/** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
|
|
558
|
+
backend_error_code;
|
|
559
|
+
constructor(message, code, refusal) {
|
|
560
|
+
super(message);
|
|
561
|
+
this.code = code;
|
|
562
|
+
this.name = "HavenSignContextError";
|
|
563
|
+
if (code === "SIGN_CONTEXT_REFUSED") {
|
|
564
|
+
this.http_status = refusal?.httpStatus;
|
|
565
|
+
this.backend_error_code = refusal?.errorCode;
|
|
566
|
+
if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
|
|
567
|
+
this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
|
|
568
|
+
this.retry_with_new_quote = true;
|
|
569
|
+
} else {
|
|
570
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
this.fallback = "typed_data_b64";
|
|
574
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
};
|
|
535
578
|
async function loadHavenIdentity(credentialsPath) {
|
|
536
579
|
if (!credentialsPath) return null;
|
|
537
580
|
try {
|
|
@@ -546,30 +589,50 @@ async function loadHavenIdentity(credentialsPath) {
|
|
|
546
589
|
return null;
|
|
547
590
|
}
|
|
548
591
|
}
|
|
549
|
-
|
|
592
|
+
var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
|
|
593
|
+
async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
|
|
550
594
|
let response;
|
|
551
595
|
try {
|
|
552
596
|
response = await fetchImpl(
|
|
553
597
|
`${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
|
|
554
|
-
{
|
|
598
|
+
{
|
|
599
|
+
headers: { Authorization: `Bearer ${identity.apiKey}` },
|
|
600
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
601
|
+
}
|
|
555
602
|
);
|
|
556
603
|
} catch (err) {
|
|
557
|
-
|
|
558
|
-
|
|
604
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
605
|
+
throw new HavenSignContextError(
|
|
606
|
+
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.`,
|
|
607
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
|
|
559
608
|
);
|
|
560
609
|
}
|
|
561
|
-
|
|
610
|
+
let body;
|
|
611
|
+
try {
|
|
612
|
+
body = await response.json();
|
|
613
|
+
} catch (err) {
|
|
614
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
615
|
+
throw new HavenSignContextError(
|
|
616
|
+
`Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
|
|
617
|
+
"SIGN_CONTEXT_TIMEOUT"
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
body = {};
|
|
621
|
+
}
|
|
562
622
|
if (!response.ok) {
|
|
563
623
|
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
564
|
-
throw new
|
|
565
|
-
`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." : "")
|
|
624
|
+
throw new HavenSignContextError(
|
|
625
|
+
`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." : ""),
|
|
626
|
+
"SIGN_CONTEXT_REFUSED",
|
|
627
|
+
{ httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
|
|
566
628
|
);
|
|
567
629
|
}
|
|
568
630
|
const signData = body.sign_data;
|
|
569
631
|
const x402Expected = body.x402_expected;
|
|
570
632
|
if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
|
|
571
|
-
throw new
|
|
572
|
-
"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."
|
|
633
|
+
throw new HavenSignContextError(
|
|
634
|
+
"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.",
|
|
635
|
+
"SIGN_CONTEXT_MALFORMED"
|
|
573
636
|
);
|
|
574
637
|
}
|
|
575
638
|
const paymentRequired = body.payment_required;
|
|
@@ -920,7 +983,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
920
983
|
authorization: args.authorization,
|
|
921
984
|
expectedAuth: args.expected_auth,
|
|
922
985
|
// Cross-check `to` against the Safe in the local credential when present.
|
|
923
|
-
expectedSafe: options.audit?.
|
|
986
|
+
expectedSafe: options.audit?.accountAddress
|
|
924
987
|
});
|
|
925
988
|
await auditSigning(
|
|
926
989
|
"haven_sign_sweep_delegate",
|
|
@@ -1000,6 +1063,18 @@ function normalizeError(err) {
|
|
|
1000
1063
|
next_action: AgentPaymentNextAction.StopAndTellUser
|
|
1001
1064
|
};
|
|
1002
1065
|
}
|
|
1066
|
+
if (err instanceof HavenSignContextError) {
|
|
1067
|
+
return {
|
|
1068
|
+
success: false,
|
|
1069
|
+
code: err.code,
|
|
1070
|
+
message: err.message,
|
|
1071
|
+
next_action: err.next_action,
|
|
1072
|
+
...err.fallback !== void 0 ? { fallback: err.fallback } : {},
|
|
1073
|
+
...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
|
|
1074
|
+
...err.http_status !== void 0 ? { http_status: err.http_status } : {},
|
|
1075
|
+
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1003
1078
|
if (err instanceof HavenSigningError) {
|
|
1004
1079
|
return { success: false, code: err.code, message: err.message };
|
|
1005
1080
|
}
|
|
@@ -1033,7 +1108,7 @@ var SIGNER_CONSENT_SURFACE_VERSION = 2;
|
|
|
1033
1108
|
function computeSignerConsentHash(input) {
|
|
1034
1109
|
const identity = [
|
|
1035
1110
|
input.delegateAddress.toLowerCase(),
|
|
1036
|
-
(input.
|
|
1111
|
+
(input.accountAddress ?? "").toLowerCase(),
|
|
1037
1112
|
input.agentId ?? "",
|
|
1038
1113
|
input.chainId ?? "",
|
|
1039
1114
|
input.network ?? ""
|
|
@@ -1052,7 +1127,7 @@ function renderSignerConsentBlock(input, hash) {
|
|
|
1052
1127
|
"",
|
|
1053
1128
|
`Delegate address: ${input.delegateAddress}`
|
|
1054
1129
|
];
|
|
1055
|
-
lines.push(`Haven wallet: ${input.
|
|
1130
|
+
lines.push(`Haven wallet: ${input.accountAddress ?? "not provided to this signer"}`);
|
|
1056
1131
|
if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
|
|
1057
1132
|
if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
|
|
1058
1133
|
if (input.network) lines.push(`Network: ${input.network}`);
|
|
@@ -1154,7 +1229,6 @@ async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
|
1154
1229
|
delegateKey: envKey,
|
|
1155
1230
|
agentId: stringField(process.env.HAVEN_AGENT_ID),
|
|
1156
1231
|
accountAddress,
|
|
1157
|
-
safeAddress: accountAddress,
|
|
1158
1232
|
chainId: chainIdField(process.env.HAVEN_CHAIN_ID, "HAVEN_CHAIN_ID"),
|
|
1159
1233
|
network: stringField(process.env.HAVEN_NETWORK),
|
|
1160
1234
|
x402BindingSigner: stringField(process.env.HAVEN_X402_BINDING_SIGNER)
|
|
@@ -1189,7 +1263,6 @@ async function loadFromFile(path) {
|
|
|
1189
1263
|
delegateKey,
|
|
1190
1264
|
agentId: stringField(raw.agent_id ?? raw.agentId),
|
|
1191
1265
|
accountAddress,
|
|
1192
|
-
safeAddress: accountAddress,
|
|
1193
1266
|
chainId: chainIdField(raw.chain_id ?? raw.chainId, "chain_id"),
|
|
1194
1267
|
network: stringField(raw.network),
|
|
1195
1268
|
x402BindingSigner: stringField(
|
|
@@ -1202,7 +1275,22 @@ function readAccountAddressField(raw) {
|
|
|
1202
1275
|
return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
|
|
1203
1276
|
}
|
|
1204
1277
|
function readAccountAddressEnv(env) {
|
|
1205
|
-
|
|
1278
|
+
const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
|
|
1279
|
+
for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
|
|
1280
|
+
const retired = stringField(env[name]);
|
|
1281
|
+
if (!retired) continue;
|
|
1282
|
+
if (!current) {
|
|
1283
|
+
throw new Error(
|
|
1284
|
+
`${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.`
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
if (retired.toLowerCase() !== current.toLowerCase()) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`${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.`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return current;
|
|
1206
1294
|
}
|
|
1207
1295
|
function stringField(value) {
|
|
1208
1296
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -1245,7 +1333,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1245
1333
|
|
|
1246
1334
|
// src/server.ts
|
|
1247
1335
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1248
|
-
var SIGNER_VERSION = "0.
|
|
1336
|
+
var SIGNER_VERSION = "0.3.0-alpha.0";
|
|
1249
1337
|
async function resolveSignerRuntime(options = {}) {
|
|
1250
1338
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1251
1339
|
if (options.delegateKey) {
|
|
@@ -1280,7 +1368,7 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1280
1368
|
audit: {
|
|
1281
1369
|
auditPath: options.auditPath ?? defaultSigningAuditPath(credentialsPath),
|
|
1282
1370
|
delegateAddress: signer.delegateAddress,
|
|
1283
|
-
|
|
1371
|
+
accountAddress: options.credentials?.accountAddress,
|
|
1284
1372
|
chainId: options.credentials?.chainId
|
|
1285
1373
|
},
|
|
1286
1374
|
// #1263: the payment_id signing path — the ONLY network call this server
|
|
@@ -1330,7 +1418,7 @@ async function runSignerConsentGate(signer, credentials, options) {
|
|
|
1330
1418
|
return ensureSignerConsent(
|
|
1331
1419
|
{
|
|
1332
1420
|
delegateAddress: signer.delegateAddress,
|
|
1333
|
-
|
|
1421
|
+
accountAddress: credentials?.accountAddress,
|
|
1334
1422
|
agentId: credentials?.agentId,
|
|
1335
1423
|
chainId: credentials?.chainId,
|
|
1336
1424
|
network: credentials?.network,
|