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.
@@ -1,4 +1,4 @@
1
- import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-KKk945Ox.cjs';
1
+ import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-Dxb9vt_M.cjs';
2
2
 
3
3
  /**
4
4
  * 5-line integration: attest a completed signature using GATE_PILOT_TOKEN.
@@ -1,4 +1,4 @@
1
- import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-KKk945Ox.js';
1
+ import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-Dxb9vt_M.js';
2
2
 
3
3
  /**
4
4
  * 5-line integration: attest a completed signature using GATE_PILOT_TOKEN.
@@ -1014,13 +1014,21 @@ function computeTxDigest(binding) {
1014
1014
  return createHash("sha256").update(canonical, "utf8").digest("hex");
1015
1015
  }
1016
1016
 
1017
+ // src/metrics/GateMetricsSink.ts
1018
+ var noOpMetricsSink = {
1019
+ emit() {
1020
+ }
1021
+ };
1022
+
1017
1023
  // src/kms/wrapAwsSdkV3KmsClient.ts
1018
1024
  function wrapKmsClient(kmsClient, gateClient, options = {}) {
1019
1025
  const defaultOptions = {
1020
1026
  mode: options.mode || "enforce",
1027
+ requireReceiptForSign: options.requireReceiptForSign ?? false,
1021
1028
  onDecision: options.onDecision || (() => {
1022
1029
  }),
1023
- extractTxIntent: options.extractTxIntent || defaultExtractTxIntent
1030
+ extractTxIntent: options.extractTxIntent || defaultExtractTxIntent,
1031
+ metricsSink: options.metricsSink ?? noOpMetricsSink
1024
1032
  };
1025
1033
  const wrapped = new Proxy(kmsClient, {
1026
1034
  get(target, prop, receiver) {
@@ -1061,12 +1069,39 @@ function defaultExtractTxIntent(command) {
1061
1069
  // Backward compatibility
1062
1070
  };
1063
1071
  }
1072
+ function buildMetricLabels(gateClient, command, signerId, txIntent) {
1073
+ const config = gateClient.config;
1074
+ const keyId = command.input?.KeyId ?? command.KeyId;
1075
+ return {
1076
+ tenantId: config?.tenantId,
1077
+ signerId: signerId || void 0,
1078
+ adoptionStage: config?.adoptionStage ?? process.env.GATE_ADOPTION_STAGE,
1079
+ env: config?.env ?? process.env.GATE_ENV ?? process.env.NODE_ENV,
1080
+ chain: txIntent.chainId != null ? String(txIntent.chainId) : txIntent.networkFamily,
1081
+ kmsKeyId: keyId,
1082
+ region: process.env.AWS_REGION
1083
+ };
1084
+ }
1085
+ function emitMetric(sink, name, labels) {
1086
+ const event = { name, labels, timestampMs: Date.now() };
1087
+ try {
1088
+ const result = sink.emit(event);
1089
+ if (result && typeof result.catch === "function") {
1090
+ result.catch(() => {
1091
+ });
1092
+ }
1093
+ } catch {
1094
+ }
1095
+ }
1064
1096
  async function handleSignCommand(command, originalClient, gateClient, options) {
1065
1097
  const txIntent = options.extractTxIntent(command);
1066
1098
  const signerId = command.input?.KeyId ?? command.KeyId ?? "unknown";
1067
- gateClient.heartbeatManager.updateSignerId(signerId);
1068
- const heartbeatToken = gateClient.heartbeatManager.getToken();
1069
- if (!heartbeatToken) {
1099
+ const labels = buildMetricLabels(gateClient, command, signerId, txIntent);
1100
+ emitMetric(options.metricsSink, "sign_attempt_total", labels);
1101
+ let heartbeatToken;
1102
+ try {
1103
+ heartbeatToken = await gateClient.heartbeatManager.getTokenForSigner(signerId, 2e3);
1104
+ } catch {
1070
1105
  throw new BlockIntelBlockedError(
1071
1106
  "HEARTBEAT_MISSING",
1072
1107
  void 0,
@@ -1090,6 +1125,28 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1090
1125
  // Type assertion - txIntent may have extra fields
1091
1126
  signingContext
1092
1127
  });
1128
+ if (decision.decision === "ALLOW" && options.requireReceiptForSign) {
1129
+ const hasReceipt2 = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
1130
+ if (!hasReceipt2) {
1131
+ emitMetric(options.metricsSink, "sign_blocked_missing_receipt_total", labels);
1132
+ options.onDecision("BLOCK", {
1133
+ error: new BlockIntelBlockedError(
1134
+ "RECEIPT_REQUIRED",
1135
+ decision.decisionId,
1136
+ decision.correlationId,
1137
+ void 0
1138
+ ),
1139
+ signerId,
1140
+ command
1141
+ });
1142
+ throw new BlockIntelBlockedError(
1143
+ "RECEIPT_REQUIRED",
1144
+ decision.decisionId,
1145
+ decision.correlationId,
1146
+ void 0
1147
+ );
1148
+ }
1149
+ }
1093
1150
  if (decision.decision === "ALLOW" && gateClient.getRequireDecisionToken() && decision.txDigest != null) {
1094
1151
  const binding = buildTxBindingObject(
1095
1152
  txIntent,
@@ -1118,6 +1175,11 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1118
1175
  );
1119
1176
  }
1120
1177
  }
1178
+ const hasReceipt = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
1179
+ if (hasReceipt) {
1180
+ emitMetric(options.metricsSink, "sign_success_with_receipt_total", labels);
1181
+ }
1182
+ emitMetric(options.metricsSink, "sign_success_total", labels);
1121
1183
  options.onDecision("ALLOW", { decision, signerId, command });
1122
1184
  if (options.mode === "dry-run") {
1123
1185
  return await originalClient.send(new SignCommand(command));
@@ -1178,7 +1240,7 @@ var ProvenanceProvider = class {
1178
1240
  var HeartbeatManager = class {
1179
1241
  httpClient;
1180
1242
  tenantId;
1181
- signerId;
1243
+ defaultSignerId;
1182
1244
  environment;
1183
1245
  baseRefreshIntervalSeconds;
1184
1246
  clientInstanceId;
@@ -1187,22 +1249,27 @@ var HeartbeatManager = class {
1187
1249
  // SDK version for tracking
1188
1250
  apiKey;
1189
1251
  // x-gate-heartbeat-key for Control Plane auth
1190
- currentToken = null;
1191
- refreshTimer = null;
1252
+ signerEntries = /* @__PURE__ */ new Map();
1253
+ evictionTimer = null;
1192
1254
  started = false;
