@thesight/sdk 0.1.1 → 0.3.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.
- package/README.md +135 -18
- package/dist/index.cjs +297 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +225 -58
- package/dist/index.d.ts +225 -58
- package/dist/index.js +298 -121
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,16 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
IdlResolver: () =>
|
|
23
|
+
IdlResolver: () => import_core4.IdlResolver,
|
|
24
24
|
InstrumentedConnection: () => InstrumentedConnection,
|
|
25
25
|
SightSpanExporter: () => SightSpanExporter,
|
|
26
|
-
|
|
26
|
+
buildDsn: () => buildDsn,
|
|
27
|
+
initSight: () => initSight,
|
|
28
|
+
parseDsn: () => parseDsn,
|
|
29
|
+
trackSolanaTransaction: () => trackSolanaTransaction
|
|
27
30
|
});
|
|
28
31
|
module.exports = __toCommonJS(index_exports);
|
|
29
32
|
var import_web3 = require("@solana/web3.js");
|
|
30
|
-
var
|
|
31
|
-
var import_core2 = require("@thesight/core");
|
|
33
|
+
var import_api2 = require("@opentelemetry/api");
|
|
32
34
|
var import_core3 = require("@thesight/core");
|
|
35
|
+
var import_core4 = require("@thesight/core");
|
|
33
36
|
|
|
34
37
|
// src/init.ts
|
|
35
38
|
var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
|
|
@@ -39,6 +42,38 @@ var import_semantic_conventions = require("@opentelemetry/semantic-conventions")
|
|
|
39
42
|
|
|
40
43
|
// src/exporter.ts
|
|
41
44
|
var import_core = require("@opentelemetry/core");
|
|
45
|
+
|
|
46
|
+
// src/dsn.ts
|
|
47
|
+
function parseDsn(dsn) {
|
|
48
|
+
let url;
|
|
49
|
+
try {
|
|
50
|
+
url = new URL(dsn);
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid Sight DSN: "${dsn}". Expected format: https://<apiKey>@<host>/ingest \u2014 get your DSN from the Sight dashboard when you create a project.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const apiKey = decodeURIComponent(url.username);
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Invalid Sight DSN: no API key found in "${dsn}". The API key goes in the username position: https://sk_live_xxx@host/ingest`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
url.username = "";
|
|
63
|
+
url.password = "";
|
|
64
|
+
const ingestUrl = url.toString().replace(/\/$/, "");
|
|
65
|
+
return { apiKey, ingestUrl };
|
|
66
|
+
}
|
|
67
|
+
function buildDsn(apiKey, ingestHost) {
|
|
68
|
+
const url = new URL(ingestHost);
|
|
69
|
+
url.username = apiKey;
|
|
70
|
+
if (!url.pathname || url.pathname === "/") {
|
|
71
|
+
url.pathname = "/ingest";
|
|
72
|
+
}
|
|
73
|
+
return url.toString();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/exporter.ts
|
|
42
77
|
var SightSpanExporter = class {
|
|
43
78
|
apiKey;
|
|
44
79
|
ingestUrl;
|
|
@@ -46,8 +81,9 @@ var SightSpanExporter = class {
|
|
|
46
81
|
maxBatchSize;
|
|
47
82
|
shuttingDown = false;
|
|
48
83
|
constructor(config) {
|
|
49
|
-
|
|
50
|
-
this.
|
|
84
|
+
const parsed = parseDsn(config.dsn);
|
|
85
|
+
this.apiKey = parsed.apiKey;
|
|
86
|
+
this.ingestUrl = parsed.ingestUrl;
|
|
51
87
|
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
52
88
|
this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);
|
|
53
89
|
if (!this.fetchImpl) {
|
|
@@ -120,7 +156,7 @@ var SightSpanExporter = class {
|
|
|
120
156
|
if (parentSpanId) out.parentSpanId = parentSpanId;
|
|
121
157
|
if (serviceName) out.serviceName = serviceName;
|
|
122
158
|
copyIfString(attr, "solana.tx.signature", out);
|
|
123
|
-
copyIfEnum(attr, "solana.tx.status", out, ["confirmed", "failed", "timeout"]);
|
|
159
|
+
copyIfEnum(attr, "solana.tx.status", out, ["submitted", "confirmed", "failed", "timeout"]);
|
|
124
160
|
copyIfNumber(attr, "solana.tx.slot", out);
|
|
125
161
|
copyIfNumber(attr, "solana.tx.cu_used", out);
|
|
126
162
|
copyIfNumber(attr, "solana.tx.cu_budget", out);
|
|
@@ -163,10 +199,11 @@ async function safeReadText(res) {
|
|
|
163
199
|
}
|
|
164
200
|
|
|
165
201
|
// src/init.ts
|
|
166
|
-
var DEFAULT_INGEST_URL = "https://ingest.thesight.dev/ingest";
|
|
167
202
|
function initSight(config) {
|
|
168
|
-
if (!config.
|
|
169
|
-
throw new Error(
|
|
203
|
+
if (!config.dsn) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"initSight: `dsn` is required. Get your DSN from the Sight dashboard: https://thesight.dev"
|
|
206
|
+
);
|
|
170
207
|
}
|
|
171
208
|
if (!config.serviceName) {
|
|
172
209
|
throw new Error("initSight: `serviceName` is required");
|
|
@@ -177,8 +214,7 @@ function initSight(config) {
|
|
|
177
214
|
});
|
|
178
215
|
const provider = new import_sdk_trace_node.NodeTracerProvider({ resource });
|
|
179
216
|
const exporter = new SightSpanExporter({
|
|
180
|
-
|
|
181
|
-
ingestUrl: config.ingestUrl ?? DEFAULT_INGEST_URL,
|
|
217
|
+
dsn: config.dsn,
|
|
182
218
|
fetchImpl: config.fetchImpl,
|
|
183
219
|
maxBatchSize: config.maxBatchSize
|
|
184
220
|
});
|
|
@@ -197,6 +233,98 @@ function initSight(config) {
|
|
|
197
233
|
};
|
|
198
234
|
}
|
|
199
235
|
|
|
236
|
+
// src/track.ts
|
|
237
|
+
var import_api = require("@opentelemetry/api");
|
|
238
|
+
var import_core2 = require("@thesight/core");
|
|
239
|
+
async function trackSolanaTransaction(opts) {
|
|
240
|
+
const tracerName = opts.serviceName ?? "@thesight/sdk";
|
|
241
|
+
const tracer = opts.tracer ?? import_api.trace.getTracer(tracerName);
|
|
242
|
+
const span = tracer.startSpan("solana.trackTransaction", {}, import_api.context.active());
|
|
243
|
+
span.setAttribute("solana.tx.signature", opts.signature);
|
|
244
|
+
const start = Date.now();
|
|
245
|
+
const deadline = start + (opts.timeoutMs ?? 3e4);
|
|
246
|
+
const commitment = opts.commitment ?? "confirmed";
|
|
247
|
+
const basePoll = opts.pollIntervalMs ?? 500;
|
|
248
|
+
const resolver = opts.idlResolver ?? buildResolverFromIdls(opts.idls);
|
|
249
|
+
try {
|
|
250
|
+
const txDetails = await pollGetTransaction(opts.connection, opts.signature, commitment, deadline, basePoll);
|
|
251
|
+
if (!txDetails) {
|
|
252
|
+
span.setAttribute("solana.tx.status", "timeout");
|
|
253
|
+
span.setAttribute("solana.tx.enrichment_ms", Date.now() - start);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
span.setAttribute("solana.tx.status", txDetails.meta?.err ? "failed" : "confirmed");
|
|
257
|
+
span.setAttribute("solana.tx.slot", txDetails.slot);
|
|
258
|
+
const fee = txDetails.meta?.fee;
|
|
259
|
+
if (fee !== void 0) span.setAttribute("solana.tx.fee_lamports", fee);
|
|
260
|
+
const cuUsed = txDetails.meta?.computeUnitsConsumed;
|
|
261
|
+
if (cuUsed !== void 0 && cuUsed !== null) {
|
|
262
|
+
span.setAttribute("solana.tx.cu_used", Number(cuUsed));
|
|
263
|
+
span.setAttribute("solana.tx.cu_budget", 2e5);
|
|
264
|
+
span.setAttribute(
|
|
265
|
+
"solana.tx.cu_utilization",
|
|
266
|
+
parseFloat((Number(cuUsed) / 2e5 * 100).toFixed(1))
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const logs = txDetails.meta?.logMessages ?? [];
|
|
270
|
+
if (logs.length > 0) {
|
|
271
|
+
const { cpiTree } = (0, import_core2.parseLogs)({ logs });
|
|
272
|
+
await (0, import_core2.enrichTree)(cpiTree, resolver);
|
|
273
|
+
for (const attr of (0, import_core2.flatAttributions)(cpiTree)) {
|
|
274
|
+
span.addEvent("cpi.invoke", {
|
|
275
|
+
"cpi.program": attr.programName ?? attr.programId,
|
|
276
|
+
"cpi.instruction": attr.instructionName ?? "unknown",
|
|
277
|
+
"cpi.depth": attr.depth,
|
|
278
|
+
"cpi.cu_consumed": attr.cuConsumed,
|
|
279
|
+
"cpi.cu_self": attr.cuSelf,
|
|
280
|
+
"cpi.percentage": parseFloat(attr.percentage.toFixed(2))
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const root = cpiTree.roots[0];
|
|
284
|
+
if (root) {
|
|
285
|
+
if (root.programName) span.setAttribute("solana.tx.program", root.programName);
|
|
286
|
+
if (root.instructionName) span.setAttribute("solana.tx.instruction", root.instructionName);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
span.setAttribute("solana.tx.enrichment_ms", Date.now() - start);
|
|
290
|
+
if (txDetails.meta?.err) {
|
|
291
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
292
|
+
} else {
|
|
293
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
span.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
297
|
+
span.setStatus({
|
|
298
|
+
code: import_api.SpanStatusCode.ERROR,
|
|
299
|
+
message: err instanceof Error ? err.message : String(err)
|
|
300
|
+
});
|
|
301
|
+
} finally {
|
|
302
|
+
span.end();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function buildResolverFromIdls(idls) {
|
|
306
|
+
const resolver = new import_core2.IdlResolver();
|
|
307
|
+
if (idls) resolver.registerMany(idls);
|
|
308
|
+
return resolver;
|
|
309
|
+
}
|
|
310
|
+
async function pollGetTransaction(connection, signature, commitment, deadline, basePollMs) {
|
|
311
|
+
let attempt = 0;
|
|
312
|
+
while (Date.now() < deadline) {
|
|
313
|
+
try {
|
|
314
|
+
const tx = await connection.getTransaction(signature, {
|
|
315
|
+
commitment,
|
|
316
|
+
maxSupportedTransactionVersion: 0
|
|
317
|
+
});
|
|
318
|
+
if (tx) return tx;
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
attempt++;
|
|
322
|
+
const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2e3);
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
200
328
|
// src/index.ts
|
|
201
329
|
var InstrumentedConnection = class extends import_web3.Connection {
|
|
202
330
|
sightConfig;
|
|
@@ -209,16 +337,29 @@ var InstrumentedConnection = class extends import_web3.Connection {
|
|
|
209
337
|
skipIdlResolution,
|
|
210
338
|
idls,
|
|
211
339
|
allowOnChainIdlFetch,
|
|
340
|
+
enrichmentTimeoutMs,
|
|
341
|
+
enrichmentPollIntervalMs,
|
|
342
|
+
disableAutoSpan,
|
|
343
|
+
commitment,
|
|
212
344
|
...connectionConfig
|
|
213
345
|
} = config ?? {};
|
|
214
346
|
super(endpoint, connectionConfig);
|
|
215
|
-
this.sightConfig = {
|
|
216
|
-
|
|
347
|
+
this.sightConfig = {
|
|
348
|
+
tracer,
|
|
349
|
+
idlRpcEndpoint,
|
|
350
|
+
skipIdlResolution,
|
|
351
|
+
allowOnChainIdlFetch,
|
|
352
|
+
enrichmentTimeoutMs: enrichmentTimeoutMs ?? 3e4,
|
|
353
|
+
enrichmentPollIntervalMs: enrichmentPollIntervalMs ?? 500,
|
|
354
|
+
disableAutoSpan,
|
|
355
|
+
commitment
|
|
356
|
+
};
|
|
357
|
+
this.idlResolver = new import_core3.IdlResolver({
|
|
217
358
|
rpcEndpoint: idlRpcEndpoint ?? endpoint,
|
|
218
359
|
allowOnChainFetch: allowOnChainIdlFetch ?? false
|
|
219
360
|
});
|
|
220
361
|
if (idls) this.idlResolver.registerMany(idls);
|
|
221
|
-
this.tracer = tracer ??
|
|
362
|
+
this.tracer = tracer ?? import_api2.trace.getTracer("@thesight/sdk");
|
|
222
363
|
}
|
|
223
364
|
/**
|
|
224
365
|
* Register an IDL for a single program. Convenience forwarder for the
|
|
@@ -235,132 +376,167 @@ var InstrumentedConnection = class extends import_web3.Connection {
|
|
|
235
376
|
registerIdls(idls) {
|
|
236
377
|
this.idlResolver.registerMany(idls);
|
|
237
378
|
}
|
|
379
|
+
// ─── Overridden method ────────────────────────────────────────────────────
|
|
238
380
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
381
|
+
* Overrides the parent `Connection.sendRawTransaction` so every submit —
|
|
382
|
+
* including those made by Anchor's `provider.sendAndConfirm`, web3.js's
|
|
383
|
+
* top-level `sendAndConfirmTransaction`, and wallet adapter flows — is
|
|
384
|
+
* observed by an OTel span automatically.
|
|
385
|
+
*
|
|
386
|
+
* The span is a child of whatever OTel context is active when the call
|
|
387
|
+
* fires, so app-level parent spans (HTTP handlers, job runs, wallet flow
|
|
388
|
+
* spans from another SDK) nest the Sight span correctly.
|
|
241
389
|
*/
|
|
242
|
-
async
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
390
|
+
async sendRawTransaction(rawTransaction, options) {
|
|
391
|
+
if (this.sightConfig.disableAutoSpan) {
|
|
392
|
+
return super.sendRawTransaction(rawTransaction, options);
|
|
393
|
+
}
|
|
394
|
+
const span = this.tracer.startSpan("solana.sendRawTransaction", {}, import_api2.context.active());
|
|
246
395
|
const submitStart = Date.now();
|
|
396
|
+
let signature;
|
|
247
397
|
try {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{ skipPreflight: false, ...options }
|
|
251
|
-
);
|
|
252
|
-
span.setAttribute("solana.tx.signature", signature);
|
|
398
|
+
signature = await super.sendRawTransaction(rawTransaction, options);
|
|
399
|
+
} catch (err) {
|
|
253
400
|
const submitMs = Date.now() - submitStart;
|
|
254
401
|
span.setAttribute("solana.tx.submit_ms", submitMs);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
span.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (decoded.errorMsg) {
|
|
291
|
-
span.setAttribute("solana.tx.error_msg", decoded.errorMsg);
|
|
292
|
-
}
|
|
293
|
-
span.end();
|
|
294
|
-
return { signature, error: decoded };
|
|
295
|
-
}
|
|
402
|
+
span.setAttribute("solana.tx.status", "failed");
|
|
403
|
+
span.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
404
|
+
span.setStatus({
|
|
405
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
406
|
+
message: err instanceof Error ? err.message : String(err)
|
|
407
|
+
});
|
|
408
|
+
span.end();
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
span.setAttribute("solana.tx.signature", signature);
|
|
412
|
+
span.setAttribute("solana.tx.submit_ms", Date.now() - submitStart);
|
|
413
|
+
span.setAttribute("solana.tx.status", "submitted");
|
|
414
|
+
void this.enrichSpanInBackground(span, signature, submitStart);
|
|
415
|
+
return signature;
|
|
416
|
+
}
|
|
417
|
+
// ─── Background enrichment ───────────────────────────────────────────────
|
|
418
|
+
/**
|
|
419
|
+
* Poll `getTransaction` until the on-chain record is available, then
|
|
420
|
+
* enrich the span with CU attribution, program names, decoded errors,
|
|
421
|
+
* and per-CPI events.
|
|
422
|
+
*
|
|
423
|
+
* This runs async and is never awaited by callers of `sendRawTransaction`.
|
|
424
|
+
* Failures inside this method attach to the span via recordException
|
|
425
|
+
* rather than throwing.
|
|
426
|
+
*/
|
|
427
|
+
async enrichSpanInBackground(span, signature, submitStart) {
|
|
428
|
+
const enrichStart = Date.now();
|
|
429
|
+
const deadline = enrichStart + (this.sightConfig.enrichmentTimeoutMs ?? 3e4);
|
|
430
|
+
const commitment = this.sightConfig.commitment ?? "confirmed";
|
|
431
|
+
const basePollMs = this.sightConfig.enrichmentPollIntervalMs ?? 500;
|
|
432
|
+
try {
|
|
433
|
+
const txDetails = await this.pollForTransaction(signature, commitment, deadline, basePollMs);
|
|
434
|
+
if (!txDetails) {
|
|
435
|
+
span.setAttribute("solana.tx.status", "timeout");
|
|
436
|
+
span.setAttribute("solana.tx.enrichment_ms", Date.now() - submitStart);
|
|
296
437
|
span.end();
|
|
297
|
-
return
|
|
438
|
+
return;
|
|
298
439
|
}
|
|
299
|
-
|
|
440
|
+
this.attachTxDetailsToSpan(span, txDetails);
|
|
300
441
|
if (!this.sightConfig.skipIdlResolution) {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
maxSupportedTransactionVersion: 0
|
|
305
|
-
});
|
|
306
|
-
if (txDetails) {
|
|
307
|
-
const slot = txDetails.slot;
|
|
308
|
-
const fee = txDetails.meta?.fee;
|
|
309
|
-
const logs = txDetails.meta?.logMessages ?? [];
|
|
310
|
-
const cuUsed = txDetails.meta?.computeUnitsConsumed;
|
|
311
|
-
span.setAttribute("solana.tx.slot", slot);
|
|
312
|
-
if (fee !== void 0) span.setAttribute("solana.tx.fee_lamports", fee);
|
|
313
|
-
if (cuUsed !== void 0) {
|
|
314
|
-
span.setAttribute("solana.tx.cu_used", cuUsed);
|
|
315
|
-
span.setAttribute("solana.tx.cu_budget", 2e5);
|
|
316
|
-
span.setAttribute("solana.tx.cu_utilization", parseFloat((cuUsed / 2e5 * 100).toFixed(1)));
|
|
317
|
-
}
|
|
318
|
-
const { cpiTree } = (0, import_core2.parseLogs)({ logs });
|
|
319
|
-
if (!this.sightConfig.skipIdlResolution) {
|
|
320
|
-
await (0, import_core2.enrichTree)(cpiTree, this.idlResolver);
|
|
321
|
-
}
|
|
322
|
-
const attributions = (0, import_core2.flatAttributions)(cpiTree);
|
|
323
|
-
for (const attr of attributions) {
|
|
324
|
-
span.addEvent("cpi.invoke", {
|
|
325
|
-
"cpi.program": attr.programName ?? attr.programId,
|
|
326
|
-
"cpi.instruction": attr.instructionName ?? "unknown",
|
|
327
|
-
"cpi.depth": attr.depth,
|
|
328
|
-
"cpi.cu_consumed": attr.cuConsumed,
|
|
329
|
-
"cpi.cu_self": attr.cuSelf,
|
|
330
|
-
"cpi.percentage": parseFloat(attr.percentage.toFixed(2))
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
const root = cpiTree.roots[0];
|
|
334
|
-
if (root) {
|
|
335
|
-
if (root.programName) span.setAttribute("solana.tx.program", root.programName);
|
|
336
|
-
if (root.instructionName) span.setAttribute("solana.tx.instruction", root.instructionName);
|
|
337
|
-
}
|
|
338
|
-
span.setAttribute("solana.tx.enrichment_ms", Date.now() - enrichStart);
|
|
339
|
-
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
340
|
-
span.end();
|
|
341
|
-
return { signature, cpiTree };
|
|
442
|
+
const logs = txDetails.meta?.logMessages ?? [];
|
|
443
|
+
if (logs.length > 0) {
|
|
444
|
+
await this.attachParsedLogsToSpan(span, logs);
|
|
342
445
|
}
|
|
343
446
|
}
|
|
344
|
-
span.
|
|
345
|
-
|
|
346
|
-
|
|
447
|
+
span.setAttribute("solana.tx.enrichment_ms", Date.now() - submitStart);
|
|
448
|
+
if (txDetails.meta?.err) {
|
|
449
|
+
span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
|
|
450
|
+
} else {
|
|
451
|
+
span.setStatus({ code: import_api2.SpanStatusCode.OK });
|
|
452
|
+
}
|
|
347
453
|
} catch (err) {
|
|
348
|
-
span.
|
|
349
|
-
span.setAttribute(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
454
|
+
span.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
455
|
+
span.setAttribute(
|
|
456
|
+
"solana.tx.enrichment_error",
|
|
457
|
+
err instanceof Error ? err.message : String(err)
|
|
458
|
+
);
|
|
459
|
+
} finally {
|
|
354
460
|
span.end();
|
|
355
|
-
throw err;
|
|
356
461
|
}
|
|
357
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Poll `getTransaction(signature)` until either the on-chain record is
|
|
465
|
+
* returned or the deadline passes. Exponential backoff (1.5x) capped at
|
|
466
|
+
* 2 seconds to balance responsiveness against RPC load.
|
|
467
|
+
*/
|
|
468
|
+
async pollForTransaction(signature, commitment, deadline, basePollMs) {
|
|
469
|
+
let attempt = 0;
|
|
470
|
+
while (Date.now() < deadline) {
|
|
471
|
+
try {
|
|
472
|
+
const tx = await super.getTransaction(signature, {
|
|
473
|
+
commitment,
|
|
474
|
+
maxSupportedTransactionVersion: 0
|
|
475
|
+
});
|
|
476
|
+
if (tx) return tx;
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
attempt++;
|
|
480
|
+
const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2e3);
|
|
481
|
+
await sleep(waitMs);
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
/** Attach the flat fields (slot, fee, CU, status) from a tx response to a span. */
|
|
486
|
+
attachTxDetailsToSpan(span, txDetails) {
|
|
487
|
+
span.setAttribute("solana.tx.status", txDetails.meta?.err ? "failed" : "confirmed");
|
|
488
|
+
span.setAttribute("solana.tx.slot", txDetails.slot);
|
|
489
|
+
const fee = txDetails.meta?.fee;
|
|
490
|
+
if (fee !== void 0) span.setAttribute("solana.tx.fee_lamports", fee);
|
|
491
|
+
const cuUsed = txDetails.meta?.computeUnitsConsumed;
|
|
492
|
+
if (cuUsed !== void 0 && cuUsed !== null) {
|
|
493
|
+
span.setAttribute("solana.tx.cu_used", Number(cuUsed));
|
|
494
|
+
span.setAttribute("solana.tx.cu_budget", 2e5);
|
|
495
|
+
span.setAttribute(
|
|
496
|
+
"solana.tx.cu_utilization",
|
|
497
|
+
parseFloat((Number(cuUsed) / 2e5 * 100).toFixed(1))
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Parse program logs into a CPI tree, enrich with registered IDLs, and
|
|
503
|
+
* emit one `cpi.invoke` event per invocation onto the span. Root
|
|
504
|
+
* program/instruction names are copied onto span attributes so dashboards
|
|
505
|
+
* can filter by them without walking events.
|
|
506
|
+
*/
|
|
507
|
+
async attachParsedLogsToSpan(span, logs) {
|
|
508
|
+
const { cpiTree } = (0, import_core3.parseLogs)({ logs });
|
|
509
|
+
await (0, import_core3.enrichTree)(cpiTree, this.idlResolver);
|
|
510
|
+
const attributions = (0, import_core3.flatAttributions)(cpiTree);
|
|
511
|
+
for (const attr of attributions) {
|
|
512
|
+
span.addEvent("cpi.invoke", {
|
|
513
|
+
"cpi.program": attr.programName ?? attr.programId,
|
|
514
|
+
"cpi.instruction": attr.instructionName ?? "unknown",
|
|
515
|
+
"cpi.depth": attr.depth,
|
|
516
|
+
"cpi.cu_consumed": attr.cuConsumed,
|
|
517
|
+
"cpi.cu_self": attr.cuSelf,
|
|
518
|
+
"cpi.percentage": parseFloat(attr.percentage.toFixed(2))
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const root = cpiTree.roots[0];
|
|
522
|
+
if (root) {
|
|
523
|
+
if (root.programName) span.setAttribute("solana.tx.program", root.programName);
|
|
524
|
+
if (root.instructionName) span.setAttribute("solana.tx.instruction", root.instructionName);
|
|
525
|
+
}
|
|
526
|
+
return cpiTree;
|
|
527
|
+
}
|
|
358
528
|
};
|
|
529
|
+
function sleep(ms) {
|
|
530
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
531
|
+
}
|
|
359
532
|
// Annotate the CommonJS export names for ESM import in node:
|
|
360
533
|
0 && (module.exports = {
|
|
361
534
|
IdlResolver,
|
|
362
535
|
InstrumentedConnection,
|
|
363
536
|
SightSpanExporter,
|
|
364
|
-
|
|
537
|
+
buildDsn,
|
|
538
|
+
initSight,
|
|
539
|
+
parseDsn,
|
|
540
|
+
trackSolanaTransaction
|
|
365
541
|
});
|
|
366
542
|
//# sourceMappingURL=index.cjs.map
|