blockintel-gate-sdk 0.3.10 → 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,24 +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
1196
- /** SignerId used for the in-flight request; used to ignore stale responses after updateSignerId(). */
1197
- acquiringForSignerId = null;
1257
+ maxSigners;
1258
+ signerIdleTtlMs;
1259
+ localRateLimitMs;
1198
1260
  constructor(options) {
1199
1261
  this.httpClient = options.httpClient;
1200
1262
  this.tenantId = options.tenantId;
1201
- this.signerId = options.signerId;
1263
+ this.defaultSignerId = options.signerId;
1202
1264
  this.environment = options.environment ?? "prod";
1203
1265
  this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
1204
1266
  this.apiKey = options.apiKey;
1205
1267
  this.clientInstanceId = options.clientInstanceId || v4();
1206
1268
  this.sdkVersion = options.sdkVersion || "1.0.0";
1207
1269
  this.apiKey = options.apiKey;
1270
+ this.maxSigners = options.maxSigners ?? 20;
1271
+ this.signerIdleTtlMs = options.signerIdleTtlMs ?? 3e5;
1272
+ this.localRateLimitMs = options.localRateLimitMs ?? 2100;
1208
1273
  }
1209
1274
  /**
1210
1275
  * Start background heartbeat refresher.
@@ -1215,46 +1280,56 @@ var HeartbeatManager = class {
1215
1280
  return;
1216
1281
  }
1217
1282
  this.started = true;
1218
- this.acquireHeartbeat().catch((error) => {
1219
- 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);
1220
1286
  });
1221
- 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);
1222
1299
  }
1223
1300
  /**
1224
- * Schedule next refresh with jitter and backoff
1301
+ * Schedule next refresh with jitter and backoff for a specific signer
1225
1302
  */
1226
- scheduleNextRefresh() {
1227
- if (!this.started) {
1303
+ scheduleRefreshForSigner(signerId, entry) {
1304
+ if (!this.started || !this.signerEntries.has(signerId)) {
1228
1305
  return;
1229
1306
  }
1307
+ if (entry.refreshTimer) {
1308
+ clearTimeout(entry.refreshTimer);
1309
+ entry.refreshTimer = null;
1310
+ }
1230
1311
  const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
1231
1312
  const jitter = Math.random() * 2e3;
1232
- const backoff = this.calculateBackoff();
1313
+ const backoff = Math.min(
1314
+ Math.pow(2, entry.consecutiveFailures) * 1e3,
1315
+ this.maxBackoffSeconds * 1e3
1316
+ );
1233
1317
  const interval = baseInterval + jitter + backoff;
1234
- this.refreshTimer = setTimeout(() => {
1235
- this.acquireHeartbeat().then(() => {
1236
- this.consecutiveFailures = 0;
1237
- 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);
1238
1323
  }).catch((error) => {
1239
- this.consecutiveFailures++;
1240
- console.error("[HEARTBEAT] Refresh failed (will retry):", error);
1241
- 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;
1242
1330
  });
1243
1331
  }, interval);
1244
1332
  }
1245
- /**
1246
- * Calculate exponential backoff (capped at maxBackoffSeconds)
1247
- */
1248
- calculateBackoff() {
1249
- if (this.consecutiveFailures === 0) {
1250
- return 0;
1251
- }
1252
- const backoffSeconds = Math.min(
1253
- Math.pow(2, this.consecutiveFailures) * 1e3,
1254
- this.maxBackoffSeconds * 1e3
1255
- );
1256
- return backoffSeconds;
1257
- }
1258
1333
  /**
1259
1334
  * Stop background heartbeat refresher
1260
1335
  */
@@ -1263,53 +1338,153 @@ var HeartbeatManager = class {
1263
1338
  return;
1264
1339
  }
1265
1340
  this.started = false;
1266
- if (this.refreshTimer) {
1267
- clearTimeout(this.refreshTimer);
1268
- 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
+ }
1269
1350
  }
1351
+ this.signerEntries.clear();
1270
1352
  }
1271
1353
  /**
1272
- * Get current heartbeat token if valid
1354
+ * Get current heartbeat token if valid for the default signer
1355
+ * @deprecated Use getTokenForSigner() instead.
1273
1356
  */
1274
1357
  getToken() {
1275
- if (!this.currentToken) {
1276
- 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;
1277
1362
  }
1278
- const now = Math.floor(Date.now() / 1e3);
1279
- if (this.currentToken.expiresAt <= now + 2) {
1280
- return null;
1281
- }
1282
- return this.currentToken.token;
1363
+ return null;
1283
1364
  }
