@thesight/sdk 0.2.0 → 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/dist/index.cjs CHANGED
@@ -23,7 +23,9 @@ __export(index_exports, {
23
23
  IdlResolver: () => import_core4.IdlResolver,
24
24
  InstrumentedConnection: () => InstrumentedConnection,
25
25
  SightSpanExporter: () => SightSpanExporter,
26
+ buildDsn: () => buildDsn,
26
27
  initSight: () => initSight,
28
+ parseDsn: () => parseDsn,
27
29
  trackSolanaTransaction: () => trackSolanaTransaction
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
@@ -40,6 +42,38 @@ var import_semantic_conventions = require("@opentelemetry/semantic-conventions")
40
42
 
41
43
  // src/exporter.ts
42
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
43
77
  var SightSpanExporter = class {
44
78
  apiKey;
45
79
  ingestUrl;
@@ -47,8 +81,9 @@ var SightSpanExporter = class {
47
81
  maxBatchSize;
48
82
  shuttingDown = false;
49
83
  constructor(config) {
50
- this.apiKey = config.apiKey;
51
- this.ingestUrl = config.ingestUrl;
84
+ const parsed = parseDsn(config.dsn);
85
+ this.apiKey = parsed.apiKey;
86
+ this.ingestUrl = parsed.ingestUrl;
52
87
  this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
53
88
  this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);
54
89
  if (!this.fetchImpl) {
@@ -121,7 +156,7 @@ var SightSpanExporter = class {
121
156
  if (parentSpanId) out.parentSpanId = parentSpanId;
122
157
  if (serviceName) out.serviceName = serviceName;
123
158
  copyIfString(attr, "solana.tx.signature", out);
124
- copyIfEnum(attr, "solana.tx.status", out, ["confirmed", "failed", "timeout"]);
159
+ copyIfEnum(attr, "solana.tx.status", out, ["submitted", "confirmed", "failed", "timeout"]);
125
160
  copyIfNumber(attr, "solana.tx.slot", out);
126
161
  copyIfNumber(attr, "solana.tx.cu_used", out);
127
162
  copyIfNumber(attr, "solana.tx.cu_budget", out);
@@ -164,10 +199,11 @@ async function safeReadText(res) {
164
199
  }
165
200
 
166
201
  // src/init.ts
167
- var DEFAULT_INGEST_URL = "https://ingest.thesight.dev/ingest";
168
202
  function initSight(config) {
169
- if (!config.apiKey) {
170
- throw new Error("initSight: `apiKey` is required");
203
+ if (!config.dsn) {
204
+ throw new Error(
205
+ "initSight: `dsn` is required. Get your DSN from the Sight dashboard: https://thesight.dev"
206
+ );
171
207
  }
172
208
  if (!config.serviceName) {
173
209
  throw new Error("initSight: `serviceName` is required");
@@ -178,8 +214,7 @@ function initSight(config) {
178
214
  });
179
215
  const provider = new import_sdk_trace_node.NodeTracerProvider({ resource });
180
216
  const exporter = new SightSpanExporter({
181
- apiKey: config.apiKey,
182
- ingestUrl: config.ingestUrl ?? DEFAULT_INGEST_URL,
217
+ dsn: config.dsn,
183
218
  fetchImpl: config.fetchImpl,
184
219
  maxBatchSize: config.maxBatchSize
185
220
  });
@@ -499,7 +534,9 @@ function sleep(ms) {
499
534
  IdlResolver,
500
535
  InstrumentedConnection,
501
536
  SightSpanExporter,
537
+ buildDsn,
502
538
  initSight,
539
+ parseDsn,
503
540
  trackSolanaTransaction
504
541
  });
505
542
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/exporter.ts","../src/track.ts"],"sourcesContent":["import {\n Connection,\n type ConnectionConfig,\n type SendOptions,\n type Commitment,\n type Finality,\n type TransactionSignature,\n type VersionedTransactionResponse,\n} from '@solana/web3.js';\nimport {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl, CpiTree } from '@thesight/core';\n\n// ─── Config ────────────────────────────────────────────────────────────────\n\nexport interface SightConfig {\n /** OTel tracer. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Override RPC endpoint for IDL fetching (defaults to same as connection) */\n idlRpcEndpoint?: string;\n\n /**\n * Commitment used when fetching confirmed tx details for enrichment.\n * Defaults to 'confirmed'.\n */\n commitment?: Commitment;\n\n /**\n * Skip IDL resolution entirely. Spans will still emit with signature and\n * timing attributes, but program names, instruction names, CPI trees, and\n * decoded errors will all be omitted. Useful when you want minimal\n * overhead and don't care about the richer observability.\n */\n skipIdlResolution?: boolean;\n\n /**\n * Pre-register IDLs at construction time. Keys are program IDs, values are\n * full Anchor IDL objects. Anchor users can pass `program.idl` directly off\n * their `Program` instance — zero setup friction.\n */\n idls?: Record<string, AnchorIdl>;\n\n /**\n * Opt into reading Anchor IDL accounts from the on-chain PDA as a fallback\n * when a program is not explicitly registered. Defaults to **false** —\n * Sight's model is that IDLs are explicitly provided by the application,\n * not silently guessed from chain.\n */\n allowOnChainIdlFetch?: boolean;\n\n /**\n * Max wall-time the background enrichment task will wait for a\n * transaction to show up in `getTransaction`. After this expires the\n * span is ended with `solana.tx.status = 'timeout'`. Default 30000 (30s).\n */\n enrichmentTimeoutMs?: number;\n\n /**\n * How often the enrichment task polls `getTransaction`. Each retry backs\n * off by 1.5x up to a cap. Default 500ms base.\n */\n enrichmentPollIntervalMs?: number;\n\n /**\n * Disable automatic span creation in the overridden `sendRawTransaction`.\n * When true, InstrumentedConnection behaves like a plain Connection. You\n * probably don't want this — it's here as an escape hatch for tests and\n * rare cases where you need the class hierarchy but not the tracing.\n */\n disableAutoSpan?: boolean;\n}\n\n// ─── Span attribute shape (documented, for downstream consumers) ──────────\n\nexport interface SightSpanAttributes {\n 'solana.tx.signature': string;\n 'solana.tx.status': 'submitted' | 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.fee_lamports'?: number;\n 'solana.tx.submit_ms': number;\n 'solana.tx.enrichment_ms'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.error_msg'?: string;\n}\n\n// ─── InstrumentedConnection ────────────────────────────────────────────────\n\n/**\n * Drop-in replacement for `@solana/web3.js`'s `Connection` that emits an\n * OpenTelemetry span for **every** call to `sendRawTransaction` —\n * regardless of whether the caller is your own code, an Anchor provider's\n * `sendAndConfirm`, `@solana/web3.js`'s top-level `sendAndConfirmTransaction`,\n * a wallet adapter, or anything else.\n *\n * Internally it overrides the parent class's `sendRawTransaction`, so the\n * span covers the same surface web3.js already mediates. No mutation of\n * the transaction bytes — we call `super.sendRawTransaction(rawTx, opts)`\n * verbatim and observe the signature it returns.\n *\n * After the signature is returned (synchronously from the caller's point\n * of view), a background task polls `getTransaction` until the on-chain\n * record is available, parses the program logs via `@thesight/core`, and\n * enriches the span with CU attribution, per-CPI events, program + instruction\n * names, and decoded error details. The background task never blocks the\n * caller — if it fails or times out, the span ends with status=timeout and\n * whatever partial data was collected.\n *\n * Usage:\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({ apiKey, serviceName: 'swap-bot' });\n *\n * // Anywhere you currently construct a Connection, construct this instead:\n * const connection = new InstrumentedConnection(rpcUrl, {\n * commitment: 'confirmed',\n * });\n *\n * // All of the following now emit spans automatically:\n * await connection.sendRawTransaction(tx.serialize());\n * await sendAndConfirmTransaction(connection, tx, signers); // web3.js\n * await program.methods.foo().rpc(); // Anchor\n * await wallet.sendTransaction(tx, connection); // wallet adapter\n */\nexport class InstrumentedConnection extends Connection {\n private sightConfig: SightConfig;\n private idlResolver: IdlResolver;\n private tracer: Tracer;\n\n constructor(endpoint: string, config?: ConnectionConfig & SightConfig) {\n const {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n idls,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs,\n enrichmentPollIntervalMs,\n disableAutoSpan,\n commitment,\n ...connectionConfig\n } = config ?? {};\n\n super(endpoint, connectionConfig);\n\n this.sightConfig = {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs: enrichmentTimeoutMs ?? 30_000,\n enrichmentPollIntervalMs: enrichmentPollIntervalMs ?? 500,\n disableAutoSpan,\n commitment,\n };\n this.idlResolver = new IdlResolver({\n rpcEndpoint: idlRpcEndpoint ?? endpoint,\n allowOnChainFetch: allowOnChainIdlFetch ?? false,\n });\n if (idls) this.idlResolver.registerMany(idls);\n this.tracer = tracer ?? trace.getTracer('@thesight/sdk');\n }\n\n /**\n * Register an IDL for a single program. Convenience forwarder for the\n * underlying resolver. Anchor users typically call this right after\n * constructing a `Program`:\n *\n * const program = new Program(idl, programId, provider);\n * connection.registerIdl(program.programId.toBase58(), program.idl);\n */\n registerIdl(programId: string, idl: AnchorIdl): void {\n this.idlResolver.register(programId, idl);\n }\n\n /** Bulk-register multiple IDLs. */\n registerIdls(idls: Record<string, AnchorIdl>): void {\n this.idlResolver.registerMany(idls);\n }\n\n // ─── Overridden method ────────────────────────────────────────────────────\n\n /**\n * Overrides the parent `Connection.sendRawTransaction` so every submit —\n * including those made by Anchor's `provider.sendAndConfirm`, web3.js's\n * top-level `sendAndConfirmTransaction`, and wallet adapter flows — is\n * observed by an OTel span automatically.\n *\n * The span is a child of whatever OTel context is active when the call\n * fires, so app-level parent spans (HTTP handlers, job runs, wallet flow\n * spans from another SDK) nest the Sight span correctly.\n */\n override async sendRawTransaction(\n rawTransaction: Buffer | Uint8Array | Array<number>,\n options?: SendOptions,\n ): Promise<TransactionSignature> {\n if (this.sightConfig.disableAutoSpan) {\n return super.sendRawTransaction(rawTransaction, options);\n }\n\n const span = this.tracer.startSpan('solana.sendRawTransaction', {}, context.active());\n const submitStart = Date.now();\n\n let signature: TransactionSignature;\n try {\n signature = await super.sendRawTransaction(rawTransaction, options);\n } catch (err) {\n const submitMs = Date.now() - submitStart;\n span.setAttribute('solana.tx.submit_ms', submitMs);\n span.setAttribute('solana.tx.status', 'failed');\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n span.end();\n throw err;\n }\n\n span.setAttribute('solana.tx.signature', signature);\n span.setAttribute('solana.tx.submit_ms', Date.now() - submitStart);\n span.setAttribute('solana.tx.status', 'submitted');\n\n // Fire-and-forget background enrichment. The returned Promise is\n // intentionally discarded — we don't want to couple the caller's\n // transaction-send latency to our observation machinery. Any enrichment\n // error is caught inside enrichSpanInBackground and attached to the\n // span, not bubbled up.\n void this.enrichSpanInBackground(span, signature, submitStart);\n\n return signature;\n }\n\n // ─── Background enrichment ───────────────────────────────────────────────\n\n /**\n * Poll `getTransaction` until the on-chain record is available, then\n * enrich the span with CU attribution, program names, decoded errors,\n * and per-CPI events.\n *\n * This runs async and is never awaited by callers of `sendRawTransaction`.\n * Failures inside this method attach to the span via recordException\n * rather than throwing.\n */\n private async enrichSpanInBackground(\n span: Span,\n signature: TransactionSignature,\n submitStart: number,\n ): Promise<void> {\n const enrichStart = Date.now();\n const deadline = enrichStart + (this.sightConfig.enrichmentTimeoutMs ?? 30_000);\n const commitment = (this.sightConfig.commitment ?? 'confirmed') as Finality;\n const basePollMs = this.sightConfig.enrichmentPollIntervalMs ?? 500;\n\n try {\n const txDetails = await this.pollForTransaction(signature, commitment, deadline, basePollMs);\n\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n span.end();\n return;\n }\n\n this.attachTxDetailsToSpan(span, txDetails);\n\n if (!this.sightConfig.skipIdlResolution) {\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n await this.attachParsedLogsToSpan(span, logs);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setAttribute(\n 'solana.tx.enrichment_error',\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n span.end();\n }\n }\n\n /**\n * Poll `getTransaction(signature)` until either the on-chain record is\n * returned or the deadline passes. Exponential backoff (1.5x) capped at\n * 2 seconds to balance responsiveness against RPC load.\n */\n private async pollForTransaction(\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n ): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await super.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff. Transient RPC errors are common\n // during the seconds immediately after submission.\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await sleep(waitMs);\n }\n return null;\n }\n\n /** Attach the flat fields (slot, fee, CU, status) from a tx response to a span. */\n private attachTxDetailsToSpan(span: Span, txDetails: VersionedTransactionResponse): void {\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n }\n\n /**\n * Parse program logs into a CPI tree, enrich with registered IDLs, and\n * emit one `cpi.invoke` event per invocation onto the span. Root\n * program/instruction names are copied onto span attributes so dashboards\n * can filter by them without walking events.\n */\n private async attachParsedLogsToSpan(span: Span, logs: string[]): Promise<CpiTree | undefined> {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, this.idlResolver);\n\n const attributions = flatAttributions(cpiTree);\n for (const attr of attributions) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n\n return cpiTree;\n }\n}\n\n// ─── Utilities ────────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ─── Re-exports ────────────────────────────────────────────────────────────\n\nexport { IdlResolver } from '@thesight/core';\nexport type {\n CpiTree,\n CuAttribution,\n FlamegraphItem,\n DecodedError,\n AnchorIdl,\n} from '@thesight/core';\n\n// ─── Tracing initialization ──────────────────────────────────────────────────\n\nexport { initSight } from './init.js';\nexport type { InitSightConfig, SightTracerHandle } from './init.js';\nexport { SightSpanExporter } from './exporter.js';\nexport type { SightExporterConfig } from './exporter.js';\n\n// ─── Non-wrapping observation helper ─────────────────────────────────────\n\nexport { trackSolanaTransaction } from './track.js';\nexport type { TrackTransactionOptions } from './track.js';\n","import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { Resource } from '@opentelemetry/resources';\nimport { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { SightSpanExporter } from './exporter.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface InitSightConfig {\n /** Project API key (`sk_live_...`) from the Sight dashboard. */\n apiKey: string;\n\n /**\n * Human-readable service name that shows up on every span. Pick something\n * that identifies this process — `'swap-bot'`, `'market-maker'`,\n * `'trade-settler'`, etc.\n */\n serviceName: string;\n\n /** Optional version string for the service. Useful for correlating spans with a deploy. */\n serviceVersion?: string;\n\n /**\n * Full ingest URL. Defaults to the hosted Sight ingest. Override for\n * local development (e.g. `http://localhost:3001/ingest`) or self-hosted\n * deployments.\n */\n ingestUrl?: string;\n\n /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */\n batchDelayMs?: number;\n\n /** Max spans per HTTP POST. Clamped to 100 by the exporter. */\n maxBatchSize?: number;\n\n /** Inject a fetch implementation for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface SightTracerHandle {\n provider: NodeTracerProvider;\n /** Flush any pending spans and release the batch processor. */\n shutdown: () => Promise<void>;\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\nconst DEFAULT_INGEST_URL = 'https://ingest.thesight.dev/ingest';\n\n/**\n * Initialize Sight tracing. Call this **once** near the top of your\n * process entry point — typically in the same file that boots your\n * HTTP server or worker.\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({\n * apiKey: process.env.SIGHT_API_KEY!,\n * serviceName: 'swap-bot',\n * ingestUrl: process.env.SIGHT_INGEST_URL, // optional, defaults to prod\n * });\n *\n * const connection = new InstrumentedConnection(process.env.RPC_URL!);\n * const { signature } = await connection.sendAndConfirmInstrumented(tx);\n *\n * Registers a NodeTracerProvider as the global OTel tracer, so any call to\n * `trace.getTracer(...)` (including the ones inside `InstrumentedConnection`)\n * routes spans through the Sight exporter. Context propagation uses the\n * Node async hooks manager so spans nest correctly across await boundaries.\n *\n * Returns a handle with a `shutdown()` method. Call it at graceful shutdown\n * time so pending spans are flushed before the process exits:\n *\n * const sight = initSight({ ... });\n * process.on('SIGTERM', async () => {\n * await sight.shutdown();\n * process.exit(0);\n * });\n */\nexport function initSight(config: InitSightConfig): SightTracerHandle {\n if (!config.apiKey) {\n throw new Error('initSight: `apiKey` is required');\n }\n if (!config.serviceName) {\n throw new Error('initSight: `serviceName` is required');\n }\n\n const resource = new Resource({\n [SEMRESATTRS_SERVICE_NAME]: config.serviceName,\n ...(config.serviceVersion\n ? { [SEMRESATTRS_SERVICE_VERSION]: config.serviceVersion }\n : {}),\n });\n\n const provider = new NodeTracerProvider({ resource });\n\n const exporter = new SightSpanExporter({\n apiKey: config.apiKey,\n ingestUrl: config.ingestUrl ?? DEFAULT_INGEST_URL,\n fetchImpl: config.fetchImpl,\n maxBatchSize: config.maxBatchSize,\n });\n\n // BatchSpanProcessor buffers spans and flushes on an interval. This is the\n // right default for production — simple span processor is one HTTP round\n // trip per span, which is fine for tests and dev but nasty at scale.\n const processor = new BatchSpanProcessor(exporter, {\n scheduledDelayMillis: config.batchDelayMs ?? 5_000,\n maxExportBatchSize: config.maxBatchSize ?? 100,\n maxQueueSize: 2048,\n });\n\n provider.addSpanProcessor(processor);\n\n // Register as the global OTel tracer provider. After this call,\n // `trace.getTracer('anything')` returns a tracer from our provider,\n // which routes spans to SightSpanExporter.\n provider.register();\n\n return {\n provider,\n shutdown: async () => {\n await provider.shutdown();\n },\n };\n}\n","import type { SpanAttributes } from '@opentelemetry/api';\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n hrTimeToMilliseconds,\n ExportResultCode,\n type ExportResult,\n} from '@opentelemetry/core';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface SightExporterConfig {\n /** Project API key (`sk_live_...`). Sent as Bearer auth on every POST. */\n apiKey: string;\n /**\n * Full ingest URL including the path (e.g. `https://ingest.thesight.dev/ingest`\n * or `http://localhost:3001/ingest` for local dev).\n */\n ingestUrl: string;\n /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Max spans per POST. Ingest enforces an upper bound of 100. */\n maxBatchSize?: number;\n}\n\n// ─── Exporter ────────────────────────────────────────────────────────────────\n\n/**\n * An OTel `SpanExporter` that translates OTel spans to Sight's custom\n * ingest payload shape and POSTs them to `/ingest`.\n *\n * Each span becomes one object in the `spans` array. Solana-specific\n * attributes flow through as-is (the set that `InstrumentedConnection`\n * already populates — `solana.tx.signature`, `solana.tx.cu_used`,\n * `solana.tx.program`, etc). The exporter intentionally does not attempt\n * to translate span events or links — the ingest schema doesn't have\n * slots for those, and we prefer dropping silently over guessing.\n *\n * The BatchSpanProcessor upstream handles retries, timeouts, and batching;\n * this exporter stays a thin wire-format translator.\n */\nexport class SightSpanExporter implements SpanExporter {\n private readonly apiKey: string;\n private readonly ingestUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly maxBatchSize: number;\n private shuttingDown = false;\n\n constructor(config: SightExporterConfig) {\n this.apiKey = config.apiKey;\n this.ingestUrl = config.ingestUrl;\n this.fetchImpl = config.fetchImpl ?? (globalThis.fetch as typeof fetch);\n // Ingest's IngestPayload schema caps at 100 spans per POST. Allow\n // operators to lower the batch size for tighter latency, but never\n // raise it above the server's limit.\n this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);\n\n if (!this.fetchImpl) {\n throw new Error(\n 'SightSpanExporter: globalThis.fetch is not available. Node >= 18 ships ' +\n 'with fetch built in, or pass `fetchImpl` explicitly.',\n );\n }\n }\n\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n if (this.shuttingDown) {\n resultCallback({ code: ExportResultCode.FAILED, error: new Error('Exporter is shut down') });\n return;\n }\n\n // Split into chunks if the upstream processor handed us a larger\n // batch than ingest will accept. Uncommon with a well-configured\n // BatchSpanProcessor but cheap to handle defensively.\n const chunks: ReadableSpan[][] = [];\n for (let i = 0; i < spans.length; i += this.maxBatchSize) {\n chunks.push(spans.slice(i, i + this.maxBatchSize));\n }\n\n try {\n for (const chunk of chunks) {\n await this.sendChunk(chunk);\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (err) {\n resultCallback({\n code: ExportResultCode.FAILED,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n async shutdown(): Promise<void> {\n this.shuttingDown = true;\n }\n\n async forceFlush(): Promise<void> {\n // The exporter is stateless — BatchSpanProcessor's own forceFlush\n // drains the queue before calling export(). Nothing to do here.\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private async sendChunk(spans: ReadableSpan[]): Promise<void> {\n const payload = {\n spans: spans.map((span) => this.convertSpan(span)),\n };\n\n const response = await this.fetchImpl(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const body = await safeReadText(response);\n throw new Error(\n `Sight ingest returned ${response.status} ${response.statusText}: ${body}`,\n );\n }\n }\n\n private convertSpan(span: ReadableSpan): IngestSpan {\n const attr = span.attributes;\n const resource = span.resource.attributes;\n const serviceName = typeof resource['service.name'] === 'string'\n ? resource['service.name']\n : undefined;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n const endTimeMs = hrTimeToMilliseconds(span.endTime);\n const durationMs = Math.max(0, endTimeMs - startTimeMs);\n\n const out: IngestSpan = {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n spanName: span.name,\n startTimeMs,\n durationMs,\n };\n\n const parentSpanId = (span as { parentSpanId?: string }).parentSpanId;\n if (parentSpanId) out.parentSpanId = parentSpanId;\n if (serviceName) out.serviceName = serviceName;\n\n copyIfString(attr, 'solana.tx.signature', out);\n copyIfEnum(attr, 'solana.tx.status', out, ['confirmed', 'failed', 'timeout']);\n copyIfNumber(attr, 'solana.tx.slot', out);\n copyIfNumber(attr, 'solana.tx.cu_used', out);\n copyIfNumber(attr, 'solana.tx.cu_budget', out);\n copyIfNumber(attr, 'solana.tx.cu_utilization', out);\n copyIfString(attr, 'solana.tx.program', out);\n copyIfString(attr, 'solana.tx.instruction', out);\n copyIfString(attr, 'solana.tx.error', out);\n copyIfNumber(attr, 'solana.tx.error_code', out);\n copyIfString(attr, 'solana.tx.error_program', out);\n copyIfNumber(attr, 'solana.tx.submit_ms', out);\n copyIfNumber(attr, 'solana.tx.confirmation_ms', out);\n copyIfNumber(attr, 'solana.tx.fee_lamports', out);\n\n return out;\n }\n}\n\n// ─── Wire format (narrow, matches ingest's zod schema) ──────────────────────\n\ninterface IngestSpan {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n serviceName?: string;\n spanName: string;\n startTimeMs: number;\n durationMs: number;\n\n 'solana.tx.signature'?: string;\n 'solana.tx.status'?: 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.submit_ms'?: number;\n 'solana.tx.confirmation_ms'?: number;\n 'solana.tx.fee_lamports'?: number;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction copyIfString(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'string') {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfNumber(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfEnum<T extends string>(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n allowed: readonly T[],\n): void {\n const v = attr[key];\n if (typeof v === 'string' && (allowed as readonly string[]).includes(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nasync function safeReadText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '<no body>';\n }\n}\n","import type { Connection, Finality, TransactionSignature, VersionedTransactionResponse } from '@solana/web3.js';\nimport { trace, context, SpanStatusCode, type Tracer } from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl } from '@thesight/core';\n\n// ─── Options ──────────────────────────────────────────────────────────────\n\nexport interface TrackTransactionOptions {\n /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */\n signature: TransactionSignature;\n\n /**\n * Any `@solana/web3.js` Connection — plain or instrumented. Used to fetch\n * the on-chain transaction via `getTransaction` for enrichment. Does\n * **not** need to be an `InstrumentedConnection`; this helper intentionally\n * never touches the transaction-send path.\n */\n connection: Connection;\n\n /**\n * Service name used for the span. Falls back to the service name\n * configured by `initSight`. Useful when you want a different service\n * name for a specific subsystem's spans.\n */\n serviceName?: string;\n\n /** OTel tracer override. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Optional IDL resolver for program/error decoding. A fresh one is built if omitted. */\n idlResolver?: IdlResolver;\n\n /**\n * Pre-registered IDLs to hand to a freshly-built resolver. Ignored when\n * `idlResolver` is provided.\n */\n idls?: Record<string, AnchorIdl>;\n\n /** Finality commitment used for the enrichment fetch. Default 'confirmed'. */\n commitment?: Finality;\n\n /** Max wall-time before the span is ended with status=timeout. Default 30000ms. */\n timeoutMs?: number;\n\n /** Base poll interval for retrying getTransaction. Default 500ms. */\n pollIntervalMs?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * **Non-wrapping observation helper.** Given a transaction signature that\n * was already sent through any `Connection`, fetch the on-chain record,\n * parse the logs, and emit a single OpenTelemetry span describing the\n * transaction.\n *\n * Unlike `InstrumentedConnection`, this helper **never touches the send\n * path** — it only observes after the fact via `getTransaction(signature)`.\n * Use this when:\n *\n * - You want observability for wallet-adapter flows where the send path\n * goes through the wallet and you can't easily override the Connection\n * - You're security-conscious and don't want any SDK code on the critical\n * transaction path\n * - You want to instrument a handful of high-value transactions rather\n * than every call a Connection makes\n *\n * Usage:\n *\n * import { trackSolanaTransaction } from '@thesight/sdk';\n * import { Connection } from '@solana/web3.js';\n *\n * const connection = new Connection(rpcUrl);\n * const signature = await wallet.sendTransaction(tx, connection);\n *\n * // Fire-and-forget. Span flushes on its own.\n * void trackSolanaTransaction({\n * signature,\n * connection,\n * serviceName: 'checkout',\n * idls: { [programId]: idl },\n * });\n *\n * Returns a `Promise<void>` — typically not awaited, but callers who want\n * to wait for the span to export (e.g. short-lived scripts) can await it.\n */\nexport async function trackSolanaTransaction(opts: TrackTransactionOptions): Promise<void> {\n const tracerName = opts.serviceName ?? '@thesight/sdk';\n const tracer = opts.tracer ?? trace.getTracer(tracerName);\n\n const span = tracer.startSpan('solana.trackTransaction', {}, context.active());\n span.setAttribute('solana.tx.signature', opts.signature);\n\n const start = Date.now();\n const deadline = start + (opts.timeoutMs ?? 30_000);\n const commitment = opts.commitment ?? ('confirmed' as Finality);\n const basePoll = opts.pollIntervalMs ?? 500;\n\n const resolver = opts.idlResolver ?? buildResolverFromIdls(opts.idls);\n\n try {\n const txDetails = await pollGetTransaction(opts.connection, opts.signature, commitment, deadline, basePoll);\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n return;\n }\n\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, resolver);\n\n for (const attr of flatAttributions(cpiTree)) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n span.end();\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction buildResolverFromIdls(idls?: Record<string, AnchorIdl>): IdlResolver {\n const resolver = new IdlResolver();\n if (idls) resolver.registerMany(idls);\n return resolver;\n}\n\nasync function pollGetTransaction(\n connection: Connection,\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await connection.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n return null;\n}\n"],"mappings":"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","names":["import_api","import_core","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/exporter.ts","../src/dsn.ts","../src/track.ts"],"sourcesContent":["import {\n Connection,\n type ConnectionConfig,\n type SendOptions,\n type Commitment,\n type Finality,\n type TransactionSignature,\n type VersionedTransactionResponse,\n} from '@solana/web3.js';\nimport {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl, CpiTree } from '@thesight/core';\n\n// ─── Config ────────────────────────────────────────────────────────────────\n\nexport interface SightConfig {\n /** OTel tracer. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Override RPC endpoint for IDL fetching (defaults to same as connection) */\n idlRpcEndpoint?: string;\n\n /**\n * Commitment used when fetching confirmed tx details for enrichment.\n * Defaults to 'confirmed'.\n */\n commitment?: Commitment;\n\n /**\n * Skip IDL resolution entirely. Spans will still emit with signature and\n * timing attributes, but program names, instruction names, CPI trees, and\n * decoded errors will all be omitted. Useful when you want minimal\n * overhead and don't care about the richer observability.\n */\n skipIdlResolution?: boolean;\n\n /**\n * Pre-register IDLs at construction time. Keys are program IDs, values are\n * full Anchor IDL objects. Anchor users can pass `program.idl` directly off\n * their `Program` instance — zero setup friction.\n */\n idls?: Record<string, AnchorIdl>;\n\n /**\n * Opt into reading Anchor IDL accounts from the on-chain PDA as a fallback\n * when a program is not explicitly registered. Defaults to **false** —\n * Sight's model is that IDLs are explicitly provided by the application,\n * not silently guessed from chain.\n */\n allowOnChainIdlFetch?: boolean;\n\n /**\n * Max wall-time the background enrichment task will wait for a\n * transaction to show up in `getTransaction`. After this expires the\n * span is ended with `solana.tx.status = 'timeout'`. Default 30000 (30s).\n */\n enrichmentTimeoutMs?: number;\n\n /**\n * How often the enrichment task polls `getTransaction`. Each retry backs\n * off by 1.5x up to a cap. Default 500ms base.\n */\n enrichmentPollIntervalMs?: number;\n\n /**\n * Disable automatic span creation in the overridden `sendRawTransaction`.\n * When true, InstrumentedConnection behaves like a plain Connection. You\n * probably don't want this — it's here as an escape hatch for tests and\n * rare cases where you need the class hierarchy but not the tracing.\n */\n disableAutoSpan?: boolean;\n}\n\n// ─── Span attribute shape (documented, for downstream consumers) ──────────\n\nexport interface SightSpanAttributes {\n 'solana.tx.signature': string;\n 'solana.tx.status': 'submitted' | 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.fee_lamports'?: number;\n 'solana.tx.submit_ms': number;\n 'solana.tx.enrichment_ms'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.error_msg'?: string;\n}\n\n// ─── InstrumentedConnection ────────────────────────────────────────────────\n\n/**\n * Drop-in replacement for `@solana/web3.js`'s `Connection` that emits an\n * OpenTelemetry span for **every** call to `sendRawTransaction` —\n * regardless of whether the caller is your own code, an Anchor provider's\n * `sendAndConfirm`, `@solana/web3.js`'s top-level `sendAndConfirmTransaction`,\n * a wallet adapter, or anything else.\n *\n * Internally it overrides the parent class's `sendRawTransaction`, so the\n * span covers the same surface web3.js already mediates. No mutation of\n * the transaction bytes — we call `super.sendRawTransaction(rawTx, opts)`\n * verbatim and observe the signature it returns.\n *\n * After the signature is returned (synchronously from the caller's point\n * of view), a background task polls `getTransaction` until the on-chain\n * record is available, parses the program logs via `@thesight/core`, and\n * enriches the span with CU attribution, per-CPI events, program + instruction\n * names, and decoded error details. The background task never blocks the\n * caller — if it fails or times out, the span ends with status=timeout and\n * whatever partial data was collected.\n *\n * Usage:\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({ dsn: process.env.SIGHT_DSN!, serviceName: 'swap-bot' });\n *\n * // Anywhere you currently construct a Connection, construct this instead:\n * const connection = new InstrumentedConnection(rpcUrl, {\n * commitment: 'confirmed',\n * });\n *\n * // All of the following now emit spans automatically:\n * await connection.sendRawTransaction(tx.serialize());\n * await sendAndConfirmTransaction(connection, tx, signers); // web3.js\n * await program.methods.foo().rpc(); // Anchor\n * await wallet.sendTransaction(tx, connection); // wallet adapter\n */\nexport class InstrumentedConnection extends Connection {\n private sightConfig: SightConfig;\n private idlResolver: IdlResolver;\n private tracer: Tracer;\n\n constructor(endpoint: string, config?: ConnectionConfig & SightConfig) {\n const {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n idls,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs,\n enrichmentPollIntervalMs,\n disableAutoSpan,\n commitment,\n ...connectionConfig\n } = config ?? {};\n\n super(endpoint, connectionConfig);\n\n this.sightConfig = {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs: enrichmentTimeoutMs ?? 30_000,\n enrichmentPollIntervalMs: enrichmentPollIntervalMs ?? 500,\n disableAutoSpan,\n commitment,\n };\n this.idlResolver = new IdlResolver({\n rpcEndpoint: idlRpcEndpoint ?? endpoint,\n allowOnChainFetch: allowOnChainIdlFetch ?? false,\n });\n if (idls) this.idlResolver.registerMany(idls);\n this.tracer = tracer ?? trace.getTracer('@thesight/sdk');\n }\n\n /**\n * Register an IDL for a single program. Convenience forwarder for the\n * underlying resolver. Anchor users typically call this right after\n * constructing a `Program`:\n *\n * const program = new Program(idl, programId, provider);\n * connection.registerIdl(program.programId.toBase58(), program.idl);\n */\n registerIdl(programId: string, idl: AnchorIdl): void {\n this.idlResolver.register(programId, idl);\n }\n\n /** Bulk-register multiple IDLs. */\n registerIdls(idls: Record<string, AnchorIdl>): void {\n this.idlResolver.registerMany(idls);\n }\n\n // ─── Overridden method ────────────────────────────────────────────────────\n\n /**\n * Overrides the parent `Connection.sendRawTransaction` so every submit —\n * including those made by Anchor's `provider.sendAndConfirm`, web3.js's\n * top-level `sendAndConfirmTransaction`, and wallet adapter flows — is\n * observed by an OTel span automatically.\n *\n * The span is a child of whatever OTel context is active when the call\n * fires, so app-level parent spans (HTTP handlers, job runs, wallet flow\n * spans from another SDK) nest the Sight span correctly.\n */\n override async sendRawTransaction(\n rawTransaction: Buffer | Uint8Array | Array<number>,\n options?: SendOptions,\n ): Promise<TransactionSignature> {\n if (this.sightConfig.disableAutoSpan) {\n return super.sendRawTransaction(rawTransaction, options);\n }\n\n const span = this.tracer.startSpan('solana.sendRawTransaction', {}, context.active());\n const submitStart = Date.now();\n\n let signature: TransactionSignature;\n try {\n signature = await super.sendRawTransaction(rawTransaction, options);\n } catch (err) {\n const submitMs = Date.now() - submitStart;\n span.setAttribute('solana.tx.submit_ms', submitMs);\n span.setAttribute('solana.tx.status', 'failed');\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n span.end();\n throw err;\n }\n\n span.setAttribute('solana.tx.signature', signature);\n span.setAttribute('solana.tx.submit_ms', Date.now() - submitStart);\n span.setAttribute('solana.tx.status', 'submitted');\n\n // Fire-and-forget background enrichment. The returned Promise is\n // intentionally discarded — we don't want to couple the caller's\n // transaction-send latency to our observation machinery. Any enrichment\n // error is caught inside enrichSpanInBackground and attached to the\n // span, not bubbled up.\n void this.enrichSpanInBackground(span, signature, submitStart);\n\n return signature;\n }\n\n // ─── Background enrichment ───────────────────────────────────────────────\n\n /**\n * Poll `getTransaction` until the on-chain record is available, then\n * enrich the span with CU attribution, program names, decoded errors,\n * and per-CPI events.\n *\n * This runs async and is never awaited by callers of `sendRawTransaction`.\n * Failures inside this method attach to the span via recordException\n * rather than throwing.\n */\n private async enrichSpanInBackground(\n span: Span,\n signature: TransactionSignature,\n submitStart: number,\n ): Promise<void> {\n const enrichStart = Date.now();\n const deadline = enrichStart + (this.sightConfig.enrichmentTimeoutMs ?? 30_000);\n const commitment = (this.sightConfig.commitment ?? 'confirmed') as Finality;\n const basePollMs = this.sightConfig.enrichmentPollIntervalMs ?? 500;\n\n try {\n const txDetails = await this.pollForTransaction(signature, commitment, deadline, basePollMs);\n\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n span.end();\n return;\n }\n\n this.attachTxDetailsToSpan(span, txDetails);\n\n if (!this.sightConfig.skipIdlResolution) {\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n await this.attachParsedLogsToSpan(span, logs);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setAttribute(\n 'solana.tx.enrichment_error',\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n span.end();\n }\n }\n\n /**\n * Poll `getTransaction(signature)` until either the on-chain record is\n * returned or the deadline passes. Exponential backoff (1.5x) capped at\n * 2 seconds to balance responsiveness against RPC load.\n */\n private async pollForTransaction(\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n ): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await super.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff. Transient RPC errors are common\n // during the seconds immediately after submission.\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await sleep(waitMs);\n }\n return null;\n }\n\n /** Attach the flat fields (slot, fee, CU, status) from a tx response to a span. */\n private attachTxDetailsToSpan(span: Span, txDetails: VersionedTransactionResponse): void {\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n }\n\n /**\n * Parse program logs into a CPI tree, enrich with registered IDLs, and\n * emit one `cpi.invoke` event per invocation onto the span. Root\n * program/instruction names are copied onto span attributes so dashboards\n * can filter by them without walking events.\n */\n private async attachParsedLogsToSpan(span: Span, logs: string[]): Promise<CpiTree | undefined> {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, this.idlResolver);\n\n const attributions = flatAttributions(cpiTree);\n for (const attr of attributions) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n\n return cpiTree;\n }\n}\n\n// ─── Utilities ────────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ─── Re-exports ────────────────────────────────────────────────────────────\n\nexport { IdlResolver } from '@thesight/core';\nexport type {\n CpiTree,\n CuAttribution,\n FlamegraphItem,\n DecodedError,\n AnchorIdl,\n} from '@thesight/core';\n\n// ─── Tracing initialization ──────────────────────────────────────────────────\n\nexport { initSight } from './init.js';\nexport type { InitSightConfig, SightTracerHandle } from './init.js';\nexport { SightSpanExporter } from './exporter.js';\nexport type { SightExporterConfig } from './exporter.js';\n\n// ─── DSN helpers ─────────────────────────────────────────────────────────\n\nexport { parseDsn, buildDsn } from './dsn.js';\nexport type { ParsedDsn } from './dsn.js';\n\n// ─── Non-wrapping observation helper ─────────────────────────────────────\n\nexport { trackSolanaTransaction } from './track.js';\nexport type { TrackTransactionOptions } from './track.js';\n","import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { Resource } from '@opentelemetry/resources';\nimport { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { SightSpanExporter } from './exporter.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface InitSightConfig {\n /**\n * Sight DSN — one string encoding both the ingest endpoint and the API key.\n *\n * Format: `https://<apiKey>@<host>/ingest`\n *\n * Get yours from the Sight dashboard when you create a project. For local\n * dev against a Docker compose stack: `http://sk_live_xxx@localhost:3001/ingest`\n */\n dsn: string;\n\n /**\n * Human-readable service name that shows up on every span. Pick something\n * that identifies this process — `'swap-bot'`, `'market-maker'`,\n * `'trade-settler'`, etc.\n */\n serviceName: string;\n\n /** Optional version string for the service. Useful for correlating spans with a deploy. */\n serviceVersion?: string;\n\n /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */\n batchDelayMs?: number;\n\n /** Max spans per HTTP POST. Clamped to 100 by the exporter. */\n maxBatchSize?: number;\n\n /** Inject a fetch implementation for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface SightTracerHandle {\n provider: NodeTracerProvider;\n /** Flush any pending spans and release the batch processor. */\n shutdown: () => Promise<void>;\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\n/**\n * Initialize Sight tracing. Call this **once** near the top of your\n * process entry point.\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({\n * dsn: process.env.SIGHT_DSN!,\n * serviceName: 'swap-bot',\n * });\n *\n * const connection = new InstrumentedConnection(process.env.RPC_URL!);\n * // Every sendRawTransaction call now emits a span automatically.\n *\n * Registers a NodeTracerProvider as the global OTel tracer. Context\n * propagation uses Node async hooks so spans nest correctly across\n * await boundaries.\n */\nexport function initSight(config: InitSightConfig): SightTracerHandle {\n if (!config.dsn) {\n throw new Error(\n 'initSight: `dsn` is required. ' +\n 'Get your DSN from the Sight dashboard: https://thesight.dev',\n );\n }\n if (!config.serviceName) {\n throw new Error('initSight: `serviceName` is required');\n }\n\n const resource = new Resource({\n [SEMRESATTRS_SERVICE_NAME]: config.serviceName,\n ...(config.serviceVersion\n ? { [SEMRESATTRS_SERVICE_VERSION]: config.serviceVersion }\n : {}),\n });\n\n const provider = new NodeTracerProvider({ resource });\n\n const exporter = new SightSpanExporter({\n dsn: config.dsn,\n fetchImpl: config.fetchImpl,\n maxBatchSize: config.maxBatchSize,\n });\n\n const processor = new BatchSpanProcessor(exporter, {\n scheduledDelayMillis: config.batchDelayMs ?? 5_000,\n maxExportBatchSize: config.maxBatchSize ?? 100,\n maxQueueSize: 2048,\n });\n\n provider.addSpanProcessor(processor);\n provider.register();\n\n return {\n provider,\n shutdown: async () => {\n await provider.shutdown();\n },\n };\n}\n","import type { SpanAttributes } from '@opentelemetry/api';\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n hrTimeToMilliseconds,\n ExportResultCode,\n type ExportResult,\n} from '@opentelemetry/core';\nimport { parseDsn } from './dsn.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface SightExporterConfig {\n /**\n * Sight DSN — one string encoding both the ingest endpoint and the API key.\n * Format: https://<apiKey>@<host>/ingest\n */\n dsn: string;\n /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Max spans per POST. Ingest enforces an upper bound of 100. */\n maxBatchSize?: number;\n}\n\n// ─── Exporter ────────────────────────────────────────────────────────────────\n\n/**\n * An OTel `SpanExporter` that translates OTel spans to Sight's custom\n * ingest payload shape and POSTs them to `/ingest`.\n *\n * Each span becomes one object in the `spans` array. Solana-specific\n * attributes flow through as-is (the set that `InstrumentedConnection`\n * already populates — `solana.tx.signature`, `solana.tx.cu_used`,\n * `solana.tx.program`, etc). The exporter intentionally does not attempt\n * to translate span events or links — the ingest schema doesn't have\n * slots for those, and we prefer dropping silently over guessing.\n *\n * The BatchSpanProcessor upstream handles retries, timeouts, and batching;\n * this exporter stays a thin wire-format translator.\n */\nexport class SightSpanExporter implements SpanExporter {\n private readonly apiKey: string;\n private readonly ingestUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly maxBatchSize: number;\n private shuttingDown = false;\n\n constructor(config: SightExporterConfig) {\n const parsed = parseDsn(config.dsn);\n this.apiKey = parsed.apiKey;\n this.ingestUrl = parsed.ingestUrl;\n this.fetchImpl = config.fetchImpl ?? (globalThis.fetch as typeof fetch);\n this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);\n\n if (!this.fetchImpl) {\n throw new Error(\n 'SightSpanExporter: globalThis.fetch is not available. Node >= 18 ships ' +\n 'with fetch built in, or pass `fetchImpl` explicitly.',\n );\n }\n }\n\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n if (this.shuttingDown) {\n resultCallback({ code: ExportResultCode.FAILED, error: new Error('Exporter is shut down') });\n return;\n }\n\n // Split into chunks if the upstream processor handed us a larger\n // batch than ingest will accept. Uncommon with a well-configured\n // BatchSpanProcessor but cheap to handle defensively.\n const chunks: ReadableSpan[][] = [];\n for (let i = 0; i < spans.length; i += this.maxBatchSize) {\n chunks.push(spans.slice(i, i + this.maxBatchSize));\n }\n\n try {\n for (const chunk of chunks) {\n await this.sendChunk(chunk);\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (err) {\n resultCallback({\n code: ExportResultCode.FAILED,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n async shutdown(): Promise<void> {\n this.shuttingDown = true;\n }\n\n async forceFlush(): Promise<void> {\n // The exporter is stateless — BatchSpanProcessor's own forceFlush\n // drains the queue before calling export(). Nothing to do here.\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private async sendChunk(spans: ReadableSpan[]): Promise<void> {\n const payload = {\n spans: spans.map((span) => this.convertSpan(span)),\n };\n\n const response = await this.fetchImpl(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const body = await safeReadText(response);\n throw new Error(\n `Sight ingest returned ${response.status} ${response.statusText}: ${body}`,\n );\n }\n }\n\n private convertSpan(span: ReadableSpan): IngestSpan {\n const attr = span.attributes;\n const resource = span.resource.attributes;\n const serviceName = typeof resource['service.name'] === 'string'\n ? resource['service.name']\n : undefined;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n const endTimeMs = hrTimeToMilliseconds(span.endTime);\n const durationMs = Math.max(0, endTimeMs - startTimeMs);\n\n const out: IngestSpan = {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n spanName: span.name,\n startTimeMs,\n durationMs,\n };\n\n const parentSpanId = (span as { parentSpanId?: string }).parentSpanId;\n if (parentSpanId) out.parentSpanId = parentSpanId;\n if (serviceName) out.serviceName = serviceName;\n\n copyIfString(attr, 'solana.tx.signature', out);\n copyIfEnum(attr, 'solana.tx.status', out, ['submitted', 'confirmed', 'failed', 'timeout']);\n copyIfNumber(attr, 'solana.tx.slot', out);\n copyIfNumber(attr, 'solana.tx.cu_used', out);\n copyIfNumber(attr, 'solana.tx.cu_budget', out);\n copyIfNumber(attr, 'solana.tx.cu_utilization', out);\n copyIfString(attr, 'solana.tx.program', out);\n copyIfString(attr, 'solana.tx.instruction', out);\n copyIfString(attr, 'solana.tx.error', out);\n copyIfNumber(attr, 'solana.tx.error_code', out);\n copyIfString(attr, 'solana.tx.error_program', out);\n copyIfNumber(attr, 'solana.tx.submit_ms', out);\n copyIfNumber(attr, 'solana.tx.confirmation_ms', out);\n copyIfNumber(attr, 'solana.tx.fee_lamports', out);\n\n return out;\n }\n}\n\n// ─── Wire format (narrow, matches ingest's zod schema) ──────────────────────\n\ninterface IngestSpan {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n serviceName?: string;\n spanName: string;\n startTimeMs: number;\n durationMs: number;\n\n 'solana.tx.signature'?: string;\n 'solana.tx.status'?: 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.submit_ms'?: number;\n 'solana.tx.confirmation_ms'?: number;\n 'solana.tx.fee_lamports'?: number;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction copyIfString(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'string') {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfNumber(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfEnum<T extends string>(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n allowed: readonly T[],\n): void {\n const v = attr[key];\n if (typeof v === 'string' && (allowed as readonly string[]).includes(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nasync function safeReadText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '<no body>';\n }\n}\n","/**\n * Parse a Sight DSN into its constituent parts.\n *\n * DSN format:\n * https://<apiKey>@<host>[:<port>]/<path>\n *\n * Examples:\n * https://sk_live_abc123@ingest.thesight.dev/ingest (production)\n * http://sk_live_abc123@localhost:3001/ingest (local dev)\n *\n * The API key sits in the URL's username position. The SDK extracts it\n * and sends it as a Bearer token to the ingest URL (with the key removed\n * from the URL). This mirrors the Sentry DSN model — one string encodes\n * both \"where to send\" and \"who you are\", so there's no separate\n * apiKey + ingestUrl to misconfig independently.\n */\n\nexport interface ParsedDsn {\n /** The API key extracted from the DSN's username position. */\n apiKey: string;\n /** The ingest endpoint URL with the API key stripped out. */\n ingestUrl: string;\n}\n\n/**\n * Parse a DSN string into `{ apiKey, ingestUrl }`.\n *\n * Throws with a clear message on:\n * - Malformed URL\n * - Missing API key (no username in the URL)\n */\nexport function parseDsn(dsn: string): ParsedDsn {\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n throw new Error(\n `Invalid Sight DSN: \"${dsn}\". ` +\n 'Expected format: https://<apiKey>@<host>/ingest — ' +\n 'get your DSN from the Sight dashboard when you create a project.',\n );\n }\n\n const apiKey = decodeURIComponent(url.username);\n if (!apiKey) {\n throw new Error(\n `Invalid Sight DSN: no API key found in \"${dsn}\". ` +\n 'The API key goes in the username position: https://sk_live_xxx@host/ingest',\n );\n }\n\n // Strip the credentials so the ingest URL is clean for HTTP requests.\n url.username = '';\n url.password = '';\n const ingestUrl = url.toString().replace(/\\/$/, ''); // trim trailing slash\n\n return { apiKey, ingestUrl };\n}\n\n/**\n * Build a DSN from its parts. Used by the dashboard when generating the\n * DSN for a newly-created project.\n */\nexport function buildDsn(apiKey: string, ingestHost: string): string {\n const url = new URL(ingestHost);\n url.username = apiKey;\n if (!url.pathname || url.pathname === '/') {\n url.pathname = '/ingest';\n }\n return url.toString();\n}\n","import type { Connection, Finality, TransactionSignature, VersionedTransactionResponse } from '@solana/web3.js';\nimport { trace, context, SpanStatusCode, type Tracer } from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl } from '@thesight/core';\n\n// ─── Options ──────────────────────────────────────────────────────────────\n\nexport interface TrackTransactionOptions {\n /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */\n signature: TransactionSignature;\n\n /**\n * Any `@solana/web3.js` Connection — plain or instrumented. Used to fetch\n * the on-chain transaction via `getTransaction` for enrichment. Does\n * **not** need to be an `InstrumentedConnection`; this helper intentionally\n * never touches the transaction-send path.\n */\n connection: Connection;\n\n /**\n * Service name used for the span. Falls back to the service name\n * configured by `initSight`. Useful when you want a different service\n * name for a specific subsystem's spans.\n */\n serviceName?: string;\n\n /** OTel tracer override. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Optional IDL resolver for program/error decoding. A fresh one is built if omitted. */\n idlResolver?: IdlResolver;\n\n /**\n * Pre-registered IDLs to hand to a freshly-built resolver. Ignored when\n * `idlResolver` is provided.\n */\n idls?: Record<string, AnchorIdl>;\n\n /** Finality commitment used for the enrichment fetch. Default 'confirmed'. */\n commitment?: Finality;\n\n /** Max wall-time before the span is ended with status=timeout. Default 30000ms. */\n timeoutMs?: number;\n\n /** Base poll interval for retrying getTransaction. Default 500ms. */\n pollIntervalMs?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * **Non-wrapping observation helper.** Given a transaction signature that\n * was already sent through any `Connection`, fetch the on-chain record,\n * parse the logs, and emit a single OpenTelemetry span describing the\n * transaction.\n *\n * Unlike `InstrumentedConnection`, this helper **never touches the send\n * path** — it only observes after the fact via `getTransaction(signature)`.\n * Use this when:\n *\n * - You want observability for wallet-adapter flows where the send path\n * goes through the wallet and you can't easily override the Connection\n * - You're security-conscious and don't want any SDK code on the critical\n * transaction path\n * - You want to instrument a handful of high-value transactions rather\n * than every call a Connection makes\n *\n * Usage:\n *\n * import { trackSolanaTransaction } from '@thesight/sdk';\n * import { Connection } from '@solana/web3.js';\n *\n * const connection = new Connection(rpcUrl);\n * const signature = await wallet.sendTransaction(tx, connection);\n *\n * // Fire-and-forget. Span flushes on its own.\n * void trackSolanaTransaction({\n * signature,\n * connection,\n * serviceName: 'checkout',\n * idls: { [programId]: idl },\n * });\n *\n * Returns a `Promise<void>` — typically not awaited, but callers who want\n * to wait for the span to export (e.g. short-lived scripts) can await it.\n */\nexport async function trackSolanaTransaction(opts: TrackTransactionOptions): Promise<void> {\n const tracerName = opts.serviceName ?? '@thesight/sdk';\n const tracer = opts.tracer ?? trace.getTracer(tracerName);\n\n const span = tracer.startSpan('solana.trackTransaction', {}, context.active());\n span.setAttribute('solana.tx.signature', opts.signature);\n\n const start = Date.now();\n const deadline = start + (opts.timeoutMs ?? 30_000);\n const commitment = opts.commitment ?? ('confirmed' as Finality);\n const basePoll = opts.pollIntervalMs ?? 500;\n\n const resolver = opts.idlResolver ?? buildResolverFromIdls(opts.idls);\n\n try {\n const txDetails = await pollGetTransaction(opts.connection, opts.signature, commitment, deadline, basePoll);\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n return;\n }\n\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, resolver);\n\n for (const attr of flatAttributions(cpiTree)) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n span.end();\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction buildResolverFromIdls(idls?: Record<string, AnchorIdl>): IdlResolver {\n const resolver = new IdlResolver();\n if (idls) resolver.registerMany(idls);\n return resolver;\n}\n\nasync function pollGetTransaction(\n connection: Connection,\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await connection.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n return null;\n}\n"],"mappings":"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","names":["import_api","import_core","import_core"]}
package/dist/index.d.cts CHANGED
@@ -7,8 +7,15 @@ import { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
7
  import { ExportResult } from '@opentelemetry/core';
8
8
 
9
9
  interface InitSightConfig {
10
- /** Project API key (`sk_live_...`) from the Sight dashboard. */
11
- apiKey: string;
10
+ /**
11
+ * Sight DSN — one string encoding both the ingest endpoint and the API key.
12
+ *
13
+ * Format: `https://<apiKey>@<host>/ingest`
14
+ *
15
+ * Get yours from the Sight dashboard when you create a project. For local
16
+ * dev against a Docker compose stack: `http://sk_live_xxx@localhost:3001/ingest`
17
+ */
18
+ dsn: string;
12
19
  /**
13
20
  * Human-readable service name that shows up on every span. Pick something
14
21
  * that identifies this process — `'swap-bot'`, `'market-maker'`,
@@ -17,12 +24,6 @@ interface InitSightConfig {
17
24
  serviceName: string;
18
25
  /** Optional version string for the service. Useful for correlating spans with a deploy. */
19
26
  serviceVersion?: string;
20
- /**
21
- * Full ingest URL. Defaults to the hosted Sight ingest. Override for
22
- * local development (e.g. `http://localhost:3001/ingest`) or self-hosted
23
- * deployments.
24
- */
25
- ingestUrl?: string;
26
27
  /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */
27
28
  batchDelayMs?: number;
28
29
  /** Max spans per HTTP POST. Clamped to 100 by the exporter. */
@@ -37,44 +38,30 @@ interface SightTracerHandle {
37
38
  }
38
39
  /**
39
40
  * Initialize Sight tracing. Call this **once** near the top of your
40
- * process entry point — typically in the same file that boots your
41
- * HTTP server or worker.
41
+ * process entry point.
42
42
  *
43
43
  * import { initSight, InstrumentedConnection } from '@thesight/sdk';
44
44
  *
45
45
  * initSight({
46
- * apiKey: process.env.SIGHT_API_KEY!,
46
+ * dsn: process.env.SIGHT_DSN!,
47
47
  * serviceName: 'swap-bot',
48
- * ingestUrl: process.env.SIGHT_INGEST_URL, // optional, defaults to prod
49
48
  * });
50
49
  *
51
50
  * const connection = new InstrumentedConnection(process.env.RPC_URL!);
52
- * const { signature } = await connection.sendAndConfirmInstrumented(tx);
51
+ * // Every sendRawTransaction call now emits a span automatically.
53
52
  *
54
- * Registers a NodeTracerProvider as the global OTel tracer, so any call to
55
- * `trace.getTracer(...)` (including the ones inside `InstrumentedConnection`)
56
- * routes spans through the Sight exporter. Context propagation uses the
57
- * Node async hooks manager so spans nest correctly across await boundaries.
58
- *
59
- * Returns a handle with a `shutdown()` method. Call it at graceful shutdown
60
- * time so pending spans are flushed before the process exits:
61
- *
62
- * const sight = initSight({ ... });
63
- * process.on('SIGTERM', async () => {
64
- * await sight.shutdown();
65
- * process.exit(0);
66
- * });
53
+ * Registers a NodeTracerProvider as the global OTel tracer. Context
54
+ * propagation uses Node async hooks so spans nest correctly across
55
+ * await boundaries.
67
56
  */
68
57
  declare function initSight(config: InitSightConfig): SightTracerHandle;
69
58
 
70
59
  interface SightExporterConfig {
71
- /** Project API key (`sk_live_...`). Sent as Bearer auth on every POST. */
72
- apiKey: string;
73
60
  /**
74
- * Full ingest URL including the path (e.g. `https://ingest.thesight.dev/ingest`
75
- * or `http://localhost:3001/ingest` for local dev).
61
+ * Sight DSN — one string encoding both the ingest endpoint and the API key.
62
+ * Format: https://<apiKey>@<host>/ingest
76
63
  */
77
- ingestUrl: string;
64
+ dsn: string;
78
65
  /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */
79
66
  fetchImpl?: typeof fetch;
80
67
  /** Max spans per POST. Ingest enforces an upper bound of 100. */
@@ -108,6 +95,42 @@ declare class SightSpanExporter implements SpanExporter {
108
95
  private convertSpan;
109
96
  }
110
97
 
98
+ /**
99
+ * Parse a Sight DSN into its constituent parts.
100
+ *
101
+ * DSN format:
102
+ * https://<apiKey>@<host>[:<port>]/<path>
103
+ *
104
+ * Examples:
105
+ * https://sk_live_abc123@ingest.thesight.dev/ingest (production)
106
+ * http://sk_live_abc123@localhost:3001/ingest (local dev)
107
+ *
108
+ * The API key sits in the URL's username position. The SDK extracts it
109
+ * and sends it as a Bearer token to the ingest URL (with the key removed
110
+ * from the URL). This mirrors the Sentry DSN model — one string encodes
111
+ * both "where to send" and "who you are", so there's no separate
112
+ * apiKey + ingestUrl to misconfig independently.
113
+ */
114
+ interface ParsedDsn {
115
+ /** The API key extracted from the DSN's username position. */
116
+ apiKey: string;
117
+ /** The ingest endpoint URL with the API key stripped out. */
118
+ ingestUrl: string;
119
+ }
120
+ /**
121
+ * Parse a DSN string into `{ apiKey, ingestUrl }`.
122
+ *
123
+ * Throws with a clear message on:
124
+ * - Malformed URL
125
+ * - Missing API key (no username in the URL)
126
+ */
127
+ declare function parseDsn(dsn: string): ParsedDsn;
128
+ /**
129
+ * Build a DSN from its parts. Used by the dashboard when generating the
130
+ * DSN for a newly-created project.
131
+ */
132
+ declare function buildDsn(apiKey: string, ingestHost: string): string;
133
+
111
134
  interface TrackTransactionOptions {
112
135
  /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */
113
136
  signature: TransactionSignature;
@@ -268,7 +291,7 @@ interface SightSpanAttributes {
268
291
  *
269
292
  * import { initSight, InstrumentedConnection } from '@thesight/sdk';
270
293
  *
271
- * initSight({ apiKey, serviceName: 'swap-bot' });
294
+ * initSight({ dsn: process.env.SIGHT_DSN!, serviceName: 'swap-bot' });
272
295
  *
273
296
  * // Anywhere you currently construct a Connection, construct this instead:
274
297
  * const connection = new InstrumentedConnection(rpcUrl, {
@@ -335,4 +358,4 @@ declare class InstrumentedConnection extends Connection {
335
358
  private attachParsedLogsToSpan;
336
359
  }
337
360
 
338
- export { type InitSightConfig, InstrumentedConnection, type SightConfig, type SightExporterConfig, type SightSpanAttributes, SightSpanExporter, type SightTracerHandle, type TrackTransactionOptions, initSight, trackSolanaTransaction };
361
+ export { type InitSightConfig, InstrumentedConnection, type ParsedDsn, type SightConfig, type SightExporterConfig, type SightSpanAttributes, SightSpanExporter, type SightTracerHandle, type TrackTransactionOptions, buildDsn, initSight, parseDsn, trackSolanaTransaction };
package/dist/index.d.ts CHANGED
@@ -7,8 +7,15 @@ import { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
7
  import { ExportResult } from '@opentelemetry/core';
8
8
 
9
9
  interface InitSightConfig {
10
- /** Project API key (`sk_live_...`) from the Sight dashboard. */
11
- apiKey: string;
10
+ /**
11
+ * Sight DSN — one string encoding both the ingest endpoint and the API key.
12
+ *
13
+ * Format: `https://<apiKey>@<host>/ingest`
14
+ *
15
+ * Get yours from the Sight dashboard when you create a project. For local
16
+ * dev against a Docker compose stack: `http://sk_live_xxx@localhost:3001/ingest`
17
+ */
18
+ dsn: string;
12
19
  /**
13
20
  * Human-readable service name that shows up on every span. Pick something
14
21
  * that identifies this process — `'swap-bot'`, `'market-maker'`,
@@ -17,12 +24,6 @@ interface InitSightConfig {
17
24
  serviceName: string;
18
25
  /** Optional version string for the service. Useful for correlating spans with a deploy. */
19
26
  serviceVersion?: string;
20
- /**
21
- * Full ingest URL. Defaults to the hosted Sight ingest. Override for
22
- * local development (e.g. `http://localhost:3001/ingest`) or self-hosted
23
- * deployments.
24
- */
25
- ingestUrl?: string;
26
27
  /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */
27
28
  batchDelayMs?: number;
28
29
  /** Max spans per HTTP POST. Clamped to 100 by the exporter. */
@@ -37,44 +38,30 @@ interface SightTracerHandle {
37
38
  }
38
39
  /**
39
40
  * Initialize Sight tracing. Call this **once** near the top of your
40
- * process entry point — typically in the same file that boots your
41
- * HTTP server or worker.
41
+ * process entry point.
42
42
  *
43
43
  * import { initSight, InstrumentedConnection } from '@thesight/sdk';
44
44
  *
45
45
  * initSight({
46
- * apiKey: process.env.SIGHT_API_KEY!,
46
+ * dsn: process.env.SIGHT_DSN!,
47
47
  * serviceName: 'swap-bot',
48
- * ingestUrl: process.env.SIGHT_INGEST_URL, // optional, defaults to prod
49
48
  * });
50
49
  *
51
50
  * const connection = new InstrumentedConnection(process.env.RPC_URL!);
52
- * const { signature } = await connection.sendAndConfirmInstrumented(tx);
51
+ * // Every sendRawTransaction call now emits a span automatically.
53
52
  *
54
- * Registers a NodeTracerProvider as the global OTel tracer, so any call to
55
- * `trace.getTracer(...)` (including the ones inside `InstrumentedConnection`)
56
- * routes spans through the Sight exporter. Context propagation uses the
57
- * Node async hooks manager so spans nest correctly across await boundaries.
58
- *
59
- * Returns a handle with a `shutdown()` method. Call it at graceful shutdown
60
- * time so pending spans are flushed before the process exits:
61
- *
62
- * const sight = initSight({ ... });
63
- * process.on('SIGTERM', async () => {
64
- * await sight.shutdown();
65
- * process.exit(0);
66
- * });
53
+ * Registers a NodeTracerProvider as the global OTel tracer. Context
54
+ * propagation uses Node async hooks so spans nest correctly across
55
+ * await boundaries.
67
56
  */
68
57
  declare function initSight(config: InitSightConfig): SightTracerHandle;
69
58
 
70
59
  interface SightExporterConfig {
71
- /** Project API key (`sk_live_...`). Sent as Bearer auth on every POST. */
72
- apiKey: string;
73
60
  /**
74
- * Full ingest URL including the path (e.g. `https://ingest.thesight.dev/ingest`
75
- * or `http://localhost:3001/ingest` for local dev).
61
+ * Sight DSN — one string encoding both the ingest endpoint and the API key.
62
+ * Format: https://<apiKey>@<host>/ingest
76
63
  */
77
- ingestUrl: string;
64
+ dsn: string;
78
65
  /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */
79
66
  fetchImpl?: typeof fetch;
80
67
  /** Max spans per POST. Ingest enforces an upper bound of 100. */
@@ -108,6 +95,42 @@ declare class SightSpanExporter implements SpanExporter {
108
95
  private convertSpan;
109
96
  }
110
97
 
98
+ /**
99
+ * Parse a Sight DSN into its constituent parts.
100
+ *
101
+ * DSN format:
102
+ * https://<apiKey>@<host>[:<port>]/<path>
103
+ *
104
+ * Examples:
105
+ * https://sk_live_abc123@ingest.thesight.dev/ingest (production)
106
+ * http://sk_live_abc123@localhost:3001/ingest (local dev)
107
+ *
108
+ * The API key sits in the URL's username position. The SDK extracts it
109
+ * and sends it as a Bearer token to the ingest URL (with the key removed
110
+ * from the URL). This mirrors the Sentry DSN model — one string encodes
111
+ * both "where to send" and "who you are", so there's no separate
112
+ * apiKey + ingestUrl to misconfig independently.
113
+ */
114
+ interface ParsedDsn {
115
+ /** The API key extracted from the DSN's username position. */
116
+ apiKey: string;
117
+ /** The ingest endpoint URL with the API key stripped out. */
118
+ ingestUrl: string;
119
+ }
120
+ /**
121
+ * Parse a DSN string into `{ apiKey, ingestUrl }`.
122
+ *
123
+ * Throws with a clear message on:
124
+ * - Malformed URL
125
+ * - Missing API key (no username in the URL)
126
+ */
127
+ declare function parseDsn(dsn: string): ParsedDsn;
128
+ /**
129
+ * Build a DSN from its parts. Used by the dashboard when generating the
130
+ * DSN for a newly-created project.
131
+ */
132
+ declare function buildDsn(apiKey: string, ingestHost: string): string;
133
+
111
134
  interface TrackTransactionOptions {
112
135
  /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */
113
136
  signature: TransactionSignature;
@@ -268,7 +291,7 @@ interface SightSpanAttributes {
268
291
  *
269
292
  * import { initSight, InstrumentedConnection } from '@thesight/sdk';
270
293
  *
271
- * initSight({ apiKey, serviceName: 'swap-bot' });
294
+ * initSight({ dsn: process.env.SIGHT_DSN!, serviceName: 'swap-bot' });
272
295
  *
273
296
  * // Anywhere you currently construct a Connection, construct this instead:
274
297
  * const connection = new InstrumentedConnection(rpcUrl, {
@@ -335,4 +358,4 @@ declare class InstrumentedConnection extends Connection {
335
358
  private attachParsedLogsToSpan;
336
359
  }
337
360
 
338
- export { type InitSightConfig, InstrumentedConnection, type SightConfig, type SightExporterConfig, type SightSpanAttributes, SightSpanExporter, type SightTracerHandle, type TrackTransactionOptions, initSight, trackSolanaTransaction };
361
+ export { type InitSightConfig, InstrumentedConnection, type ParsedDsn, type SightConfig, type SightExporterConfig, type SightSpanAttributes, SightSpanExporter, type SightTracerHandle, type TrackTransactionOptions, buildDsn, initSight, parseDsn, trackSolanaTransaction };
package/dist/index.js CHANGED
@@ -21,6 +21,38 @@ import {
21
21
  hrTimeToMilliseconds,
22
22
  ExportResultCode
23
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
24
56
  var SightSpanExporter = class {
25
57
  apiKey;
26
58
  ingestUrl;
@@ -28,8 +60,9 @@ var SightSpanExporter = class {
28
60
  maxBatchSize;
29
61
  shuttingDown = false;
30
62
  constructor(config) {
31
- this.apiKey = config.apiKey;
32
- this.ingestUrl = config.ingestUrl;
63
+ const parsed = parseDsn(config.dsn);
64
+ this.apiKey = parsed.apiKey;
65
+ this.ingestUrl = parsed.ingestUrl;
33
66
  this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
34
67
  this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);
35
68
  if (!this.fetchImpl) {
@@ -102,7 +135,7 @@ var SightSpanExporter = class {
102
135
  if (parentSpanId) out.parentSpanId = parentSpanId;
103
136
  if (serviceName) out.serviceName = serviceName;
104
137
  copyIfString(attr, "solana.tx.signature", out);
105
- copyIfEnum(attr, "solana.tx.status", out, ["confirmed", "failed", "timeout"]);
138
+ copyIfEnum(attr, "solana.tx.status", out, ["submitted", "confirmed", "failed", "timeout"]);
106
139
  copyIfNumber(attr, "solana.tx.slot", out);
107
140
  copyIfNumber(attr, "solana.tx.cu_used", out);
108
141
  copyIfNumber(attr, "solana.tx.cu_budget", out);
@@ -145,10 +178,11 @@ async function safeReadText(res) {
145
178
  }
146
179
 
147
180
  // src/init.ts
148
- var DEFAULT_INGEST_URL = "https://ingest.thesight.dev/ingest";
149
181
  function initSight(config) {
150
- if (!config.apiKey) {
151
- throw new Error("initSight: `apiKey` is required");
182
+ if (!config.dsn) {
183
+ throw new Error(
184
+ "initSight: `dsn` is required. Get your DSN from the Sight dashboard: https://thesight.dev"
185
+ );
152
186
  }
153
187
  if (!config.serviceName) {
154
188
  throw new Error("initSight: `serviceName` is required");
@@ -159,8 +193,7 @@ function initSight(config) {
159
193
  });
160
194
  const provider = new NodeTracerProvider({ resource });
161
195
  const exporter = new SightSpanExporter({
162
- apiKey: config.apiKey,
163
- ingestUrl: config.ingestUrl ?? DEFAULT_INGEST_URL,
196
+ dsn: config.dsn,
164
197
  fetchImpl: config.fetchImpl,
165
198
  maxBatchSize: config.maxBatchSize
166
199
  });
@@ -479,7 +512,9 @@ export {
479
512
  IdlResolver3 as IdlResolver,
480
513
  InstrumentedConnection,
481
514
  SightSpanExporter,
515
+ buildDsn,
482
516
  initSight,
517
+ parseDsn,
483
518
  trackSolanaTransaction
484
519
  };
485
520
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/exporter.ts","../src/track.ts"],"sourcesContent":["import {\n Connection,\n type ConnectionConfig,\n type SendOptions,\n type Commitment,\n type Finality,\n type TransactionSignature,\n type VersionedTransactionResponse,\n} from '@solana/web3.js';\nimport {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl, CpiTree } from '@thesight/core';\n\n// ─── Config ────────────────────────────────────────────────────────────────\n\nexport interface SightConfig {\n /** OTel tracer. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Override RPC endpoint for IDL fetching (defaults to same as connection) */\n idlRpcEndpoint?: string;\n\n /**\n * Commitment used when fetching confirmed tx details for enrichment.\n * Defaults to 'confirmed'.\n */\n commitment?: Commitment;\n\n /**\n * Skip IDL resolution entirely. Spans will still emit with signature and\n * timing attributes, but program names, instruction names, CPI trees, and\n * decoded errors will all be omitted. Useful when you want minimal\n * overhead and don't care about the richer observability.\n */\n skipIdlResolution?: boolean;\n\n /**\n * Pre-register IDLs at construction time. Keys are program IDs, values are\n * full Anchor IDL objects. Anchor users can pass `program.idl` directly off\n * their `Program` instance — zero setup friction.\n */\n idls?: Record<string, AnchorIdl>;\n\n /**\n * Opt into reading Anchor IDL accounts from the on-chain PDA as a fallback\n * when a program is not explicitly registered. Defaults to **false** —\n * Sight's model is that IDLs are explicitly provided by the application,\n * not silently guessed from chain.\n */\n allowOnChainIdlFetch?: boolean;\n\n /**\n * Max wall-time the background enrichment task will wait for a\n * transaction to show up in `getTransaction`. After this expires the\n * span is ended with `solana.tx.status = 'timeout'`. Default 30000 (30s).\n */\n enrichmentTimeoutMs?: number;\n\n /**\n * How often the enrichment task polls `getTransaction`. Each retry backs\n * off by 1.5x up to a cap. Default 500ms base.\n */\n enrichmentPollIntervalMs?: number;\n\n /**\n * Disable automatic span creation in the overridden `sendRawTransaction`.\n * When true, InstrumentedConnection behaves like a plain Connection. You\n * probably don't want this — it's here as an escape hatch for tests and\n * rare cases where you need the class hierarchy but not the tracing.\n */\n disableAutoSpan?: boolean;\n}\n\n// ─── Span attribute shape (documented, for downstream consumers) ──────────\n\nexport interface SightSpanAttributes {\n 'solana.tx.signature': string;\n 'solana.tx.status': 'submitted' | 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.fee_lamports'?: number;\n 'solana.tx.submit_ms': number;\n 'solana.tx.enrichment_ms'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.error_msg'?: string;\n}\n\n// ─── InstrumentedConnection ────────────────────────────────────────────────\n\n/**\n * Drop-in replacement for `@solana/web3.js`'s `Connection` that emits an\n * OpenTelemetry span for **every** call to `sendRawTransaction` —\n * regardless of whether the caller is your own code, an Anchor provider's\n * `sendAndConfirm`, `@solana/web3.js`'s top-level `sendAndConfirmTransaction`,\n * a wallet adapter, or anything else.\n *\n * Internally it overrides the parent class's `sendRawTransaction`, so the\n * span covers the same surface web3.js already mediates. No mutation of\n * the transaction bytes — we call `super.sendRawTransaction(rawTx, opts)`\n * verbatim and observe the signature it returns.\n *\n * After the signature is returned (synchronously from the caller's point\n * of view), a background task polls `getTransaction` until the on-chain\n * record is available, parses the program logs via `@thesight/core`, and\n * enriches the span with CU attribution, per-CPI events, program + instruction\n * names, and decoded error details. The background task never blocks the\n * caller — if it fails or times out, the span ends with status=timeout and\n * whatever partial data was collected.\n *\n * Usage:\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({ apiKey, serviceName: 'swap-bot' });\n *\n * // Anywhere you currently construct a Connection, construct this instead:\n * const connection = new InstrumentedConnection(rpcUrl, {\n * commitment: 'confirmed',\n * });\n *\n * // All of the following now emit spans automatically:\n * await connection.sendRawTransaction(tx.serialize());\n * await sendAndConfirmTransaction(connection, tx, signers); // web3.js\n * await program.methods.foo().rpc(); // Anchor\n * await wallet.sendTransaction(tx, connection); // wallet adapter\n */\nexport class InstrumentedConnection extends Connection {\n private sightConfig: SightConfig;\n private idlResolver: IdlResolver;\n private tracer: Tracer;\n\n constructor(endpoint: string, config?: ConnectionConfig & SightConfig) {\n const {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n idls,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs,\n enrichmentPollIntervalMs,\n disableAutoSpan,\n commitment,\n ...connectionConfig\n } = config ?? {};\n\n super(endpoint, connectionConfig);\n\n this.sightConfig = {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs: enrichmentTimeoutMs ?? 30_000,\n enrichmentPollIntervalMs: enrichmentPollIntervalMs ?? 500,\n disableAutoSpan,\n commitment,\n };\n this.idlResolver = new IdlResolver({\n rpcEndpoint: idlRpcEndpoint ?? endpoint,\n allowOnChainFetch: allowOnChainIdlFetch ?? false,\n });\n if (idls) this.idlResolver.registerMany(idls);\n this.tracer = tracer ?? trace.getTracer('@thesight/sdk');\n }\n\n /**\n * Register an IDL for a single program. Convenience forwarder for the\n * underlying resolver. Anchor users typically call this right after\n * constructing a `Program`:\n *\n * const program = new Program(idl, programId, provider);\n * connection.registerIdl(program.programId.toBase58(), program.idl);\n */\n registerIdl(programId: string, idl: AnchorIdl): void {\n this.idlResolver.register(programId, idl);\n }\n\n /** Bulk-register multiple IDLs. */\n registerIdls(idls: Record<string, AnchorIdl>): void {\n this.idlResolver.registerMany(idls);\n }\n\n // ─── Overridden method ────────────────────────────────────────────────────\n\n /**\n * Overrides the parent `Connection.sendRawTransaction` so every submit —\n * including those made by Anchor's `provider.sendAndConfirm`, web3.js's\n * top-level `sendAndConfirmTransaction`, and wallet adapter flows — is\n * observed by an OTel span automatically.\n *\n * The span is a child of whatever OTel context is active when the call\n * fires, so app-level parent spans (HTTP handlers, job runs, wallet flow\n * spans from another SDK) nest the Sight span correctly.\n */\n override async sendRawTransaction(\n rawTransaction: Buffer | Uint8Array | Array<number>,\n options?: SendOptions,\n ): Promise<TransactionSignature> {\n if (this.sightConfig.disableAutoSpan) {\n return super.sendRawTransaction(rawTransaction, options);\n }\n\n const span = this.tracer.startSpan('solana.sendRawTransaction', {}, context.active());\n const submitStart = Date.now();\n\n let signature: TransactionSignature;\n try {\n signature = await super.sendRawTransaction(rawTransaction, options);\n } catch (err) {\n const submitMs = Date.now() - submitStart;\n span.setAttribute('solana.tx.submit_ms', submitMs);\n span.setAttribute('solana.tx.status', 'failed');\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n span.end();\n throw err;\n }\n\n span.setAttribute('solana.tx.signature', signature);\n span.setAttribute('solana.tx.submit_ms', Date.now() - submitStart);\n span.setAttribute('solana.tx.status', 'submitted');\n\n // Fire-and-forget background enrichment. The returned Promise is\n // intentionally discarded — we don't want to couple the caller's\n // transaction-send latency to our observation machinery. Any enrichment\n // error is caught inside enrichSpanInBackground and attached to the\n // span, not bubbled up.\n void this.enrichSpanInBackground(span, signature, submitStart);\n\n return signature;\n }\n\n // ─── Background enrichment ───────────────────────────────────────────────\n\n /**\n * Poll `getTransaction` until the on-chain record is available, then\n * enrich the span with CU attribution, program names, decoded errors,\n * and per-CPI events.\n *\n * This runs async and is never awaited by callers of `sendRawTransaction`.\n * Failures inside this method attach to the span via recordException\n * rather than throwing.\n */\n private async enrichSpanInBackground(\n span: Span,\n signature: TransactionSignature,\n submitStart: number,\n ): Promise<void> {\n const enrichStart = Date.now();\n const deadline = enrichStart + (this.sightConfig.enrichmentTimeoutMs ?? 30_000);\n const commitment = (this.sightConfig.commitment ?? 'confirmed') as Finality;\n const basePollMs = this.sightConfig.enrichmentPollIntervalMs ?? 500;\n\n try {\n const txDetails = await this.pollForTransaction(signature, commitment, deadline, basePollMs);\n\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n span.end();\n return;\n }\n\n this.attachTxDetailsToSpan(span, txDetails);\n\n if (!this.sightConfig.skipIdlResolution) {\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n await this.attachParsedLogsToSpan(span, logs);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setAttribute(\n 'solana.tx.enrichment_error',\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n span.end();\n }\n }\n\n /**\n * Poll `getTransaction(signature)` until either the on-chain record is\n * returned or the deadline passes. Exponential backoff (1.5x) capped at\n * 2 seconds to balance responsiveness against RPC load.\n */\n private async pollForTransaction(\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n ): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await super.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff. Transient RPC errors are common\n // during the seconds immediately after submission.\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await sleep(waitMs);\n }\n return null;\n }\n\n /** Attach the flat fields (slot, fee, CU, status) from a tx response to a span. */\n private attachTxDetailsToSpan(span: Span, txDetails: VersionedTransactionResponse): void {\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n }\n\n /**\n * Parse program logs into a CPI tree, enrich with registered IDLs, and\n * emit one `cpi.invoke` event per invocation onto the span. Root\n * program/instruction names are copied onto span attributes so dashboards\n * can filter by them without walking events.\n */\n private async attachParsedLogsToSpan(span: Span, logs: string[]): Promise<CpiTree | undefined> {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, this.idlResolver);\n\n const attributions = flatAttributions(cpiTree);\n for (const attr of attributions) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n\n return cpiTree;\n }\n}\n\n// ─── Utilities ────────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ─── Re-exports ────────────────────────────────────────────────────────────\n\nexport { IdlResolver } from '@thesight/core';\nexport type {\n CpiTree,\n CuAttribution,\n FlamegraphItem,\n DecodedError,\n AnchorIdl,\n} from '@thesight/core';\n\n// ─── Tracing initialization ──────────────────────────────────────────────────\n\nexport { initSight } from './init.js';\nexport type { InitSightConfig, SightTracerHandle } from './init.js';\nexport { SightSpanExporter } from './exporter.js';\nexport type { SightExporterConfig } from './exporter.js';\n\n// ─── Non-wrapping observation helper ─────────────────────────────────────\n\nexport { trackSolanaTransaction } from './track.js';\nexport type { TrackTransactionOptions } from './track.js';\n","import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { Resource } from '@opentelemetry/resources';\nimport { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { SightSpanExporter } from './exporter.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface InitSightConfig {\n /** Project API key (`sk_live_...`) from the Sight dashboard. */\n apiKey: string;\n\n /**\n * Human-readable service name that shows up on every span. Pick something\n * that identifies this process — `'swap-bot'`, `'market-maker'`,\n * `'trade-settler'`, etc.\n */\n serviceName: string;\n\n /** Optional version string for the service. Useful for correlating spans with a deploy. */\n serviceVersion?: string;\n\n /**\n * Full ingest URL. Defaults to the hosted Sight ingest. Override for\n * local development (e.g. `http://localhost:3001/ingest`) or self-hosted\n * deployments.\n */\n ingestUrl?: string;\n\n /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */\n batchDelayMs?: number;\n\n /** Max spans per HTTP POST. Clamped to 100 by the exporter. */\n maxBatchSize?: number;\n\n /** Inject a fetch implementation for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface SightTracerHandle {\n provider: NodeTracerProvider;\n /** Flush any pending spans and release the batch processor. */\n shutdown: () => Promise<void>;\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\nconst DEFAULT_INGEST_URL = 'https://ingest.thesight.dev/ingest';\n\n/**\n * Initialize Sight tracing. Call this **once** near the top of your\n * process entry point — typically in the same file that boots your\n * HTTP server or worker.\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({\n * apiKey: process.env.SIGHT_API_KEY!,\n * serviceName: 'swap-bot',\n * ingestUrl: process.env.SIGHT_INGEST_URL, // optional, defaults to prod\n * });\n *\n * const connection = new InstrumentedConnection(process.env.RPC_URL!);\n * const { signature } = await connection.sendAndConfirmInstrumented(tx);\n *\n * Registers a NodeTracerProvider as the global OTel tracer, so any call to\n * `trace.getTracer(...)` (including the ones inside `InstrumentedConnection`)\n * routes spans through the Sight exporter. Context propagation uses the\n * Node async hooks manager so spans nest correctly across await boundaries.\n *\n * Returns a handle with a `shutdown()` method. Call it at graceful shutdown\n * time so pending spans are flushed before the process exits:\n *\n * const sight = initSight({ ... });\n * process.on('SIGTERM', async () => {\n * await sight.shutdown();\n * process.exit(0);\n * });\n */\nexport function initSight(config: InitSightConfig): SightTracerHandle {\n if (!config.apiKey) {\n throw new Error('initSight: `apiKey` is required');\n }\n if (!config.serviceName) {\n throw new Error('initSight: `serviceName` is required');\n }\n\n const resource = new Resource({\n [SEMRESATTRS_SERVICE_NAME]: config.serviceName,\n ...(config.serviceVersion\n ? { [SEMRESATTRS_SERVICE_VERSION]: config.serviceVersion }\n : {}),\n });\n\n const provider = new NodeTracerProvider({ resource });\n\n const exporter = new SightSpanExporter({\n apiKey: config.apiKey,\n ingestUrl: config.ingestUrl ?? DEFAULT_INGEST_URL,\n fetchImpl: config.fetchImpl,\n maxBatchSize: config.maxBatchSize,\n });\n\n // BatchSpanProcessor buffers spans and flushes on an interval. This is the\n // right default for production — simple span processor is one HTTP round\n // trip per span, which is fine for tests and dev but nasty at scale.\n const processor = new BatchSpanProcessor(exporter, {\n scheduledDelayMillis: config.batchDelayMs ?? 5_000,\n maxExportBatchSize: config.maxBatchSize ?? 100,\n maxQueueSize: 2048,\n });\n\n provider.addSpanProcessor(processor);\n\n // Register as the global OTel tracer provider. After this call,\n // `trace.getTracer('anything')` returns a tracer from our provider,\n // which routes spans to SightSpanExporter.\n provider.register();\n\n return {\n provider,\n shutdown: async () => {\n await provider.shutdown();\n },\n };\n}\n","import type { SpanAttributes } from '@opentelemetry/api';\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n hrTimeToMilliseconds,\n ExportResultCode,\n type ExportResult,\n} from '@opentelemetry/core';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface SightExporterConfig {\n /** Project API key (`sk_live_...`). Sent as Bearer auth on every POST. */\n apiKey: string;\n /**\n * Full ingest URL including the path (e.g. `https://ingest.thesight.dev/ingest`\n * or `http://localhost:3001/ingest` for local dev).\n */\n ingestUrl: string;\n /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Max spans per POST. Ingest enforces an upper bound of 100. */\n maxBatchSize?: number;\n}\n\n// ─── Exporter ────────────────────────────────────────────────────────────────\n\n/**\n * An OTel `SpanExporter` that translates OTel spans to Sight's custom\n * ingest payload shape and POSTs them to `/ingest`.\n *\n * Each span becomes one object in the `spans` array. Solana-specific\n * attributes flow through as-is (the set that `InstrumentedConnection`\n * already populates — `solana.tx.signature`, `solana.tx.cu_used`,\n * `solana.tx.program`, etc). The exporter intentionally does not attempt\n * to translate span events or links — the ingest schema doesn't have\n * slots for those, and we prefer dropping silently over guessing.\n *\n * The BatchSpanProcessor upstream handles retries, timeouts, and batching;\n * this exporter stays a thin wire-format translator.\n */\nexport class SightSpanExporter implements SpanExporter {\n private readonly apiKey: string;\n private readonly ingestUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly maxBatchSize: number;\n private shuttingDown = false;\n\n constructor(config: SightExporterConfig) {\n this.apiKey = config.apiKey;\n this.ingestUrl = config.ingestUrl;\n this.fetchImpl = config.fetchImpl ?? (globalThis.fetch as typeof fetch);\n // Ingest's IngestPayload schema caps at 100 spans per POST. Allow\n // operators to lower the batch size for tighter latency, but never\n // raise it above the server's limit.\n this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);\n\n if (!this.fetchImpl) {\n throw new Error(\n 'SightSpanExporter: globalThis.fetch is not available. Node >= 18 ships ' +\n 'with fetch built in, or pass `fetchImpl` explicitly.',\n );\n }\n }\n\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n if (this.shuttingDown) {\n resultCallback({ code: ExportResultCode.FAILED, error: new Error('Exporter is shut down') });\n return;\n }\n\n // Split into chunks if the upstream processor handed us a larger\n // batch than ingest will accept. Uncommon with a well-configured\n // BatchSpanProcessor but cheap to handle defensively.\n const chunks: ReadableSpan[][] = [];\n for (let i = 0; i < spans.length; i += this.maxBatchSize) {\n chunks.push(spans.slice(i, i + this.maxBatchSize));\n }\n\n try {\n for (const chunk of chunks) {\n await this.sendChunk(chunk);\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (err) {\n resultCallback({\n code: ExportResultCode.FAILED,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n async shutdown(): Promise<void> {\n this.shuttingDown = true;\n }\n\n async forceFlush(): Promise<void> {\n // The exporter is stateless — BatchSpanProcessor's own forceFlush\n // drains the queue before calling export(). Nothing to do here.\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private async sendChunk(spans: ReadableSpan[]): Promise<void> {\n const payload = {\n spans: spans.map((span) => this.convertSpan(span)),\n };\n\n const response = await this.fetchImpl(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const body = await safeReadText(response);\n throw new Error(\n `Sight ingest returned ${response.status} ${response.statusText}: ${body}`,\n );\n }\n }\n\n private convertSpan(span: ReadableSpan): IngestSpan {\n const attr = span.attributes;\n const resource = span.resource.attributes;\n const serviceName = typeof resource['service.name'] === 'string'\n ? resource['service.name']\n : undefined;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n const endTimeMs = hrTimeToMilliseconds(span.endTime);\n const durationMs = Math.max(0, endTimeMs - startTimeMs);\n\n const out: IngestSpan = {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n spanName: span.name,\n startTimeMs,\n durationMs,\n };\n\n const parentSpanId = (span as { parentSpanId?: string }).parentSpanId;\n if (parentSpanId) out.parentSpanId = parentSpanId;\n if (serviceName) out.serviceName = serviceName;\n\n copyIfString(attr, 'solana.tx.signature', out);\n copyIfEnum(attr, 'solana.tx.status', out, ['confirmed', 'failed', 'timeout']);\n copyIfNumber(attr, 'solana.tx.slot', out);\n copyIfNumber(attr, 'solana.tx.cu_used', out);\n copyIfNumber(attr, 'solana.tx.cu_budget', out);\n copyIfNumber(attr, 'solana.tx.cu_utilization', out);\n copyIfString(attr, 'solana.tx.program', out);\n copyIfString(attr, 'solana.tx.instruction', out);\n copyIfString(attr, 'solana.tx.error', out);\n copyIfNumber(attr, 'solana.tx.error_code', out);\n copyIfString(attr, 'solana.tx.error_program', out);\n copyIfNumber(attr, 'solana.tx.submit_ms', out);\n copyIfNumber(attr, 'solana.tx.confirmation_ms', out);\n copyIfNumber(attr, 'solana.tx.fee_lamports', out);\n\n return out;\n }\n}\n\n// ─── Wire format (narrow, matches ingest's zod schema) ──────────────────────\n\ninterface IngestSpan {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n serviceName?: string;\n spanName: string;\n startTimeMs: number;\n durationMs: number;\n\n 'solana.tx.signature'?: string;\n 'solana.tx.status'?: 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.submit_ms'?: number;\n 'solana.tx.confirmation_ms'?: number;\n 'solana.tx.fee_lamports'?: number;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction copyIfString(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'string') {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfNumber(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfEnum<T extends string>(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n allowed: readonly T[],\n): void {\n const v = attr[key];\n if (typeof v === 'string' && (allowed as readonly string[]).includes(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nasync function safeReadText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '<no body>';\n }\n}\n","import type { Connection, Finality, TransactionSignature, VersionedTransactionResponse } from '@solana/web3.js';\nimport { trace, context, SpanStatusCode, type Tracer } from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl } from '@thesight/core';\n\n// ─── Options ──────────────────────────────────────────────────────────────\n\nexport interface TrackTransactionOptions {\n /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */\n signature: TransactionSignature;\n\n /**\n * Any `@solana/web3.js` Connection — plain or instrumented. Used to fetch\n * the on-chain transaction via `getTransaction` for enrichment. Does\n * **not** need to be an `InstrumentedConnection`; this helper intentionally\n * never touches the transaction-send path.\n */\n connection: Connection;\n\n /**\n * Service name used for the span. Falls back to the service name\n * configured by `initSight`. Useful when you want a different service\n * name for a specific subsystem's spans.\n */\n serviceName?: string;\n\n /** OTel tracer override. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Optional IDL resolver for program/error decoding. A fresh one is built if omitted. */\n idlResolver?: IdlResolver;\n\n /**\n * Pre-registered IDLs to hand to a freshly-built resolver. Ignored when\n * `idlResolver` is provided.\n */\n idls?: Record<string, AnchorIdl>;\n\n /** Finality commitment used for the enrichment fetch. Default 'confirmed'. */\n commitment?: Finality;\n\n /** Max wall-time before the span is ended with status=timeout. Default 30000ms. */\n timeoutMs?: number;\n\n /** Base poll interval for retrying getTransaction. Default 500ms. */\n pollIntervalMs?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * **Non-wrapping observation helper.** Given a transaction signature that\n * was already sent through any `Connection`, fetch the on-chain record,\n * parse the logs, and emit a single OpenTelemetry span describing the\n * transaction.\n *\n * Unlike `InstrumentedConnection`, this helper **never touches the send\n * path** — it only observes after the fact via `getTransaction(signature)`.\n * Use this when:\n *\n * - You want observability for wallet-adapter flows where the send path\n * goes through the wallet and you can't easily override the Connection\n * - You're security-conscious and don't want any SDK code on the critical\n * transaction path\n * - You want to instrument a handful of high-value transactions rather\n * than every call a Connection makes\n *\n * Usage:\n *\n * import { trackSolanaTransaction } from '@thesight/sdk';\n * import { Connection } from '@solana/web3.js';\n *\n * const connection = new Connection(rpcUrl);\n * const signature = await wallet.sendTransaction(tx, connection);\n *\n * // Fire-and-forget. Span flushes on its own.\n * void trackSolanaTransaction({\n * signature,\n * connection,\n * serviceName: 'checkout',\n * idls: { [programId]: idl },\n * });\n *\n * Returns a `Promise<void>` — typically not awaited, but callers who want\n * to wait for the span to export (e.g. short-lived scripts) can await it.\n */\nexport async function trackSolanaTransaction(opts: TrackTransactionOptions): Promise<void> {\n const tracerName = opts.serviceName ?? '@thesight/sdk';\n const tracer = opts.tracer ?? trace.getTracer(tracerName);\n\n const span = tracer.startSpan('solana.trackTransaction', {}, context.active());\n span.setAttribute('solana.tx.signature', opts.signature);\n\n const start = Date.now();\n const deadline = start + (opts.timeoutMs ?? 30_000);\n const commitment = opts.commitment ?? ('confirmed' as Finality);\n const basePoll = opts.pollIntervalMs ?? 500;\n\n const resolver = opts.idlResolver ?? buildResolverFromIdls(opts.idls);\n\n try {\n const txDetails = await pollGetTransaction(opts.connection, opts.signature, commitment, deadline, basePoll);\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n return;\n }\n\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, resolver);\n\n for (const attr of flatAttributions(cpiTree)) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n span.end();\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction buildResolverFromIdls(idls?: Record<string, AnchorIdl>): IdlResolver {\n const resolver = new IdlResolver();\n if (idls) resolver.registerMany(idls);\n return resolver;\n}\n\nasync function pollGetTransaction(\n connection: Connection,\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await connection.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n return null;\n}\n"],"mappings":"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","names":["trace","context","SpanStatusCode","parseLogs","IdlResolver","enrichTree","flatAttributions","IdlResolver","trace","context","SpanStatusCode","parseLogs","enrichTree","flatAttributions"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/exporter.ts","../src/dsn.ts","../src/track.ts"],"sourcesContent":["import {\n Connection,\n type ConnectionConfig,\n type SendOptions,\n type Commitment,\n type Finality,\n type TransactionSignature,\n type VersionedTransactionResponse,\n} from '@solana/web3.js';\nimport {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl, CpiTree } from '@thesight/core';\n\n// ─── Config ────────────────────────────────────────────────────────────────\n\nexport interface SightConfig {\n /** OTel tracer. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Override RPC endpoint for IDL fetching (defaults to same as connection) */\n idlRpcEndpoint?: string;\n\n /**\n * Commitment used when fetching confirmed tx details for enrichment.\n * Defaults to 'confirmed'.\n */\n commitment?: Commitment;\n\n /**\n * Skip IDL resolution entirely. Spans will still emit with signature and\n * timing attributes, but program names, instruction names, CPI trees, and\n * decoded errors will all be omitted. Useful when you want minimal\n * overhead and don't care about the richer observability.\n */\n skipIdlResolution?: boolean;\n\n /**\n * Pre-register IDLs at construction time. Keys are program IDs, values are\n * full Anchor IDL objects. Anchor users can pass `program.idl` directly off\n * their `Program` instance — zero setup friction.\n */\n idls?: Record<string, AnchorIdl>;\n\n /**\n * Opt into reading Anchor IDL accounts from the on-chain PDA as a fallback\n * when a program is not explicitly registered. Defaults to **false** —\n * Sight's model is that IDLs are explicitly provided by the application,\n * not silently guessed from chain.\n */\n allowOnChainIdlFetch?: boolean;\n\n /**\n * Max wall-time the background enrichment task will wait for a\n * transaction to show up in `getTransaction`. After this expires the\n * span is ended with `solana.tx.status = 'timeout'`. Default 30000 (30s).\n */\n enrichmentTimeoutMs?: number;\n\n /**\n * How often the enrichment task polls `getTransaction`. Each retry backs\n * off by 1.5x up to a cap. Default 500ms base.\n */\n enrichmentPollIntervalMs?: number;\n\n /**\n * Disable automatic span creation in the overridden `sendRawTransaction`.\n * When true, InstrumentedConnection behaves like a plain Connection. You\n * probably don't want this — it's here as an escape hatch for tests and\n * rare cases where you need the class hierarchy but not the tracing.\n */\n disableAutoSpan?: boolean;\n}\n\n// ─── Span attribute shape (documented, for downstream consumers) ──────────\n\nexport interface SightSpanAttributes {\n 'solana.tx.signature': string;\n 'solana.tx.status': 'submitted' | 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.fee_lamports'?: number;\n 'solana.tx.submit_ms': number;\n 'solana.tx.enrichment_ms'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.error_msg'?: string;\n}\n\n// ─── InstrumentedConnection ────────────────────────────────────────────────\n\n/**\n * Drop-in replacement for `@solana/web3.js`'s `Connection` that emits an\n * OpenTelemetry span for **every** call to `sendRawTransaction` —\n * regardless of whether the caller is your own code, an Anchor provider's\n * `sendAndConfirm`, `@solana/web3.js`'s top-level `sendAndConfirmTransaction`,\n * a wallet adapter, or anything else.\n *\n * Internally it overrides the parent class's `sendRawTransaction`, so the\n * span covers the same surface web3.js already mediates. No mutation of\n * the transaction bytes — we call `super.sendRawTransaction(rawTx, opts)`\n * verbatim and observe the signature it returns.\n *\n * After the signature is returned (synchronously from the caller's point\n * of view), a background task polls `getTransaction` until the on-chain\n * record is available, parses the program logs via `@thesight/core`, and\n * enriches the span with CU attribution, per-CPI events, program + instruction\n * names, and decoded error details. The background task never blocks the\n * caller — if it fails or times out, the span ends with status=timeout and\n * whatever partial data was collected.\n *\n * Usage:\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({ dsn: process.env.SIGHT_DSN!, serviceName: 'swap-bot' });\n *\n * // Anywhere you currently construct a Connection, construct this instead:\n * const connection = new InstrumentedConnection(rpcUrl, {\n * commitment: 'confirmed',\n * });\n *\n * // All of the following now emit spans automatically:\n * await connection.sendRawTransaction(tx.serialize());\n * await sendAndConfirmTransaction(connection, tx, signers); // web3.js\n * await program.methods.foo().rpc(); // Anchor\n * await wallet.sendTransaction(tx, connection); // wallet adapter\n */\nexport class InstrumentedConnection extends Connection {\n private sightConfig: SightConfig;\n private idlResolver: IdlResolver;\n private tracer: Tracer;\n\n constructor(endpoint: string, config?: ConnectionConfig & SightConfig) {\n const {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n idls,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs,\n enrichmentPollIntervalMs,\n disableAutoSpan,\n commitment,\n ...connectionConfig\n } = config ?? {};\n\n super(endpoint, connectionConfig);\n\n this.sightConfig = {\n tracer,\n idlRpcEndpoint,\n skipIdlResolution,\n allowOnChainIdlFetch,\n enrichmentTimeoutMs: enrichmentTimeoutMs ?? 30_000,\n enrichmentPollIntervalMs: enrichmentPollIntervalMs ?? 500,\n disableAutoSpan,\n commitment,\n };\n this.idlResolver = new IdlResolver({\n rpcEndpoint: idlRpcEndpoint ?? endpoint,\n allowOnChainFetch: allowOnChainIdlFetch ?? false,\n });\n if (idls) this.idlResolver.registerMany(idls);\n this.tracer = tracer ?? trace.getTracer('@thesight/sdk');\n }\n\n /**\n * Register an IDL for a single program. Convenience forwarder for the\n * underlying resolver. Anchor users typically call this right after\n * constructing a `Program`:\n *\n * const program = new Program(idl, programId, provider);\n * connection.registerIdl(program.programId.toBase58(), program.idl);\n */\n registerIdl(programId: string, idl: AnchorIdl): void {\n this.idlResolver.register(programId, idl);\n }\n\n /** Bulk-register multiple IDLs. */\n registerIdls(idls: Record<string, AnchorIdl>): void {\n this.idlResolver.registerMany(idls);\n }\n\n // ─── Overridden method ────────────────────────────────────────────────────\n\n /**\n * Overrides the parent `Connection.sendRawTransaction` so every submit —\n * including those made by Anchor's `provider.sendAndConfirm`, web3.js's\n * top-level `sendAndConfirmTransaction`, and wallet adapter flows — is\n * observed by an OTel span automatically.\n *\n * The span is a child of whatever OTel context is active when the call\n * fires, so app-level parent spans (HTTP handlers, job runs, wallet flow\n * spans from another SDK) nest the Sight span correctly.\n */\n override async sendRawTransaction(\n rawTransaction: Buffer | Uint8Array | Array<number>,\n options?: SendOptions,\n ): Promise<TransactionSignature> {\n if (this.sightConfig.disableAutoSpan) {\n return super.sendRawTransaction(rawTransaction, options);\n }\n\n const span = this.tracer.startSpan('solana.sendRawTransaction', {}, context.active());\n const submitStart = Date.now();\n\n let signature: TransactionSignature;\n try {\n signature = await super.sendRawTransaction(rawTransaction, options);\n } catch (err) {\n const submitMs = Date.now() - submitStart;\n span.setAttribute('solana.tx.submit_ms', submitMs);\n span.setAttribute('solana.tx.status', 'failed');\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n span.end();\n throw err;\n }\n\n span.setAttribute('solana.tx.signature', signature);\n span.setAttribute('solana.tx.submit_ms', Date.now() - submitStart);\n span.setAttribute('solana.tx.status', 'submitted');\n\n // Fire-and-forget background enrichment. The returned Promise is\n // intentionally discarded — we don't want to couple the caller's\n // transaction-send latency to our observation machinery. Any enrichment\n // error is caught inside enrichSpanInBackground and attached to the\n // span, not bubbled up.\n void this.enrichSpanInBackground(span, signature, submitStart);\n\n return signature;\n }\n\n // ─── Background enrichment ───────────────────────────────────────────────\n\n /**\n * Poll `getTransaction` until the on-chain record is available, then\n * enrich the span with CU attribution, program names, decoded errors,\n * and per-CPI events.\n *\n * This runs async and is never awaited by callers of `sendRawTransaction`.\n * Failures inside this method attach to the span via recordException\n * rather than throwing.\n */\n private async enrichSpanInBackground(\n span: Span,\n signature: TransactionSignature,\n submitStart: number,\n ): Promise<void> {\n const enrichStart = Date.now();\n const deadline = enrichStart + (this.sightConfig.enrichmentTimeoutMs ?? 30_000);\n const commitment = (this.sightConfig.commitment ?? 'confirmed') as Finality;\n const basePollMs = this.sightConfig.enrichmentPollIntervalMs ?? 500;\n\n try {\n const txDetails = await this.pollForTransaction(signature, commitment, deadline, basePollMs);\n\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n span.end();\n return;\n }\n\n this.attachTxDetailsToSpan(span, txDetails);\n\n if (!this.sightConfig.skipIdlResolution) {\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n await this.attachParsedLogsToSpan(span, logs);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - submitStart);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setAttribute(\n 'solana.tx.enrichment_error',\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n span.end();\n }\n }\n\n /**\n * Poll `getTransaction(signature)` until either the on-chain record is\n * returned or the deadline passes. Exponential backoff (1.5x) capped at\n * 2 seconds to balance responsiveness against RPC load.\n */\n private async pollForTransaction(\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n ): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await super.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff. Transient RPC errors are common\n // during the seconds immediately after submission.\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await sleep(waitMs);\n }\n return null;\n }\n\n /** Attach the flat fields (slot, fee, CU, status) from a tx response to a span. */\n private attachTxDetailsToSpan(span: Span, txDetails: VersionedTransactionResponse): void {\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n }\n\n /**\n * Parse program logs into a CPI tree, enrich with registered IDLs, and\n * emit one `cpi.invoke` event per invocation onto the span. Root\n * program/instruction names are copied onto span attributes so dashboards\n * can filter by them without walking events.\n */\n private async attachParsedLogsToSpan(span: Span, logs: string[]): Promise<CpiTree | undefined> {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, this.idlResolver);\n\n const attributions = flatAttributions(cpiTree);\n for (const attr of attributions) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n\n return cpiTree;\n }\n}\n\n// ─── Utilities ────────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ─── Re-exports ────────────────────────────────────────────────────────────\n\nexport { IdlResolver } from '@thesight/core';\nexport type {\n CpiTree,\n CuAttribution,\n FlamegraphItem,\n DecodedError,\n AnchorIdl,\n} from '@thesight/core';\n\n// ─── Tracing initialization ──────────────────────────────────────────────────\n\nexport { initSight } from './init.js';\nexport type { InitSightConfig, SightTracerHandle } from './init.js';\nexport { SightSpanExporter } from './exporter.js';\nexport type { SightExporterConfig } from './exporter.js';\n\n// ─── DSN helpers ─────────────────────────────────────────────────────────\n\nexport { parseDsn, buildDsn } from './dsn.js';\nexport type { ParsedDsn } from './dsn.js';\n\n// ─── Non-wrapping observation helper ─────────────────────────────────────\n\nexport { trackSolanaTransaction } from './track.js';\nexport type { TrackTransactionOptions } from './track.js';\n","import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { Resource } from '@opentelemetry/resources';\nimport { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { SightSpanExporter } from './exporter.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface InitSightConfig {\n /**\n * Sight DSN — one string encoding both the ingest endpoint and the API key.\n *\n * Format: `https://<apiKey>@<host>/ingest`\n *\n * Get yours from the Sight dashboard when you create a project. For local\n * dev against a Docker compose stack: `http://sk_live_xxx@localhost:3001/ingest`\n */\n dsn: string;\n\n /**\n * Human-readable service name that shows up on every span. Pick something\n * that identifies this process — `'swap-bot'`, `'market-maker'`,\n * `'trade-settler'`, etc.\n */\n serviceName: string;\n\n /** Optional version string for the service. Useful for correlating spans with a deploy. */\n serviceVersion?: string;\n\n /** BatchSpanProcessor flush interval, in ms. Defaults to 5s. */\n batchDelayMs?: number;\n\n /** Max spans per HTTP POST. Clamped to 100 by the exporter. */\n maxBatchSize?: number;\n\n /** Inject a fetch implementation for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface SightTracerHandle {\n provider: NodeTracerProvider;\n /** Flush any pending spans and release the batch processor. */\n shutdown: () => Promise<void>;\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\n/**\n * Initialize Sight tracing. Call this **once** near the top of your\n * process entry point.\n *\n * import { initSight, InstrumentedConnection } from '@thesight/sdk';\n *\n * initSight({\n * dsn: process.env.SIGHT_DSN!,\n * serviceName: 'swap-bot',\n * });\n *\n * const connection = new InstrumentedConnection(process.env.RPC_URL!);\n * // Every sendRawTransaction call now emits a span automatically.\n *\n * Registers a NodeTracerProvider as the global OTel tracer. Context\n * propagation uses Node async hooks so spans nest correctly across\n * await boundaries.\n */\nexport function initSight(config: InitSightConfig): SightTracerHandle {\n if (!config.dsn) {\n throw new Error(\n 'initSight: `dsn` is required. ' +\n 'Get your DSN from the Sight dashboard: https://thesight.dev',\n );\n }\n if (!config.serviceName) {\n throw new Error('initSight: `serviceName` is required');\n }\n\n const resource = new Resource({\n [SEMRESATTRS_SERVICE_NAME]: config.serviceName,\n ...(config.serviceVersion\n ? { [SEMRESATTRS_SERVICE_VERSION]: config.serviceVersion }\n : {}),\n });\n\n const provider = new NodeTracerProvider({ resource });\n\n const exporter = new SightSpanExporter({\n dsn: config.dsn,\n fetchImpl: config.fetchImpl,\n maxBatchSize: config.maxBatchSize,\n });\n\n const processor = new BatchSpanProcessor(exporter, {\n scheduledDelayMillis: config.batchDelayMs ?? 5_000,\n maxExportBatchSize: config.maxBatchSize ?? 100,\n maxQueueSize: 2048,\n });\n\n provider.addSpanProcessor(processor);\n provider.register();\n\n return {\n provider,\n shutdown: async () => {\n await provider.shutdown();\n },\n };\n}\n","import type { SpanAttributes } from '@opentelemetry/api';\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n hrTimeToMilliseconds,\n ExportResultCode,\n type ExportResult,\n} from '@opentelemetry/core';\nimport { parseDsn } from './dsn.js';\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\nexport interface SightExporterConfig {\n /**\n * Sight DSN — one string encoding both the ingest endpoint and the API key.\n * Format: https://<apiKey>@<host>/ingest\n */\n dsn: string;\n /** Inject a fake fetch in tests. Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Max spans per POST. Ingest enforces an upper bound of 100. */\n maxBatchSize?: number;\n}\n\n// ─── Exporter ────────────────────────────────────────────────────────────────\n\n/**\n * An OTel `SpanExporter` that translates OTel spans to Sight's custom\n * ingest payload shape and POSTs them to `/ingest`.\n *\n * Each span becomes one object in the `spans` array. Solana-specific\n * attributes flow through as-is (the set that `InstrumentedConnection`\n * already populates — `solana.tx.signature`, `solana.tx.cu_used`,\n * `solana.tx.program`, etc). The exporter intentionally does not attempt\n * to translate span events or links — the ingest schema doesn't have\n * slots for those, and we prefer dropping silently over guessing.\n *\n * The BatchSpanProcessor upstream handles retries, timeouts, and batching;\n * this exporter stays a thin wire-format translator.\n */\nexport class SightSpanExporter implements SpanExporter {\n private readonly apiKey: string;\n private readonly ingestUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly maxBatchSize: number;\n private shuttingDown = false;\n\n constructor(config: SightExporterConfig) {\n const parsed = parseDsn(config.dsn);\n this.apiKey = parsed.apiKey;\n this.ingestUrl = parsed.ingestUrl;\n this.fetchImpl = config.fetchImpl ?? (globalThis.fetch as typeof fetch);\n this.maxBatchSize = Math.min(100, config.maxBatchSize ?? 100);\n\n if (!this.fetchImpl) {\n throw new Error(\n 'SightSpanExporter: globalThis.fetch is not available. Node >= 18 ships ' +\n 'with fetch built in, or pass `fetchImpl` explicitly.',\n );\n }\n }\n\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n if (this.shuttingDown) {\n resultCallback({ code: ExportResultCode.FAILED, error: new Error('Exporter is shut down') });\n return;\n }\n\n // Split into chunks if the upstream processor handed us a larger\n // batch than ingest will accept. Uncommon with a well-configured\n // BatchSpanProcessor but cheap to handle defensively.\n const chunks: ReadableSpan[][] = [];\n for (let i = 0; i < spans.length; i += this.maxBatchSize) {\n chunks.push(spans.slice(i, i + this.maxBatchSize));\n }\n\n try {\n for (const chunk of chunks) {\n await this.sendChunk(chunk);\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (err) {\n resultCallback({\n code: ExportResultCode.FAILED,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n async shutdown(): Promise<void> {\n this.shuttingDown = true;\n }\n\n async forceFlush(): Promise<void> {\n // The exporter is stateless — BatchSpanProcessor's own forceFlush\n // drains the queue before calling export(). Nothing to do here.\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private async sendChunk(spans: ReadableSpan[]): Promise<void> {\n const payload = {\n spans: spans.map((span) => this.convertSpan(span)),\n };\n\n const response = await this.fetchImpl(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const body = await safeReadText(response);\n throw new Error(\n `Sight ingest returned ${response.status} ${response.statusText}: ${body}`,\n );\n }\n }\n\n private convertSpan(span: ReadableSpan): IngestSpan {\n const attr = span.attributes;\n const resource = span.resource.attributes;\n const serviceName = typeof resource['service.name'] === 'string'\n ? resource['service.name']\n : undefined;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n const endTimeMs = hrTimeToMilliseconds(span.endTime);\n const durationMs = Math.max(0, endTimeMs - startTimeMs);\n\n const out: IngestSpan = {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n spanName: span.name,\n startTimeMs,\n durationMs,\n };\n\n const parentSpanId = (span as { parentSpanId?: string }).parentSpanId;\n if (parentSpanId) out.parentSpanId = parentSpanId;\n if (serviceName) out.serviceName = serviceName;\n\n copyIfString(attr, 'solana.tx.signature', out);\n copyIfEnum(attr, 'solana.tx.status', out, ['submitted', 'confirmed', 'failed', 'timeout']);\n copyIfNumber(attr, 'solana.tx.slot', out);\n copyIfNumber(attr, 'solana.tx.cu_used', out);\n copyIfNumber(attr, 'solana.tx.cu_budget', out);\n copyIfNumber(attr, 'solana.tx.cu_utilization', out);\n copyIfString(attr, 'solana.tx.program', out);\n copyIfString(attr, 'solana.tx.instruction', out);\n copyIfString(attr, 'solana.tx.error', out);\n copyIfNumber(attr, 'solana.tx.error_code', out);\n copyIfString(attr, 'solana.tx.error_program', out);\n copyIfNumber(attr, 'solana.tx.submit_ms', out);\n copyIfNumber(attr, 'solana.tx.confirmation_ms', out);\n copyIfNumber(attr, 'solana.tx.fee_lamports', out);\n\n return out;\n }\n}\n\n// ─── Wire format (narrow, matches ingest's zod schema) ──────────────────────\n\ninterface IngestSpan {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n serviceName?: string;\n spanName: string;\n startTimeMs: number;\n durationMs: number;\n\n 'solana.tx.signature'?: string;\n 'solana.tx.status'?: 'confirmed' | 'failed' | 'timeout';\n 'solana.tx.slot'?: number;\n 'solana.tx.cu_used'?: number;\n 'solana.tx.cu_budget'?: number;\n 'solana.tx.cu_utilization'?: number;\n 'solana.tx.program'?: string;\n 'solana.tx.instruction'?: string;\n 'solana.tx.error'?: string;\n 'solana.tx.error_code'?: number;\n 'solana.tx.error_program'?: string;\n 'solana.tx.submit_ms'?: number;\n 'solana.tx.confirmation_ms'?: number;\n 'solana.tx.fee_lamports'?: number;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction copyIfString(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'string') {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfNumber(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n): void {\n const v = attr[key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nfunction copyIfEnum<T extends string>(\n attr: SpanAttributes,\n key: keyof IngestSpan & string,\n out: IngestSpan,\n allowed: readonly T[],\n): void {\n const v = attr[key];\n if (typeof v === 'string' && (allowed as readonly string[]).includes(v)) {\n (out as unknown as Record<string, unknown>)[key] = v;\n }\n}\n\nasync function safeReadText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '<no body>';\n }\n}\n","/**\n * Parse a Sight DSN into its constituent parts.\n *\n * DSN format:\n * https://<apiKey>@<host>[:<port>]/<path>\n *\n * Examples:\n * https://sk_live_abc123@ingest.thesight.dev/ingest (production)\n * http://sk_live_abc123@localhost:3001/ingest (local dev)\n *\n * The API key sits in the URL's username position. The SDK extracts it\n * and sends it as a Bearer token to the ingest URL (with the key removed\n * from the URL). This mirrors the Sentry DSN model — one string encodes\n * both \"where to send\" and \"who you are\", so there's no separate\n * apiKey + ingestUrl to misconfig independently.\n */\n\nexport interface ParsedDsn {\n /** The API key extracted from the DSN's username position. */\n apiKey: string;\n /** The ingest endpoint URL with the API key stripped out. */\n ingestUrl: string;\n}\n\n/**\n * Parse a DSN string into `{ apiKey, ingestUrl }`.\n *\n * Throws with a clear message on:\n * - Malformed URL\n * - Missing API key (no username in the URL)\n */\nexport function parseDsn(dsn: string): ParsedDsn {\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n throw new Error(\n `Invalid Sight DSN: \"${dsn}\". ` +\n 'Expected format: https://<apiKey>@<host>/ingest — ' +\n 'get your DSN from the Sight dashboard when you create a project.',\n );\n }\n\n const apiKey = decodeURIComponent(url.username);\n if (!apiKey) {\n throw new Error(\n `Invalid Sight DSN: no API key found in \"${dsn}\". ` +\n 'The API key goes in the username position: https://sk_live_xxx@host/ingest',\n );\n }\n\n // Strip the credentials so the ingest URL is clean for HTTP requests.\n url.username = '';\n url.password = '';\n const ingestUrl = url.toString().replace(/\\/$/, ''); // trim trailing slash\n\n return { apiKey, ingestUrl };\n}\n\n/**\n * Build a DSN from its parts. Used by the dashboard when generating the\n * DSN for a newly-created project.\n */\nexport function buildDsn(apiKey: string, ingestHost: string): string {\n const url = new URL(ingestHost);\n url.username = apiKey;\n if (!url.pathname || url.pathname === '/') {\n url.pathname = '/ingest';\n }\n return url.toString();\n}\n","import type { Connection, Finality, TransactionSignature, VersionedTransactionResponse } from '@solana/web3.js';\nimport { trace, context, SpanStatusCode, type Tracer } from '@opentelemetry/api';\nimport { parseLogs, IdlResolver, enrichTree, flatAttributions } from '@thesight/core';\nimport type { AnchorIdl } from '@thesight/core';\n\n// ─── Options ──────────────────────────────────────────────────────────────\n\nexport interface TrackTransactionOptions {\n /** The signature returned from a prior `sendRawTransaction` / `sendTransaction` call. */\n signature: TransactionSignature;\n\n /**\n * Any `@solana/web3.js` Connection — plain or instrumented. Used to fetch\n * the on-chain transaction via `getTransaction` for enrichment. Does\n * **not** need to be an `InstrumentedConnection`; this helper intentionally\n * never touches the transaction-send path.\n */\n connection: Connection;\n\n /**\n * Service name used for the span. Falls back to the service name\n * configured by `initSight`. Useful when you want a different service\n * name for a specific subsystem's spans.\n */\n serviceName?: string;\n\n /** OTel tracer override. Defaults to `trace.getTracer('@thesight/sdk')`. */\n tracer?: Tracer;\n\n /** Optional IDL resolver for program/error decoding. A fresh one is built if omitted. */\n idlResolver?: IdlResolver;\n\n /**\n * Pre-registered IDLs to hand to a freshly-built resolver. Ignored when\n * `idlResolver` is provided.\n */\n idls?: Record<string, AnchorIdl>;\n\n /** Finality commitment used for the enrichment fetch. Default 'confirmed'. */\n commitment?: Finality;\n\n /** Max wall-time before the span is ended with status=timeout. Default 30000ms. */\n timeoutMs?: number;\n\n /** Base poll interval for retrying getTransaction. Default 500ms. */\n pollIntervalMs?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * **Non-wrapping observation helper.** Given a transaction signature that\n * was already sent through any `Connection`, fetch the on-chain record,\n * parse the logs, and emit a single OpenTelemetry span describing the\n * transaction.\n *\n * Unlike `InstrumentedConnection`, this helper **never touches the send\n * path** — it only observes after the fact via `getTransaction(signature)`.\n * Use this when:\n *\n * - You want observability for wallet-adapter flows where the send path\n * goes through the wallet and you can't easily override the Connection\n * - You're security-conscious and don't want any SDK code on the critical\n * transaction path\n * - You want to instrument a handful of high-value transactions rather\n * than every call a Connection makes\n *\n * Usage:\n *\n * import { trackSolanaTransaction } from '@thesight/sdk';\n * import { Connection } from '@solana/web3.js';\n *\n * const connection = new Connection(rpcUrl);\n * const signature = await wallet.sendTransaction(tx, connection);\n *\n * // Fire-and-forget. Span flushes on its own.\n * void trackSolanaTransaction({\n * signature,\n * connection,\n * serviceName: 'checkout',\n * idls: { [programId]: idl },\n * });\n *\n * Returns a `Promise<void>` — typically not awaited, but callers who want\n * to wait for the span to export (e.g. short-lived scripts) can await it.\n */\nexport async function trackSolanaTransaction(opts: TrackTransactionOptions): Promise<void> {\n const tracerName = opts.serviceName ?? '@thesight/sdk';\n const tracer = opts.tracer ?? trace.getTracer(tracerName);\n\n const span = tracer.startSpan('solana.trackTransaction', {}, context.active());\n span.setAttribute('solana.tx.signature', opts.signature);\n\n const start = Date.now();\n const deadline = start + (opts.timeoutMs ?? 30_000);\n const commitment = opts.commitment ?? ('confirmed' as Finality);\n const basePoll = opts.pollIntervalMs ?? 500;\n\n const resolver = opts.idlResolver ?? buildResolverFromIdls(opts.idls);\n\n try {\n const txDetails = await pollGetTransaction(opts.connection, opts.signature, commitment, deadline, basePoll);\n if (!txDetails) {\n span.setAttribute('solana.tx.status', 'timeout');\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n return;\n }\n\n span.setAttribute('solana.tx.status', txDetails.meta?.err ? 'failed' : 'confirmed');\n span.setAttribute('solana.tx.slot', txDetails.slot);\n\n const fee = txDetails.meta?.fee;\n if (fee !== undefined) span.setAttribute('solana.tx.fee_lamports', fee);\n\n const cuUsed = txDetails.meta?.computeUnitsConsumed;\n if (cuUsed !== undefined && cuUsed !== null) {\n span.setAttribute('solana.tx.cu_used', Number(cuUsed));\n span.setAttribute('solana.tx.cu_budget', 200_000);\n span.setAttribute(\n 'solana.tx.cu_utilization',\n parseFloat((Number(cuUsed) / 200_000 * 100).toFixed(1)),\n );\n }\n\n const logs = txDetails.meta?.logMessages ?? [];\n if (logs.length > 0) {\n const { cpiTree } = parseLogs({ logs });\n await enrichTree(cpiTree, resolver);\n\n for (const attr of flatAttributions(cpiTree)) {\n span.addEvent('cpi.invoke', {\n 'cpi.program': attr.programName ?? attr.programId,\n 'cpi.instruction': attr.instructionName ?? 'unknown',\n 'cpi.depth': attr.depth,\n 'cpi.cu_consumed': attr.cuConsumed,\n 'cpi.cu_self': attr.cuSelf,\n 'cpi.percentage': parseFloat(attr.percentage.toFixed(2)),\n });\n }\n\n const root = cpiTree.roots[0];\n if (root) {\n if (root.programName) span.setAttribute('solana.tx.program', root.programName);\n if (root.instructionName) span.setAttribute('solana.tx.instruction', root.instructionName);\n }\n }\n\n span.setAttribute('solana.tx.enrichment_ms', Date.now() - start);\n\n if (txDetails.meta?.err) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n span.end();\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction buildResolverFromIdls(idls?: Record<string, AnchorIdl>): IdlResolver {\n const resolver = new IdlResolver();\n if (idls) resolver.registerMany(idls);\n return resolver;\n}\n\nasync function pollGetTransaction(\n connection: Connection,\n signature: TransactionSignature,\n commitment: Finality,\n deadline: number,\n basePollMs: number,\n): Promise<VersionedTransactionResponse | null> {\n let attempt = 0;\n while (Date.now() < deadline) {\n try {\n const tx = await connection.getTransaction(signature, {\n commitment,\n maxSupportedTransactionVersion: 0,\n });\n if (tx) return tx;\n } catch {\n // Ignore — retry with backoff\n }\n attempt++;\n const waitMs = Math.min(basePollMs * Math.pow(1.5, attempt - 1), 2_000);\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n return null;\n}\n"],"mappings":"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","names":["trace","context","SpanStatusCode","parseLogs","IdlResolver","enrichTree","flatAttributions","IdlResolver","trace","context","SpanStatusCode","parseLogs","enrichTree","flatAttributions"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thesight/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sight SDK — OpenTelemetry-native observability for Solana. One call (initSight) wires a NodeTracerProvider + Sight exporter, then InstrumentedConnection turns every transaction into a span with per-CPI compute-unit attribution, decoded Anchor errors, and wallet-to-program correlation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@thesight/core": "^0.2.0",
41
+ "@thesight/core": "^0.3.0",
42
42
  "@solana/web3.js": "^1.95.0",
43
43
  "@opentelemetry/api": "^1.8.0",
44
44
  "@opentelemetry/core": "^1.24.0",