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