1193
- consecutiveFailures = 0;
1194
1255
  maxBackoffSeconds = 30;
1195
1256
  // Maximum backoff interval
1257
+ maxSigners;
1258
+ signerIdleTtlMs;
1259
+ localRateLimitMs;
1196
1260
  constructor(options) {
1197
1261
  this.httpClient = options.httpClient;
1198
1262
  this.tenantId = options.tenantId;
1199
- this.signerId = options.signerId;
1263
+ this.defaultSignerId = options.signerId;
1200
1264
  this.environment = options.environment ?? "prod";
1201
1265
  this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
1202
1266
  this.apiKey = options.apiKey;
1203
1267
  this.clientInstanceId = options.clientInstanceId || v4();
1204
1268
  this.sdkVersion = options.sdkVersion || "1.0.0";
1205
1269
  this.apiKey = options.apiKey;
1270
+ this.maxSigners = options.maxSigners ?? 20;
1271
+ this.signerIdleTtlMs = options.signerIdleTtlMs ?? 3e5;
1272
+ this.localRateLimitMs = options.localRateLimitMs ?? 2100;
1206
1273
  }
1207
1274
  /**
1208
1275
  * Start background heartbeat refresher.
@@ -1213,46 +1280,56 @@ var HeartbeatManager = class {
1213
1280
  return;
1214
1281
  }
1215
1282
  this.started = true;
1216
- this.acquireHeartbeat().catch((error) => {
1217
- console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1283
+ this.startEvictionTimer();
1284
+ this.getTokenForSigner(this.defaultSignerId, 0).catch((error) => {
1285
+ console.warn("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1218
1286
  });
1219
- this.scheduleNextRefresh();
1287
+ }
1288
+ startEvictionTimer() {
1289
+ if (this.evictionTimer) clearInterval(this.evictionTimer);
1290
+ this.evictionTimer = setInterval(() => {
1291
+ const now = Date.now();
1292
+ for (const [signerId, entry] of this.signerEntries) {
1293
+ if (now - entry.lastUsedMs > this.signerIdleTtlMs) {
1294
+ if (entry.refreshTimer) clearTimeout(entry.refreshTimer);
1295
+ this.signerEntries.delete(signerId);
1296
+ }
1297
+ }
1298
+ }, 6e4);
1220
1299
  }
1221
1300
  /**
1222
- * Schedule next refresh with jitter and backoff
1301
+ * Schedule next refresh with jitter and backoff for a specific signer
1223
1302
  */
