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.
- package/dist/{contracts-KKk945Ox.d.cts → contracts-Dxb9vt_M.d.cts} +12 -0
- package/dist/{contracts-KKk945Ox.d.ts → contracts-Dxb9vt_M.d.ts} +12 -0
- package/dist/index.cjs +270 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -20
- package/dist/index.d.ts +69 -20
- package/dist/index.js +270 -105
- package/dist/index.js.map +1 -1
- package/dist/pilot/index.cjs +269 -104
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +1 -1
- package/dist/pilot/index.d.ts +1 -1
- package/dist/pilot/index.js +269 -104
- package/dist/pilot/index.js.map +1 -1
- package/package.json +1 -1
package/dist/pilot/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-
|
|
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.
|
package/dist/pilot/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-
|
|
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.
|
package/dist/pilot/index.js
CHANGED
|
@@ -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
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1197
|
-
|
|
1257
|
+
maxSigners;
|
|
1258
|
+
signerIdleTtlMs;
|
|
1259
|
+
localRateLimitMs;
|
|
1198
1260
|
constructor(options) {
|
|
1199
1261
|
this.httpClient = options.httpClient;
|
|
1200
1262
|
this.tenantId = options.tenantId;
|
|
1201
|
-
this.
|
|
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.
|
|
1219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1235
|
-
this.
|
|
1236
|
-
|
|
1237
|
-
|
|
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
|
-
|
|
1240
|
-
console.error(
|
|
1241
|
-
this.
|
|
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.
|
|
1267
|
-
|
|
1268
|
-
this.
|
|
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
|
-
|
|
1276
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1374
|
+
* @deprecated Use getTokenForSigner() — signerId changes are handled automatically by the per-signer cache.
|
|
1293
1375
|
*/
|
|
1294
1376
|
updateSignerId(signerId) {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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(
|
|
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 ??
|
|
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
|
-
|
|
1784
|
-
|
|
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 ??
|
|
1813
|
-
signerId:
|
|
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,
|