@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/index.cjs CHANGED
@@ -503,10 +503,12 @@ async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
503
503
  if (path) return loadFromFile(path);
504
504
  const envKey = stringField(process.env.HAVEN_DELEGATE_KEY);
505
505
  if (envKey) {
506
+ const accountAddress = readAccountAddressEnv(process.env);
506
507
  return {
507
508
  delegateKey: envKey,
508
509
  agentId: stringField(process.env.HAVEN_AGENT_ID),
509
- safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),
510
+ accountAddress,
511
+ safeAddress: accountAddress,
510
512
  chainId: chainIdField(process.env.HAVEN_CHAIN_ID, "HAVEN_CHAIN_ID"),
511
513
  network: stringField(process.env.HAVEN_NETWORK),
512
514
  x402BindingSigner: stringField(process.env.HAVEN_X402_BINDING_SIGNER)
@@ -536,10 +538,12 @@ async function loadFromFile(path) {
536
538
  if (!delegateKey) {
537
539
  throw new Error("Haven credentials are missing delegate_key \u2014 the edge signer needs it to sign.");
538
540
  }
541
+ const accountAddress = readAccountAddressField(raw);
539
542
  return {
540
543
  delegateKey,
541
544
  agentId: stringField(raw.agent_id ?? raw.agentId),
542
- safeAddress: stringField(raw.safe_address ?? raw.safeAddress),
545
+ accountAddress,
546
+ safeAddress: accountAddress,
543
547
  chainId: chainIdField(raw.chain_id ?? raw.chainId, "chain_id"),
544
548
  network: stringField(raw.network),
545
549
  x402BindingSigner: stringField(
@@ -548,6 +552,12 @@ async function loadFromFile(path) {
548
552
  sourcePath: path
549
553
  };
550
554
  }
555
+ function readAccountAddressField(raw) {
556
+ return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
557
+ }
558
+ function readAccountAddressEnv(env) {
559
+ return stringField(env.HAVEN_ACCOUNT_ADDRESS ?? env.HAVEN_WALLET_ADDRESS ?? env.HAVEN_SAFE_ADDRESS);
560
+ }
551
561
  function stringField(value) {
552
562
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
553
563
  }
@@ -619,6 +629,49 @@ function stableStringify(value) {
619
629
  const object = value;
620
630
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
621
631
  }
632
+ var HavenSignContextError = class extends sdk.HavenSigningError {
633
+ /**
634
+ * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
635
+ * actually a remedy — a transport failure (timeout, unreachable) or a
636
+ * body this signer could not read. `typed_data_b64` is NOT in the default
637
+ * quote result since #1272: obtain it by re-running the SAME quote tool
638
+ * with the SAME idempotency_key plus `include_signing_payload: true`.
639
+ * Absent on a backend REFUSAL: an expired, executed or unsignable intent
640
+ * cannot be rescued by re-signing its bytes.
641
+ */
642
+ fallback;
643
+ /**
644
+ * #3001: `AgentPaymentNextAction` values the signer already emits — the
645
+ * version-mismatch refusal's `stop_and_tell_user` for the classes where
646
+ * retrying the same call cannot help, and `payment_window_expired` (with
647
+ * `retry_with_new_quote`) for the backend's 410 `expired`, exactly as the
648
+ * plain `HavenError` branch already does for that code.
649
+ */
650
+ next_action;
651
+ retry_with_new_quote;
652
+ /** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
653
+ http_status;
654
+ /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
655
+ backend_error_code;
656
+ constructor(message, code, refusal) {
657
+ super(message);
658
+ this.code = code;
659
+ this.name = "HavenSignContextError";
660
+ if (code === "SIGN_CONTEXT_REFUSED") {
661
+ this.http_status = refusal?.httpStatus;
662
+ this.backend_error_code = refusal?.errorCode;
663
+ if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
664
+ this.next_action = sdk.AgentPaymentNextAction.PaymentWindowExpired;
665
+ this.retry_with_new_quote = true;
666
+ } else {
667
+ this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
668
+ }
669
+ } else {
670
+ this.fallback = "typed_data_b64";
671
+ this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
672
+ }
673
+ }
674
+ };
622
675
  async function loadHavenIdentity(credentialsPath) {
623
676
  if (!credentialsPath) return null;
624
677
  try {
@@ -633,30 +686,50 @@ async function loadHavenIdentity(credentialsPath) {
633
686
  return null;
634
687
  }
635
688
  }
636
- async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch) {
689
+ var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
690
+ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
637
691
  let response;
638
692
  try {
639
693
  response = await fetchImpl(
640
694
  `${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
641
- { headers: { Authorization: `Bearer ${identity.apiKey}` } }
695
+ {
696
+ headers: { Authorization: `Bearer ${identity.apiKey}` },
697
+ signal: AbortSignal.timeout(timeoutMs)
698
+ }
642
699
  );
643
700
  } catch (err) {
644
- throw new sdk.HavenSigningError(
645
- `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.`
701
+ const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
702
+ throw new HavenSignContextError(
703
+ 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.`,
704
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
646
705
  );
647
706
  }
648
- const body = await response.json().catch(() => ({}));
707
+ let body;
708
+ try {
709
+ body = await response.json();
710
+ } catch (err) {
711
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
712
+ throw new HavenSignContextError(
713
+ `Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
714
+ "SIGN_CONTEXT_TIMEOUT"
715
+ );
716
+ }
717
+ body = {};
718
+ }
649
719
  if (!response.ok) {
650
720
  const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
651
- throw new sdk.HavenSigningError(
652
- `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
+ throw new HavenSignContextError(
722
+ `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." : ""),
723
+ "SIGN_CONTEXT_REFUSED",
724
+ { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
653
725
  );
654
726
  }
655
727
  const signData = body.sign_data;
656
728
  const x402Expected = body.x402_expected;
657
729
  if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
658
- throw new sdk.HavenSigningError(
659
- "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
+ throw new HavenSignContextError(
731
+ "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.",
732
+ "SIGN_CONTEXT_MALFORMED"
660
733
  );
661
734
  }
662
735
  const paymentRequired = body.payment_required;
@@ -1087,6 +1160,18 @@ function normalizeError(err) {
1087
1160
  next_action: sdk.AgentPaymentNextAction.StopAndTellUser
1088
1161
  };
1089
1162
  }
1163
+ if (err instanceof HavenSignContextError) {
1164
+ return {
1165
+ success: false,
1166
+ code: err.code,
1167
+ message: err.message,
1168
+ next_action: err.next_action,
1169
+ ...err.fallback !== void 0 ? { fallback: err.fallback } : {},
1170
+ ...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
1171
+ ...err.http_status !== void 0 ? { http_status: err.http_status } : {},
1172
+ ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
1173
+ };
1174
+ }
1090
1175
  if (err instanceof sdk.HavenSigningError) {
1091
1176
  return { success: false, code: err.code, message: err.message };
1092
1177
  }
@@ -1233,7 +1318,7 @@ async function writeAckFile(path$1, hash) {
1233
1318
  );
1234
1319
  }
1235
1320
  var SIGNER_NAME = "@haven_ai/signer";
1236
- var SIGNER_VERSION = "0.1.37-alpha.0";
1321
+ var SIGNER_VERSION = "0.2.1-alpha.0";
1237
1322
  async function resolveEdgeSigner(options = {}) {
1238
1323
  const { signer } = await resolveSignerRuntime(options);
1239
1324
  return signer;
@@ -1272,7 +1357,7 @@ function buildSignerMcpServer(signer, options = {}) {
1272
1357
  audit: {
1273
1358
  auditPath: options.auditPath ?? defaultSigningAuditPath(credentialsPath),
1274
1359
  delegateAddress: signer.delegateAddress,
1275
- safeAddress: options.credentials?.safeAddress,
1360
+ safeAddress: options.credentials?.accountAddress ?? options.credentials?.safeAddress,
1276
1361
  chainId: options.credentials?.chainId
1277
1362
  },
1278
1363
  // #1263: the payment_id signing path — the ONLY network call this server
@@ -1322,7 +1407,7 @@ async function runSignerConsentGate(signer, credentials, options) {
1322
1407
  return ensureSignerConsent(
1323
1408
  {
1324
1409
  delegateAddress: signer.delegateAddress,
1325
- safeAddress: credentials?.safeAddress,
1410
+ safeAddress: credentials?.accountAddress ?? credentials?.safeAddress,
1326
1411
  agentId: credentials?.agentId,
1327
1412
  chainId: credentials?.chainId,
1328
1413
  network: credentials?.network,
@@ -1362,6 +1447,8 @@ exports.defaultSigningAuditPath = defaultSigningAuditPath;
1362
1447
  exports.ensureSignerConsent = ensureSignerConsent;
1363
1448
  exports.hashPayloadForAudit = hashPayloadForAudit;
1364
1449
  exports.loadSignerCredentials = loadSignerCredentials;
1450
+ exports.readAccountAddressEnv = readAccountAddressEnv;
1451
+ exports.readAccountAddressField = readAccountAddressField;
1365
1452
  exports.renderSignerConsentBlock = renderSignerConsentBlock;
1366
1453
  exports.resolveEdgeSigner = resolveEdgeSigner;
1367
1454
  exports.resolveSignerRuntime = resolveSignerRuntime;