@haven_ai/signer 0.1.37-alpha.0 → 0.2.1-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/dist/cli.js CHANGED
@@ -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
- async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch) {
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
- { headers: { Authorization: `Bearer ${identity.apiKey}` } }
598
+ {
599
+ headers: { Authorization: `Bearer ${identity.apiKey}` },
600
+ signal: AbortSignal.timeout(timeoutMs)
601
+ }
555
602
  );
556
603
  } catch (err) {
557
- throw new HavenSigningError(
558
- `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.`
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
- const body = await response.json().catch(() => ({}));
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 HavenSigningError(
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 HavenSigningError(
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;
@@ -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
  }
@@ -1149,10 +1224,12 @@ async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
1149
1224
  if (path) return loadFromFile(path);
1150
1225
  const envKey = stringField(process.env.HAVEN_DELEGATE_KEY);
1151
1226
  if (envKey) {
1227
+ const accountAddress = readAccountAddressEnv(process.env);
1152
1228
  return {
1153
1229
  delegateKey: envKey,
1154
1230
  agentId: stringField(process.env.HAVEN_AGENT_ID),
1155
- safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),
1231
+ accountAddress,
1232
+ safeAddress: accountAddress,
1156
1233
  chainId: chainIdField(process.env.HAVEN_CHAIN_ID, "HAVEN_CHAIN_ID"),
1157
1234
  network: stringField(process.env.HAVEN_NETWORK),
1158
1235
  x402BindingSigner: stringField(process.env.HAVEN_X402_BINDING_SIGNER)
@@ -1182,10 +1259,12 @@ async function loadFromFile(path) {
1182
1259
  if (!delegateKey) {
1183
1260
  throw new Error("Haven credentials are missing delegate_key \u2014 the edge signer needs it to sign.");
1184
1261
  }
1262
+ const accountAddress = readAccountAddressField(raw);
1185
1263
  return {
1186
1264
  delegateKey,
1187
1265
  agentId: stringField(raw.agent_id ?? raw.agentId),
1188
- safeAddress: stringField(raw.safe_address ?? raw.safeAddress),
1266
+ accountAddress,
1267
+ safeAddress: accountAddress,
1189
1268
  chainId: chainIdField(raw.chain_id ?? raw.chainId, "chain_id"),
1190
1269
  network: stringField(raw.network),
1191
1270
  x402BindingSigner: stringField(
@@ -1194,6 +1273,12 @@ async function loadFromFile(path) {
1194
1273
  sourcePath: path
1195
1274
  };
1196
1275
  }
1276
+ function readAccountAddressField(raw) {
1277
+ return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
1278
+ }
1279
+ function readAccountAddressEnv(env) {
1280
+ return stringField(env.HAVEN_ACCOUNT_ADDRESS ?? env.HAVEN_WALLET_ADDRESS ?? env.HAVEN_SAFE_ADDRESS);
1281
+ }
1197
1282
  function stringField(value) {
1198
1283
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
1199
1284
  }
@@ -1235,7 +1320,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
1235
1320
 
1236
1321
  // src/server.ts
1237
1322
  var SIGNER_NAME = "@haven_ai/signer";
1238
- var SIGNER_VERSION = "0.1.37-alpha.0";
1323
+ var SIGNER_VERSION = "0.2.1-alpha.0";
1239
1324
  async function resolveSignerRuntime(options = {}) {
1240
1325
  assertSupportedNodeVersion(options.nodeVersion);
1241
1326
  if (options.delegateKey) {
@@ -1270,7 +1355,7 @@ function buildSignerMcpServer(signer, options = {}) {
1270
1355
  audit: {
1271
1356
  auditPath: options.auditPath ?? defaultSigningAuditPath(credentialsPath),
1272
1357
  delegateAddress: signer.delegateAddress,
1273
- safeAddress: options.credentials?.safeAddress,
1358
+ safeAddress: options.credentials?.accountAddress ?? options.credentials?.safeAddress,
1274
1359
  chainId: options.credentials?.chainId
1275
1360
  },
1276
1361
  // #1263: the payment_id signing path — the ONLY network call this server
@@ -1320,7 +1405,7 @@ async function runSignerConsentGate(signer, credentials, options) {
1320
1405
  return ensureSignerConsent(
1321
1406
  {
1322
1407
  delegateAddress: signer.delegateAddress,
1323
- safeAddress: credentials?.safeAddress,
1408
+ safeAddress: credentials?.accountAddress ?? credentials?.safeAddress,
1324
1409
  agentId: credentials?.agentId,
1325
1410
  chainId: credentials?.chainId,
1326
1411
  network: credentials?.network,