1284
1365
  /**
1285
- * Check if current heartbeat token is valid
1366
+ * Check if current heartbeat token is valid for the default signer
1367
+ * @deprecated Use getTokenForSigner() instead.
1286
1368
  */
1287
1369
  isValid() {
1288
1370
  return this.getToken() !== null;
1289
1371
  }
1290
1372
  /**
1291
1373
  * Update signer ID (called when signer is known).
1292
- * Invalidates current token and triggers an immediate heartbeat acquisition so evaluate() can get a matching token.
1374
+ * @deprecated Use getTokenForSigner() signerId changes are handled automatically by the per-signer cache.
1293
1375
  */
1294
1376
  updateSignerId(signerId) {
1295
- if (this.signerId !== signerId) {
1296
- this.signerId = signerId;
1297
- this.currentToken = null;
1298
- if (this.started && this.apiKey) {
1299
- setImmediate(() => {
1300
- this.acquireHeartbeat().catch((err) => {
1301
- console.warn("[HEARTBEAT] Immediate acquire after signerId change failed:", err instanceof Error ? err.message : err);
1302
- });
1303
- });
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) {
1304
1437
  }
1438
+ const t2 = getValidToken(entry);
1439
+ if (t2) return t2;
1305
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
+ );
1306
1481
  }
1307
1482
  /**
1308
- * Acquire a new heartbeat token from Control Plane
1483
+ * Acquire a new heartbeat token from Control Plane for a specific signer
1309
1484
  * NEVER logs token value (security)
1310
1485
  * Requires x-gate-heartbeat-key header (apiKey) for authentication.
1311
1486
  */