1224
- scheduleNextRefresh() {
1225
- if (!this.started) {
1303
+ scheduleRefreshForSigner(signerId, entry) {
1304
+ if (!this.started || !this.signerEntries.has(signerId)) {
1226
1305
  return;
1227
1306
  }
1307
+ if (entry.refreshTimer) {
1308
+ clearTimeout(entry.refreshTimer);
1309
+ entry.refreshTimer = null;
1310
+ }
1228
1311
  const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
1229
1312
  const jitter = Math.random() * 2e3;
1230
- const backoff = this.calculateBackoff();
1313
+ const backoff = Math.min(
1314
+ Math.pow(2, entry.consecutiveFailures) * 1e3,
1315
+ this.maxBackoffSeconds * 1e3
1316
+ );
1231
1317
  const interval = baseInterval + jitter + backoff;
1232
- this.refreshTimer = setTimeout(() => {
1233
- this.acquireHeartbeat().then(() => {
1234
- this.consecutiveFailures = 0;
1235
- this.scheduleNextRefresh();
1318
+ entry.refreshTimer = setTimeout(() => {
1319
+ if (!this.signerEntries.has(signerId)) return;
1320
+ entry.acquiring = true;
1321
+ entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).then(() => {
1322
+ this.scheduleRefreshForSigner(signerId, entry);
1236
1323
  }).catch((error) => {
1237
- this.consecutiveFailures++;
1238
- console.error("[HEARTBEAT] Refresh failed (will retry):", error);
1239
- this.scheduleNextRefresh();
1324
+ entry.consecutiveFailures++;
1325
+ console.error(`[HEARTBEAT] Refresh failed for signer ${signerId} (will retry):`, error.message || error);
1326
+ this.scheduleRefreshForSigner(signerId, entry);
1327
+ }).finally(() => {
1328
+ entry.acquiring = false;
1329
+ entry.acquirePromise = null;
1240
1330
  });
1241
1331
  }, interval);
1242
1332
  }
1243
- /**
1244
- * Calculate exponential backoff (capped at maxBackoffSeconds)
1245
- */
1246
- calculateBackoff() {
1247
- if (this.consecutiveFailures === 0) {
1248
- return 0;
1249
- }
1250
- const backoffSeconds = Math.min(
1251
- Math.pow(2, this.consecutiveFailures) * 1e3,
1252
- this.maxBackoffSeconds * 1e3
1253
- );
1254
- return backoffSeconds;
1255
- }
1256
1333
  /**
1257
1334
  * Stop background heartbeat refresher
1258
1335
  */
@@ -1261,45 +1338,153 @@ var HeartbeatManager = class {
1261
1338
  return;
1262
1339
  }
1263
1340
  this.started = false;
1264
- if (this.refreshTimer) {
1265
- clearTimeout(this.refreshTimer);
1266
- this.refreshTimer = null;
1341
+ if (this.evictionTimer) {
1342
+ clearInterval(this.evictionTimer);
1343
+ this.evictionTimer = null;
1344
+ }
1345
+ for (const [signerId, entry] of this.signerEntries) {
1346
+ if (entry.refreshTimer) {
1347
+ clearTimeout(entry.refreshTimer);
1348
+ entry.refreshTimer = null;
1349
+ }
1267
1350
  }
1351
+ this.signerEntries.clear();
1268
1352
  }
1269
1353
  /**
1270
- * Get current heartbeat token if valid
1354
+ * Get current heartbeat token if valid for the default signer
1355
+ * @deprecated Use getTokenForSigner() instead.
1271
1356
  */
1272
1357
  getToken() {
1273
- if (!this.currentToken) {
1274
- return null;
1275
- }
1276
- const now = Math.floor(Date.now() / 1e3);
1277
- if (this.currentToken.expiresAt <= now + 2) {
1278
- return null;
1358
+ const entry = this.signerEntries.get(this.defaultSignerId);
1359
+ if (entry && entry.token && entry.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
1360
+ entry.lastUsedMs = Date.now();
1361
+ return entry.token.token;
1279
1362
  }
1280
- return this.currentToken.token;
1363
+ return null;
1281
1364
  }
1282
1365
  /**
1283
- * Check if current heartbeat token is valid
1366
+ * Check if current heartbeat token is valid for the default signer
1367
+ * @deprecated Use getTokenForSigner() instead.
1284
1368
  */
1285
1369
  isValid() {
1286
1370
  return this.getToken() !== null;
1287
1371
  }
1288
1372
  /**
1289
- * Update signer ID (called when signer is known)
1373
+ * Update signer ID (called when signer is known).
1374
+ * @deprecated Use getTokenForSigner() — signerId changes are handled automatically by the per-signer cache.
1290
1375
  */
1291
1376
  updateSignerId(signerId) {
1292
- if (this.signerId !== signerId) {
1293
- this.signerId = signerId;
1294
- this.currentToken = null;
1377
+ this.defaultSignerId = signerId;
1378
+ }
1379
+ /**
1380
+ * Get a valid heartbeat token for a specific signer.
1381
+ * Returns immediately if a cached valid token exists.
1382
+ * If no token, triggers acquisition and returns a Promise that resolves
1383
+ * when the token is available (or rejects after maxWaitMs).
1384
+ */
1385
+ async getTokenForSigner(signerId, maxWaitMs = 2e3) {
1386
+ if (!this.started) {
1387
+ throw new GateError("HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */, "HeartbeatManager not started");
1388
+ }
1389
+ const startTime = Date.now();
1390
+ let entry = this.signerEntries.get(signerId);
1391
+ const now = Date.now();
1392
+ const getValidToken = (e) => {
1393
+ if (e.token && e.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
1394
+ return e.token.token;
1395
+ }
1396
+ return null;
1397
+ };
1398
+ if (entry) {
1399
+ entry.lastUsedMs = now;
1400
+ const t2 = getValidToken(entry);
1401
+ if (t2) return t2;
1402
+ } else {
1403
+ if (this.signerEntries.size >= this.maxSigners) {
1404
+ let oldestSignerId = null;
1405
+ let oldestUsedMs = Infinity;
1406
+ for (const [sId, e] of this.signerEntries) {
1407
+ if (e.lastUsedMs < oldestUsedMs) {
1408
+ oldestUsedMs = e.lastUsedMs;
1409
+ oldestSignerId = sId;
1410
+ }
1411
+ }
1412
+ if (oldestSignerId) {
1413
+ const oldestEntry = this.signerEntries.get(oldestSignerId);
1414
+ if (oldestEntry?.refreshTimer) clearTimeout(oldestEntry.refreshTimer);
1415
+ this.signerEntries.delete(oldestSignerId);
1416
+ }
1417
+ }
1418
+ entry = {
1419
+ token: null,
1420
+ refreshTimer: null,
1421
+ consecutiveFailures: 0,
1422
+ lastAcquireAttemptMs: 0,
1423
+ lastUsedMs: now,
1424
+ acquiring: false,
1425
+ acquirePromise: null
1426
+ };
1427
+ this.signerEntries.set(signerId, entry);
1428
+ }
1429
+ if (entry.acquiring && entry.acquirePromise) {
1430
+ const remainingWait = Math.max(0, maxWaitMs - (Date.now() - startTime));
1431
+ try {
1432
+ await Promise.race([
1433
+ entry.acquirePromise,
1434
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait))
1435
+ ]);
1436
+ } catch (e) {
1437
+ }
1438
+ const t2 = getValidToken(entry);
1439
+ if (t2) return t2;
1295
1440
  }
1441
+ const timeSinceLastAttempt = Date.now() - entry.lastAcquireAttemptMs;
1442
+ let timeToWaitBeforeFetch = 0;
1443
+ if (timeSinceLastAttempt < this.localRateLimitMs) {
1444
+ timeToWaitBeforeFetch = this.localRateLimitMs - timeSinceLastAttempt;
1445
+ }
1446
+ const remainingWait2 = Math.max(0, maxWaitMs - (Date.now() - startTime));
1447
+ if (timeToWaitBeforeFetch >= remainingWait2) {
1448
+ throw new GateError(
1449
+ "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1450
+ "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1451
+ );
1452
+ }
1453
+ if (timeToWaitBeforeFetch > 0) {
1454
+ await new Promise((resolve) => setTimeout(resolve, timeToWaitBeforeFetch));
1455
+ }
1456
+ if (!entry.acquiring) {
1457
+ entry.acquiring = true;
1458
+ entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).finally(() => {
1459
+ if (entry) {
1460
+ entry.acquiring = false;
1461
+ entry.acquirePromise = null;
1462
+ }
1463
+ });
1464
+ }
1465
+ const remainingWait3 = Math.max(0, maxWaitMs - (Date.now() - startTime));
1466
+ try {
1467
+ if (entry.acquirePromise) {
1468
+ await Promise.race([
1469
+ entry.acquirePromise,
1470
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait3))
1471
+ ]);
1472
+ }
1473
+ } catch (e) {
1474
+ }
1475
+ const t = getValidToken(entry);
1476
+ if (t) return t;
1477
+ throw new GateError(
1478
+ "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1479
+ "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1480
+ );
1296
1481
  }
1297
1482
  /**
1298
- * Acquire a new heartbeat token from Control Plane
1483
+ * Acquire a new heartbeat token from Control Plane for a specific signer
1299
1484
  * NEVER logs token value (security)
1300
1485
  * Requires x-gate-heartbeat-key header (apiKey) for authentication.
1301
1486
  */
1302
- async acquireHeartbeat() {
1487
+ async acquireHeartbeatForSigner(signerId, entry) {
1303
1488
  if (!this.apiKey || this.apiKey.length === 0) {
1304
1489
  throw new GateError(
1305
1490
  "UNAUTHORIZED" /* UNAUTHORIZED */,
@@ -1307,6 +1492,7 @@ var HeartbeatManager = class {
1307
1492
  {}
1308
1493
  );
1309
1494
  }
1495
+ entry.lastAcquireAttemptMs = Date.now();
1310
1496
  try {
1311
1497
  const response = await this.httpClient.request({
1312
1498
  method: "POST",
@@ -1316,12 +1502,15 @@ var HeartbeatManager = class {
1316
1502
  },
1317
1503
  body: {
1318
1504
  tenantId: this.tenantId,
1319
- signerId: this.signerId,
1505
+ signerId,
1320
1506
  environment: this.environment,
1321
1507
  clientInstanceId: this.clientInstanceId,
1322
1508
  sdkVersion: this.sdkVersion
1323
1509
  }
1324
1510
  });
1511
+ if (!this.signerEntries.has(signerId)) {
1512
+ return;
1513
+ }
1325
1514
  if (response.success && response.data) {
1326
1515
  const token = response.data.heartbeatToken;
1327
1516
  const expiresAt = response.data.expiresAt;
@@ -1331,18 +1520,23 @@ var HeartbeatManager = class {
1331
1520
  "Invalid heartbeat response: missing token or expiresAt"
1332
1521
  );
1333
1522
  }
1334
- this.currentToken = {
1523
+ entry.token = {
1335
1524
  token,
1336
1525
  expiresAt,
1337
1526
  jti: response.data.jti,
1338
1527
  policyHash: response.data.policyHash
1339
1528
  };
1529
+ entry.consecutiveFailures = 0;
1340
1530
  console.log("[HEARTBEAT] Acquired heartbeat token", {
1341
1531
  expiresAt,
1532
+ signerId,
1342
1533
  jti: response.data.jti,
1343
1534
  policyHash: response.data.policyHash?.substring(0, 8) + "..."
1344
1535
  // DO NOT log token value
1345
1536
  });
1537
+ if (!entry.refreshTimer) {
1538
+ this.scheduleRefreshForSigner(signerId, entry);
1539
+ }
1346
1540
  } else {
1347
1541
  const error = response.error || {};
1348
1542
  throw new GateError(
@@ -1351,7 +1545,7 @@ var HeartbeatManager = class {
1351
1545
  );
1352
1546
  }
1353
1547
  } catch (error) {
1354
- console.error("[HEARTBEAT] Failed to acquire heartbeat:", error.message || error);
1548
+ console.error(`[HEARTBEAT] Failed to acquire heartbeat for signer ${signerId}:`, error.message || error);
1355
1549
  throw error;
1356
1550
  }
1357
1551
  }
@@ -1608,6 +1802,7 @@ var IamPermissionRiskChecker = class {
1608
1802
  };
1609
1803
 
1610
1804
  // src/client/GateClient.ts
1805
+ var DEFAULT_SIGNER_ID = "gate-sdk-client";
1611
1806
  var GateClient = class {
1612
1807
  config;
1613
1808
  httpClient;
@@ -1682,7 +1877,7 @@ var GateClient = class {
1682
1877
  // 5s timeout for heartbeat
1683
1878
  userAgent: config.userAgent
1684
1879
  });
1685
- const initialSignerId = config.signerId ?? "trading-bot-signer";
1880
+ const initialSignerId = config.signerId ?? DEFAULT_SIGNER_ID;
1686
1881
  this.heartbeatManager = new HeartbeatManager({
1687
1882
  httpClient: heartbeatHttpClient,
1688
1883
  tenantId: config.tenantId,
@@ -1757,26 +1952,10 @@ var GateClient = class {
1757
1952
  const requestMode = req.mode || this.mode;
1758
1953
  const requireToken = this.getRequireDecisionToken();
1759
1954
  const executeRequest = async () => {
1760
- if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
1761
- this.heartbeatManager.updateSignerId(req.signingContext.signerId);
1762
- }
1763
1955
  let heartbeatToken = null;
1764
1956
  if (!this.config.local && this.heartbeatManager) {
1765
- heartbeatToken = this.heartbeatManager.getToken();
1766
- if (!heartbeatToken) {
1767
- const maxWaitMs = 2e3;
1768
- const startTime2 = Date.now();
1769
- while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
1770
- await new Promise((resolve) => setTimeout(resolve, 50));
1771
- heartbeatToken = this.heartbeatManager.getToken();
1772
- }
1773
- }
1774
- if (!heartbeatToken) {
1775
- throw new GateError(
1776
- "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1777
- "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1778
- );
1779
- }
1957
+ const effectiveSignerId2 = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1958
+ heartbeatToken = await this.heartbeatManager.getTokenForSigner(effectiveSignerId2, 2e3);
1780
1959
  }
1781
1960
  const txIntent = { ...req.txIntent };
1782
1961
  if (txIntent.to && !txIntent.toAddress) {
@@ -1789,10 +1968,11 @@ var GateClient = class {
1789
1968
  if (txIntent.from && !txIntent.fromAddress) {
1790
1969
  delete txIntent.from;
1791
1970
  }
1971
+ const effectiveSignerId = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1792
1972
  const signingContext = {
1793
1973
  ...req.signingContext,
1794
- actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? "gate-sdk-client",
1795
- signerId: req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? "gate-sdk-client"
1974
+ actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? DEFAULT_SIGNER_ID,
1975
+ signerId: effectiveSignerId
1796
1976
  };
1797
1977
  if (heartbeatToken) {
1798
1978
  signingContext.heartbeatToken = heartbeatToken;
@@ -1909,6 +2089,9 @@ var GateClient = class {
1909
2089
  enforced: responseData.enforced ?? requestMode === "ENFORCE",
1910
2090
  shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
1911
2091
  mode: responseData.mode ?? requestMode,
2092
+ receipt: responseData.receipt,
2093
+ decisionHash: responseData.decision_hash ?? responseData.decisionHash,
2094
+ receiptSignature: responseData.receipt_signature ?? responseData.receiptSignature,
1912
2095
  ...simulationData ? {
1913
2096
  simulation: {
1914
2097
  willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,