blockintel-gate-sdk 0.3.9 → 0.3.11

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.
@@ -169,6 +169,18 @@ interface DefenseEvaluateResponseV2 {
169
169
  * Gate mode used for this evaluation
170
170
  */
171
171
  mode?: GateMode;
172
+ /**
173
+ * Gate receipt (when HARD_KMS_GATEWAY (or HARD_KMS_ATTESTED, deprecated) or receipt requested). Required for KMS signing in receipt-required mode.
174
+ */
175
+ receipt?: Record<string, unknown>;
176
+ /**
177
+ * Decision hash from receipt (SHA256 of canonical receipt payload). Used for receipt-required KMS flow.
178
+ */
179
+ decisionHash?: string;
180
+ /**
181
+ * Receipt signature (HS256:base64). Used for receipt-required KMS flow.
182
+ */
183
+ receiptSignature?: string;
172
184
  /**
173
185
  * Metadata (evaluation latency, simulation, policy hash for pinning)
174
186
  */
@@ -169,6 +169,18 @@ interface DefenseEvaluateResponseV2 {
169
169
  * Gate mode used for this evaluation
170
170
  */
171
171
  mode?: GateMode;
172
+ /**
173
+ * Gate receipt (when HARD_KMS_GATEWAY (or HARD_KMS_ATTESTED, deprecated) or receipt requested). Required for KMS signing in receipt-required mode.
174
+ */
175
+ receipt?: Record<string, unknown>;
176
+ /**
177
+ * Decision hash from receipt (SHA256 of canonical receipt payload). Used for receipt-required KMS flow.
178
+ */
179
+ decisionHash?: string;
180
+ /**
181
+ * Receipt signature (HS256:base64). Used for receipt-required KMS flow.
182
+ */
183
+ receiptSignature?: string;
172
184
  /**
173
185
  * Metadata (evaluation latency, simulation, policy hash for pinning)
174
186
  */
package/dist/index.cjs CHANGED
@@ -1047,13 +1047,21 @@ function computeTxDigest(binding) {
1047
1047
  return crypto.createHash("sha256").update(canonical, "utf8").digest("hex");
1048
1048
  }
1049
1049
 
1050
+ // src/metrics/GateMetricsSink.ts
1051
+ var noOpMetricsSink = {
1052
+ emit() {
1053
+ }
1054
+ };
1055
+
1050
1056
  // src/kms/wrapAwsSdkV3KmsClient.ts
1051
1057
  function wrapKmsClient(kmsClient, gateClient, options = {}) {
1052
1058
  const defaultOptions = {
1053
1059
  mode: options.mode || "enforce",
1060
+ requireReceiptForSign: options.requireReceiptForSign ?? false,
1054
1061
  onDecision: options.onDecision || (() => {
1055
1062
  }),
1056
- extractTxIntent: options.extractTxIntent || defaultExtractTxIntent
1063
+ extractTxIntent: options.extractTxIntent || defaultExtractTxIntent,
1064
+ metricsSink: options.metricsSink ?? noOpMetricsSink
1057
1065
  };
1058
1066
  const wrapped = new Proxy(kmsClient, {
1059
1067
  get(target, prop, receiver) {
@@ -1094,12 +1102,39 @@ function defaultExtractTxIntent(command) {
1094
1102
  // Backward compatibility
1095
1103
  };
1096
1104
  }
1105
+ function buildMetricLabels(gateClient, command, signerId, txIntent) {
1106
+ const config = gateClient.config;
1107
+ const keyId = command.input?.KeyId ?? command.KeyId;
1108
+ return {
1109
+ tenantId: config?.tenantId,
1110
+ signerId: signerId || void 0,
1111
+ adoptionStage: config?.adoptionStage ?? process.env.GATE_ADOPTION_STAGE,
1112
+ env: config?.env ?? process.env.GATE_ENV ?? process.env.NODE_ENV,
1113
+ chain: txIntent.chainId != null ? String(txIntent.chainId) : txIntent.networkFamily,
1114
+ kmsKeyId: keyId,
1115
+ region: process.env.AWS_REGION
1116
+ };
1117
+ }
1118
+ function emitMetric(sink, name, labels) {
1119
+ const event = { name, labels, timestampMs: Date.now() };
1120
+ try {
1121
+ const result = sink.emit(event);
1122
+ if (result && typeof result.catch === "function") {
1123
+ result.catch(() => {
1124
+ });
1125
+ }
1126
+ } catch {
1127
+ }
1128
+ }
1097
1129
  async function handleSignCommand(command, originalClient, gateClient, options) {
1098
1130
  const txIntent = options.extractTxIntent(command);
1099
1131
  const signerId = command.input?.KeyId ?? command.KeyId ?? "unknown";
1100
- gateClient.heartbeatManager.updateSignerId(signerId);
1101
- const heartbeatToken = gateClient.heartbeatManager.getToken();
1102
- if (!heartbeatToken) {
1132
+ const labels = buildMetricLabels(gateClient, command, signerId, txIntent);
1133
+ emitMetric(options.metricsSink, "sign_attempt_total", labels);
1134
+ let heartbeatToken;
1135
+ try {
1136
+ heartbeatToken = await gateClient.heartbeatManager.getTokenForSigner(signerId, 2e3);
1137
+ } catch {
1103
1138
  throw new BlockIntelBlockedError(
1104
1139
  "HEARTBEAT_MISSING",
1105
1140
  void 0,
@@ -1123,6 +1158,28 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1123
1158
  // Type assertion - txIntent may have extra fields
1124
1159
  signingContext
1125
1160
  });
1161
+ if (decision.decision === "ALLOW" && options.requireReceiptForSign) {
1162
+ const hasReceipt2 = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
1163
+ if (!hasReceipt2) {
1164
+ emitMetric(options.metricsSink, "sign_blocked_missing_receipt_total", labels);
1165
+ options.onDecision("BLOCK", {
1166
+ error: new BlockIntelBlockedError(
1167
+ "RECEIPT_REQUIRED",
1168
+ decision.decisionId,
1169
+ decision.correlationId,
1170
+ void 0
1171
+ ),
1172
+ signerId,
1173
+ command
1174
+ });
1175
+ throw new BlockIntelBlockedError(
1176
+ "RECEIPT_REQUIRED",
1177
+ decision.decisionId,
1178
+ decision.correlationId,
1179
+ void 0
1180
+ );
1181
+ }
1182
+ }
1126
1183
  if (decision.decision === "ALLOW" && gateClient.getRequireDecisionToken() && decision.txDigest != null) {
1127
1184
  const binding = buildTxBindingObject(
1128
1185
  txIntent,
@@ -1151,6 +1208,11 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1151
1208
  );
1152
1209
  }
1153
1210
  }
1211
+ const hasReceipt = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
1212
+ if (hasReceipt) {
1213
+ emitMetric(options.metricsSink, "sign_success_with_receipt_total", labels);
1214
+ }
1215
+ emitMetric(options.metricsSink, "sign_success_total", labels);
1154
1216
  options.onDecision("ALLOW", { decision, signerId, command });
1155
1217
  if (options.mode === "dry-run") {
1156
1218
  return await originalClient.send(new clientKms.SignCommand(command));
@@ -1211,7 +1273,7 @@ var ProvenanceProvider = class {
1211
1273
  var HeartbeatManager = class {
1212
1274
  httpClient;
1213
1275
  tenantId;
1214
- signerId;
1276
+ defaultSignerId;
1215
1277
  environment;
1216
1278
  baseRefreshIntervalSeconds;
1217
1279
  clientInstanceId;
@@ -1220,22 +1282,27 @@ var HeartbeatManager = class {
1220
1282
  // SDK version for tracking
1221
1283
  apiKey;
1222
1284
  // x-gate-heartbeat-key for Control Plane auth
1223
- currentToken = null;
1224
- refreshTimer = null;
1285
+ signerEntries = /* @__PURE__ */ new Map();
1286
+ evictionTimer = null;
1225
1287
  started = false;
1226
- consecutiveFailures = 0;
1227
1288
  maxBackoffSeconds = 30;
1228
1289
  // Maximum backoff interval
1290
+ maxSigners;
1291
+ signerIdleTtlMs;
1292
+ localRateLimitMs;
1229
1293
  constructor(options) {
1230
1294
  this.httpClient = options.httpClient;
1231
1295
  this.tenantId = options.tenantId;
1232
- this.signerId = options.signerId;
1296
+ this.defaultSignerId = options.signerId;
1233
1297
  this.environment = options.environment ?? "prod";
1234
1298
  this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
1235
1299
  this.apiKey = options.apiKey;
1236
1300
  this.clientInstanceId = options.clientInstanceId || uuid.v4();
1237
1301
  this.sdkVersion = options.sdkVersion || "1.0.0";
1238
1302
  this.apiKey = options.apiKey;
1303
+ this.maxSigners = options.maxSigners ?? 20;
1304
+ this.signerIdleTtlMs = options.signerIdleTtlMs ?? 3e5;
1305
+ this.localRateLimitMs = options.localRateLimitMs ?? 2100;
1239
1306
  }
1240
1307
  /**
1241
1308
  * Start background heartbeat refresher.
@@ -1246,46 +1313,56 @@ var HeartbeatManager = class {
1246
1313
  return;
1247
1314
  }
1248
1315
  this.started = true;
1249
- this.acquireHeartbeat().catch((error) => {
1250
- console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1316
+ this.startEvictionTimer();
1317
+ this.getTokenForSigner(this.defaultSignerId, 0).catch((error) => {
1318
+ console.warn("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1251
1319
  });
1252
- this.scheduleNextRefresh();
1320
+ }
1321
+ startEvictionTimer() {
1322
+ if (this.evictionTimer) clearInterval(this.evictionTimer);
1323
+ this.evictionTimer = setInterval(() => {
1324
+ const now = Date.now();
1325
+ for (const [signerId, entry] of this.signerEntries) {
1326
+ if (now - entry.lastUsedMs > this.signerIdleTtlMs) {
1327
+ if (entry.refreshTimer) clearTimeout(entry.refreshTimer);
1328
+ this.signerEntries.delete(signerId);
1329
+ }
1330
+ }
1331
+ }, 6e4);
1253
1332
  }
1254
1333
  /**
1255
- * Schedule next refresh with jitter and backoff
1334
+ * Schedule next refresh with jitter and backoff for a specific signer
1256
1335
  */
1257
- scheduleNextRefresh() {
1258
- if (!this.started) {
1336
+ scheduleRefreshForSigner(signerId, entry) {
1337
+ if (!this.started || !this.signerEntries.has(signerId)) {
1259
1338
  return;
1260
1339
  }
1340
+ if (entry.refreshTimer) {
1341
+ clearTimeout(entry.refreshTimer);
1342
+ entry.refreshTimer = null;
1343
+ }
1261
1344
  const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
1262
1345
  const jitter = Math.random() * 2e3;
1263
- const backoff = this.calculateBackoff();
1346
+ const backoff = Math.min(
1347
+ Math.pow(2, entry.consecutiveFailures) * 1e3,
1348
+ this.maxBackoffSeconds * 1e3
1349
+ );
1264
1350
  const interval = baseInterval + jitter + backoff;
1265
- this.refreshTimer = setTimeout(() => {
1266
- this.acquireHeartbeat().then(() => {
1267
- this.consecutiveFailures = 0;
1268
- this.scheduleNextRefresh();
1351
+ entry.refreshTimer = setTimeout(() => {
1352
+ if (!this.signerEntries.has(signerId)) return;
1353
+ entry.acquiring = true;
1354
+ entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).then(() => {
1355
+ this.scheduleRefreshForSigner(signerId, entry);
1269
1356
  }).catch((error) => {
1270
- this.consecutiveFailures++;
1271
- console.error("[HEARTBEAT] Refresh failed (will retry):", error);
1272
- this.scheduleNextRefresh();
1357
+ entry.consecutiveFailures++;
1358
+ console.error(`[HEARTBEAT] Refresh failed for signer ${signerId} (will retry):`, error.message || error);
1359
+ this.scheduleRefreshForSigner(signerId, entry);
1360
+ }).finally(() => {
1361
+ entry.acquiring = false;
1362
+ entry.acquirePromise = null;
1273
1363
  });
1274
1364
  }, interval);
1275
1365
  }
1276
- /**
1277
- * Calculate exponential backoff (capped at maxBackoffSeconds)
1278
- */
1279
- calculateBackoff() {
1280
- if (this.consecutiveFailures === 0) {
1281
- return 0;
1282
- }
1283
- const backoffSeconds = Math.min(
1284
- Math.pow(2, this.consecutiveFailures) * 1e3,
1285
- this.maxBackoffSeconds * 1e3
1286
- );
1287
- return backoffSeconds;
1288
- }
1289
1366
  /**
1290
1367
  * Stop background heartbeat refresher
1291
1368
  */
@@ -1294,45 +1371,153 @@ var HeartbeatManager = class {
1294
1371
  return;
1295
1372
  }
1296
1373
  this.started = false;
1297
- if (this.refreshTimer) {
1298
- clearTimeout(this.refreshTimer);
1299
- this.refreshTimer = null;
1374
+ if (this.evictionTimer) {
1375
+ clearInterval(this.evictionTimer);
1376
+ this.evictionTimer = null;
1377
+ }
1378
+ for (const [signerId, entry] of this.signerEntries) {
1379
+ if (entry.refreshTimer) {
1380
+ clearTimeout(entry.refreshTimer);
1381
+ entry.refreshTimer = null;
1382
+ }
1300
1383
  }
1384
+ this.signerEntries.clear();
1301
1385
  }
1302
1386
  /**
1303
- * Get current heartbeat token if valid
1387
+ * Get current heartbeat token if valid for the default signer
1388
+ * @deprecated Use getTokenForSigner() instead.
1304
1389
  */
1305
1390
  getToken() {
1306
- if (!this.currentToken) {
1307
- return null;
1391
+ const entry = this.signerEntries.get(this.defaultSignerId);
1392
+ if (entry && entry.token && entry.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
1393
+ entry.lastUsedMs = Date.now();
1394
+ return entry.token.token;
1308
1395
  }
1309
- const now = Math.floor(Date.now() / 1e3);
1310
- if (this.currentToken.expiresAt <= now + 2) {
1311
- return null;
1312
- }
1313
- return this.currentToken.token;
1396
+ return null;
1314
1397
  }
1315
1398
  /**
1316
- * Check if current heartbeat token is valid
1399
+ * Check if current heartbeat token is valid for the default signer
1400
+ * @deprecated Use getTokenForSigner() instead.
1317
1401
  */
1318
1402
  isValid() {
1319
1403
  return this.getToken() !== null;
1320
1404
  }
1321
1405
  /**
1322
- * Update signer ID (called when signer is known)
1406
+ * Update signer ID (called when signer is known).
1407
+ * @deprecated Use getTokenForSigner() — signerId changes are handled automatically by the per-signer cache.
1323
1408
  */
1324
1409
  updateSignerId(signerId) {
1325
- if (this.signerId !== signerId) {
1326
- this.signerId = signerId;
1327
- this.currentToken = null;
1410
+ this.defaultSignerId = signerId;
1411
+ }
1412
+ /**
1413
+ * Get a valid heartbeat token for a specific signer.
1414
+ * Returns immediately if a cached valid token exists.
1415
+ * If no token, triggers acquisition and returns a Promise that resolves
1416
+ * when the token is available (or rejects after maxWaitMs).
1417
+ */
1418
+ async getTokenForSigner(signerId, maxWaitMs = 2e3) {
1419
+ if (!this.started) {
1420
+ throw new GateError("HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */, "HeartbeatManager not started");
1328
1421
  }
1422
+ const startTime = Date.now();
1423
+ let entry = this.signerEntries.get(signerId);
1424
+ const now = Date.now();
1425
+ const getValidToken = (e) => {
1426
+ if (e.token && e.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
1427
+ return e.token.token;
1428
+ }
1429
+ return null;
1430
+ };
1431
+ if (entry) {
1432
+ entry.lastUsedMs = now;
1433
+ const t2 = getValidToken(entry);
1434
+ if (t2) return t2;
1435
+ } else {
1436
+ if (this.signerEntries.size >= this.maxSigners) {
1437
+ let oldestSignerId = null;
1438
+ let oldestUsedMs = Infinity;
1439
+ for (const [sId, e] of this.signerEntries) {
1440
+ if (e.lastUsedMs < oldestUsedMs) {
1441
+ oldestUsedMs = e.lastUsedMs;
1442
+ oldestSignerId = sId;
1443
+ }
1444
+ }
1445
+ if (oldestSignerId) {
1446
+ const oldestEntry = this.signerEntries.get(oldestSignerId);
1447
+ if (oldestEntry?.refreshTimer) clearTimeout(oldestEntry.refreshTimer);
1448
+ this.signerEntries.delete(oldestSignerId);
1449
+ }
1450
+ }
1451
+ entry = {
1452
+ token: null,
1453
+ refreshTimer: null,
1454
+ consecutiveFailures: 0,
1455
+ lastAcquireAttemptMs: 0,
1456
+ lastUsedMs: now,
1457
+ acquiring: false,
1458
+ acquirePromise: null
1459
+ };
1460
+ this.signerEntries.set(signerId, entry);
1461
+ }
1462
+ if (entry.acquiring && entry.acquirePromise) {
1463
+ const remainingWait = Math.max(0, maxWaitMs - (Date.now() - startTime));
1464
+ try {
1465
+ await Promise.race([
1466
+ entry.acquirePromise,
1467
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait))
1468
+ ]);
1469
+ } catch (e) {
1470
+ }
1471
+ const t2 = getValidToken(entry);
1472
+ if (t2) return t2;
1473
+ }
1474
+ const timeSinceLastAttempt = Date.now() - entry.lastAcquireAttemptMs;
1475
+ let timeToWaitBeforeFetch = 0;
1476
+ if (timeSinceLastAttempt < this.localRateLimitMs) {
1477
+ timeToWaitBeforeFetch = this.localRateLimitMs - timeSinceLastAttempt;
1478
+ }
1479
+ const remainingWait2 = Math.max(0, maxWaitMs - (Date.now() - startTime));
1480
+ if (timeToWaitBeforeFetch >= remainingWait2) {
1481
+ throw new GateError(
1482
+ "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1483
+ "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1484
+ );
1485
+ }
1486
+ if (timeToWaitBeforeFetch > 0) {
1487
+ await new Promise((resolve) => setTimeout(resolve, timeToWaitBeforeFetch));
1488
+ }
1489
+ if (!entry.acquiring) {
1490
+ entry.acquiring = true;
1491
+ entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).finally(() => {
1492
+ if (entry) {
1493
+ entry.acquiring = false;
1494
+ entry.acquirePromise = null;
1495
+ }
1496
+ });
1497
+ }
1498
+ const remainingWait3 = Math.max(0, maxWaitMs - (Date.now() - startTime));
1499
+ try {
1500
+ if (entry.acquirePromise) {
1501
+ await Promise.race([
1502
+ entry.acquirePromise,
1503
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait3))
1504
+ ]);
1505
+ }
1506
+ } catch (e) {
1507
+ }
1508
+ const t = getValidToken(entry);
1509
+ if (t) return t;
1510
+ throw new GateError(
1511
+ "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1512
+ "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1513
+ );
1329
1514
  }
1330
1515
  /**
1331
- * Acquire a new heartbeat token from Control Plane
1516
+ * Acquire a new heartbeat token from Control Plane for a specific signer
1332
1517
  * NEVER logs token value (security)
1333
1518
  * Requires x-gate-heartbeat-key header (apiKey) for authentication.
1334
1519
  */
1335
- async acquireHeartbeat() {
1520
+ async acquireHeartbeatForSigner(signerId, entry) {
1336
1521
  if (!this.apiKey || this.apiKey.length === 0) {
1337
1522
  throw new GateError(
1338
1523
  "UNAUTHORIZED" /* UNAUTHORIZED */,
@@ -1340,6 +1525,7 @@ var HeartbeatManager = class {
1340
1525
  {}
1341
1526
  );
1342
1527
  }
1528
+ entry.lastAcquireAttemptMs = Date.now();
1343
1529
  try {
1344
1530
  const response = await this.httpClient.request({
1345
1531
  method: "POST",
@@ -1349,12 +1535,15 @@ var HeartbeatManager = class {
1349
1535
  },
1350
1536
  body: {
1351
1537
  tenantId: this.tenantId,
1352
- signerId: this.signerId,
1538
+ signerId,
1353
1539
  environment: this.environment,
1354
1540
  clientInstanceId: this.clientInstanceId,
1355
1541
  sdkVersion: this.sdkVersion
1356
1542
  }
1357
1543
  });
1544
+ if (!this.signerEntries.has(signerId)) {
1545
+ return;
1546
+ }
1358
1547
  if (response.success && response.data) {
1359
1548
  const token = response.data.heartbeatToken;
1360
1549
  const expiresAt = response.data.expiresAt;
@@ -1364,18 +1553,23 @@ var HeartbeatManager = class {
1364
1553
  "Invalid heartbeat response: missing token or expiresAt"
1365
1554
  );
1366
1555
  }
1367
- this.currentToken = {
1556
+ entry.token = {
1368
1557
  token,
1369
1558
  expiresAt,
1370
1559
  jti: response.data.jti,
1371
1560
  policyHash: response.data.policyHash
1372
1561
  };
1562
+ entry.consecutiveFailures = 0;
1373
1563
  console.log("[HEARTBEAT] Acquired heartbeat token", {
1374
1564
  expiresAt,
1565
+ signerId,
1375
1566
  jti: response.data.jti,
1376
1567
  policyHash: response.data.policyHash?.substring(0, 8) + "..."
1377
1568
  // DO NOT log token value
1378
1569
  });
1570
+ if (!entry.refreshTimer) {
1571
+ this.scheduleRefreshForSigner(signerId, entry);
1572
+ }
1379
1573
  } else {
1380
1574
  const error = response.error || {};
1381
1575
  throw new GateError(
@@ -1384,7 +1578,7 @@ var HeartbeatManager = class {
1384
1578
  );
1385
1579
  }
1386
1580
  } catch (error) {
1387
- console.error("[HEARTBEAT] Failed to acquire heartbeat:", error.message || error);
1581
+ console.error(`[HEARTBEAT] Failed to acquire heartbeat for signer ${signerId}:`, error.message || error);
1388
1582
  throw error;
1389
1583
  }
1390
1584
  }
@@ -1641,6 +1835,7 @@ var IamPermissionRiskChecker = class {
1641
1835
  };
1642
1836
 
1643
1837
  // src/client/GateClient.ts
1838
+ var DEFAULT_SIGNER_ID = "gate-sdk-client";
1644
1839
  var GateClient = class {
1645
1840
  config;
1646
1841
  httpClient;
@@ -1715,7 +1910,7 @@ var GateClient = class {
1715
1910
  // 5s timeout for heartbeat
1716
1911
  userAgent: config.userAgent
1717
1912
  });
1718
- const initialSignerId = config.signerId ?? "trading-bot-signer";
1913
+ const initialSignerId = config.signerId ?? DEFAULT_SIGNER_ID;
1719
1914
  this.heartbeatManager = new HeartbeatManager({
1720
1915
  httpClient: heartbeatHttpClient,
1721
1916
  tenantId: config.tenantId,
@@ -1790,26 +1985,10 @@ var GateClient = class {
1790
1985
  const requestMode = req.mode || this.mode;
1791
1986
  const requireToken = this.getRequireDecisionToken();
1792
1987
  const executeRequest = async () => {
1793
- if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
1794
- this.heartbeatManager.updateSignerId(req.signingContext.signerId);
1795
- }
1796
1988
  let heartbeatToken = null;
1797
1989
  if (!this.config.local && this.heartbeatManager) {
1798
- heartbeatToken = this.heartbeatManager.getToken();
1799
- if (!heartbeatToken) {
1800
- const maxWaitMs = 2e3;
1801
- const startTime2 = Date.now();
1802
- while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
1803
- await new Promise((resolve) => setTimeout(resolve, 50));
1804
- heartbeatToken = this.heartbeatManager.getToken();
1805
- }
1806
- }
1807
- if (!heartbeatToken) {
1808
- throw new GateError(
1809
- "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1810
- "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1811
- );
1812
- }
1990
+ const effectiveSignerId2 = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1991
+ heartbeatToken = await this.heartbeatManager.getTokenForSigner(effectiveSignerId2, 2e3);
1813
1992
  }
1814
1993
  const txIntent = { ...req.txIntent };
1815
1994
  if (txIntent.to && !txIntent.toAddress) {
@@ -1822,10 +2001,11 @@ var GateClient = class {
1822
2001
  if (txIntent.from && !txIntent.fromAddress) {
1823
2002
  delete txIntent.from;
1824
2003
  }
2004
+ const effectiveSignerId = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1825
2005
  const signingContext = {
1826
2006
  ...req.signingContext,
1827
- actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? "gate-sdk-client",
1828
- signerId: req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? "gate-sdk-client"
2007
+ actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? DEFAULT_SIGNER_ID,
2008
+ signerId: effectiveSignerId
1829
2009
  };
1830
2010
  if (heartbeatToken) {
1831
2011
  signingContext.heartbeatToken = heartbeatToken;
@@ -1942,6 +2122,9 @@ var GateClient = class {
1942
2122
  enforced: responseData.enforced ?? requestMode === "ENFORCE",
1943
2123
  shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
1944
2124
  mode: responseData.mode ?? requestMode,
2125
+ receipt: responseData.receipt,
2126
+ decisionHash: responseData.decision_hash ?? responseData.decisionHash,
2127
+ receiptSignature: responseData.receipt_signature ?? responseData.receiptSignature,
1945
2128
  ...simulationData ? {
1946
2129
  simulation: {
1947
2130
  willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,
@@ -3035,6 +3218,7 @@ exports.buildTxBindingObject = buildTxBindingObject;
3035
3218
  exports.computeTxDigest = computeTxDigest;
3036
3219
  exports.createGateClient = createGateClient;
3037
3220
  exports.default = GateClient;
3221
+ exports.noOpMetricsSink = noOpMetricsSink;
3038
3222
  exports.wrapKmsClient = wrapKmsClient;
3039
3223
  //# sourceMappingURL=index.cjs.map
3040
3224
  //# sourceMappingURL=index.cjs.map