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