1312
- async acquireHeartbeat() {
1487
+ async acquireHeartbeatForSigner(signerId, entry) {
1313
1488
  if (!this.apiKey || this.apiKey.length === 0) {
1314
1489
  throw new GateError(
1315
1490
  "UNAUTHORIZED" /* UNAUTHORIZED */,
@@ -1317,8 +1492,7 @@ var HeartbeatManager = class {
1317
1492
  {}
1318
1493
  );
1319
1494
  }
1320
- const signerIdAtRequest = this.signerId;
1321
- this.acquiringForSignerId = signerIdAtRequest;
1495
+ entry.lastAcquireAttemptMs = Date.now();
1322
1496
  try {
1323
1497
  const response = await this.httpClient.request({
1324
1498
  method: "POST",
@@ -1328,12 +1502,15 @@ var HeartbeatManager = class {
1328
1502
  },
1329
1503
  body: {
1330
1504
  tenantId: this.tenantId,
1331
- signerId: this.signerId,
1505
+ signerId,
1332
1506
  environment: this.environment,
1333
1507
  clientInstanceId: this.clientInstanceId,
1334
1508
  sdkVersion: this.sdkVersion
1335
1509
  }
1336
1510
  });
1511
+ if (!this.signerEntries.has(signerId)) {
1512
+ return;
1513
+ }
1337
1514
  if (response.success && response.data) {
1338
1515
  const token = response.data.heartbeatToken;
1339
1516
  const expiresAt = response.data.expiresAt;
@@ -1343,19 +1520,22 @@ var HeartbeatManager = class {
1343
1520
  "Invalid heartbeat response: missing token or expiresAt"
1344
1521
  );
1345
1522
  }
1346
- if (this.signerId === signerIdAtRequest) {
1347
- this.currentToken = {
1348
- token,
1349
- expiresAt,
1350
- jti: response.data.jti,
1351
- policyHash: response.data.policyHash
1352
- };
1353
- console.log("[HEARTBEAT] Acquired heartbeat token", {
1354
- expiresAt,
1355
- jti: response.data.jti,
1356
- policyHash: response.data.policyHash?.substring(0, 8) + "..."
1357
- // DO NOT log token value
1358
- });
1523
+ entry.token = {
1524
+ token,
1525
+ expiresAt,
1526
+ jti: response.data.jti,
1527
+ policyHash: response.data.policyHash
1528
+ };
1529
+ entry.consecutiveFailures = 0;
1530
+ console.log("[HEARTBEAT] Acquired heartbeat token", {
1531
+ expiresAt,
1532
+ signerId,
1533
+ jti: response.data.jti,
1534
+ policyHash: response.data.policyHash?.substring(0, 8) + "..."
1535
+ // DO NOT log token value
1536
+ });
1537
+ if (!entry.refreshTimer) {
1538
+ this.scheduleRefreshForSigner(signerId, entry);
1359
1539
  }
1360
1540
  } else {
1361
1541
  const error = response.error || {};
@@ -1365,12 +1545,8 @@ var HeartbeatManager = class {
1365
1545
  );
1366
1546
  }
1367
1547
  } catch (error) {
1368
- console.error("[HEARTBEAT] Failed to acquire heartbeat:", error.message || error);
1548
+ console.error(`[HEARTBEAT] Failed to acquire heartbeat for signer ${signerId}:`, error.message || error);
1369
1549
  throw error;
1370
- } finally {
1371
- if (this.acquiringForSignerId === signerIdAtRequest) {
1372
- this.acquiringForSignerId = null;
1373
- }
1374
1550
  }
1375
1551
  }
1376
1552
  /**
@@ -1626,6 +1802,7 @@ var IamPermissionRiskChecker = class {
1626
1802
  };
1627
1803
 
1628
1804
  // src/client/GateClient.ts
1805
+ var DEFAULT_SIGNER_ID = "gate-sdk-client";
1629
1806
  var GateClient = class {
1630
1807
  config;
1631
1808
  httpClient;
@@ -1700,7 +1877,7 @@ var GateClient = class {
1700
1877
  // 5s timeout for heartbeat
1701
1878
  userAgent: config.userAgent
1702
1879
  });
1703
- const initialSignerId = config.signerId ?? "trading-bot-signer";
1880
+ const initialSignerId = config.signerId ?? DEFAULT_SIGNER_ID;
1704
1881
  this.heartbeatManager = new HeartbeatManager({
1705
1882
  httpClient: heartbeatHttpClient,
1706
1883
  tenantId: config.tenantId,
@@ -1775,26 +1952,10 @@ var GateClient = class {
1775
1952
  const requestMode = req.mode || this.mode;
1776
1953
  const requireToken = this.getRequireDecisionToken();
1777
1954
  const executeRequest = async () => {
1778
- if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
1779
- this.heartbeatManager.updateSignerId(req.signingContext.signerId);
1780
- }
1781
1955
  let heartbeatToken = null;
1782
1956
  if (!this.config.local && this.heartbeatManager) {
1783
- heartbeatToken = this.heartbeatManager.getToken();
1784
- if (!heartbeatToken) {
1785
- const maxWaitMs = 2e3;
1786
- const startTime2 = Date.now();
1787
- while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
1788
- await new Promise((resolve) => setTimeout(resolve, 50));
1789
- heartbeatToken = this.heartbeatManager.getToken();
1790
- }
1791
- }
1792
- if (!heartbeatToken) {
1793
- throw new GateError(
1794
- "HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
1795
- "Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
1796
- );
1797
- }
1957
+ const effectiveSignerId2 = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1958
+ heartbeatToken = await this.heartbeatManager.getTokenForSigner(effectiveSignerId2, 2e3);
1798
1959
  }
1799
1960
  const txIntent = { ...req.txIntent };
1800
1961
  if (txIntent.to && !txIntent.toAddress) {
@@ -1807,10 +1968,11 @@ var GateClient = class {
1807
1968
  if (txIntent.from && !txIntent.fromAddress) {
1808
1969
  delete txIntent.from;
1809
1970
  }
1971
+ const effectiveSignerId = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
1810
1972
  const signingContext = {
1811
1973
  ...req.signingContext,
1812
- actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? "gate-sdk-client",
1813
- signerId: req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? "gate-sdk-client"
1974
+ actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? DEFAULT_SIGNER_ID,
1975
+ signerId: effectiveSignerId
1814
1976
  };
1815
1977
  if (heartbeatToken) {
1816
1978
  signingContext.heartbeatToken = heartbeatToken;
@@ -1927,6 +2089,9 @@ var GateClient = class {
1927
2089
  enforced: responseData.enforced ?? requestMode === "ENFORCE",
1928
2090
  shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
1929
2091
  mode: responseData.mode ?? requestMode,
2092
+ receipt: responseData.receipt,
2093
+ decisionHash: responseData.decision_hash ?? responseData.decisionHash,
2094
+ receiptSignature: responseData.receipt_signature ?? responseData.receiptSignature,
1930
2095
  ...simulationData ? {
1931
2096
  simulation: {
1932
2097
  willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,