npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thesight/sdk

v0.11.2

Published

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

Readme

@thesight/sdk

See the truth of your Solana program.

Sight is the first OpenTelemetry-native observability SDK for Solana. One package install, one initSight() call, and every transaction becomes a span with per-CPI compute-unit attribution, decoded Anchor errors, and full wallet↔RPC↔program correlation — consumable by the Sight dashboard or any APM backend (Datadog, Grafana, Honeycomb, Tempo).

Install

pnpm add @thesight/sdk
# or npm install @thesight/sdk
# or yarn add @thesight/sdk

All OpenTelemetry dependencies are bundled transitively — you do not need to pnpm add @opentelemetry/* separately.

Ships both ESM and CJS builds. Works from modern TypeScript projects, Node ESM, legacy CJS via require(), bundlers, and serverless runtimes.

Quickstart — InstrumentedConnection (automatic spans)

The simplest pattern: swap your Connection for InstrumentedConnection and every transaction you send through it gets a span automatically — no matter whether you're calling it directly, going through an Anchor provider.sendAndConfirm, using web3.js's top-level sendAndConfirmTransaction, or letting a wallet adapter drive the flow.

import { initSight, InstrumentedConnection } from '@thesight/sdk';
import { clusterApiUrl } from '@solana/web3.js';

// Call once near the top of your process entry point.
// Get your DSN from the Sight dashboard when you create a project.
initSight({
  dsn:         process.env.SIGHT_DSN!,
  serviceName: 'my-service',
});

// Drop-in replacement for @solana/web3.js Connection
const connection = new InstrumentedConnection(clusterApiUrl('mainnet'));

// Anywhere a Solana tx goes out, a span goes with it:
await connection.sendRawTransaction(tx.serialize());
// or...
await sendAndConfirmTransaction(connection, tx, signers);
// or...
const program = new Program(idl, programId, new AnchorProvider(connection, wallet, {}));
await program.methods.foo().rpc();
// or...
await wallet.sendTransaction(tx, connection);

Under the hood, InstrumentedConnection overrides the single method every Solana send-path eventually calls — sendRawTransaction — so you get spans regardless of which layer is driving the transaction.

What the span does, step by step

  1. On the synchronous submit path, a span named solana.sendRawTransaction starts as a child of whatever OTel context is active.
  2. super.sendRawTransaction(rawTx, options) is called verbatim — no byte mutation, no interception of signing, no private key access.
  3. The returned signature is attached to the span and the caller gets it back immediately.
  4. In the background (never blocking the caller), a task polls getTransaction(signature) with exponential backoff until the on-chain record is available, parses the program logs into a CPI tree via @thesight/core, enriches with registered Anchor IDLs, and adds cpi.invoke events, per-program CU attribution, and decoded error details to the span.
  5. The span ends and flushes via the Sight exporter configured by initSight.

Next.js

Sight's initSight() requires Node's native require to load OTel modules at runtime — this conflicts with Next.js's webpack bundler if you initialize Sight inside a Server Component or API route directly.

The easiest fix is wrapping your next.config.js with the provided helper, which tells Next.js to leave Sight and OTel out of its bundles:

// next.config.js
const { withSight } = require('@thesight/sdk/next');

module.exports = withSight({
  // ...your existing config
});

Then call initSight as normal in your server code or API routes.

Alternative: instrumentation.ts

Next.js also supports an instrumentation.ts file (app root) that runs in Node before webpack bundling. No config wrapper needed at all:

// instrumentation.ts  (at the root of your Next.js app, next to package.json)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { initSight } = await import('@thesight/sdk');
    initSight({
      dsn:         process.env.SIGHT_DSN!,
      serviceName: 'my-app',
    });
  }
}

Enable it in next.config.js:

module.exports = {
  experimental: { instrumentationHook: true },
};

Note: instrumentation.ts is stable in Next.js 15+ and no longer requires experimental.instrumentationHook.


Alternative — trackSolanaTransaction (non-wrapping)

If you don't want to wrap your Connection at all, trackSolanaTransaction observes by signature alone after you've already sent the transaction through any connection:

import { Connection } from '@solana/web3.js';
import { initSight, trackSolanaTransaction } from '@thesight/sdk';

initSight({ dsn: process.env.SIGHT_DSN!, serviceName: 'checkout' });

// Use your normal Connection, unchanged.
const connection = new Connection(rpcUrl);
const signature  = await wallet.sendTransaction(tx, connection);

// Fire-and-forget — span flushes on its own in the background.
void trackSolanaTransaction({
  signature,
  connection,
  serviceName: 'checkout',
  idls: { [swapProgramId]: swapIdl },
});

This helper never touches the send path. It only observes the transaction after the fact by calling getTransaction(signature). Recommended when:

  • You're security-conscious and don't want any SDK code on the critical transaction path
  • Your send path goes through a wallet adapter or a wrapper SDK you don't control
  • You want to instrument a specific high-value transaction rather than everything a connection sends

Custom user-defined spans

sight.span() wraps a unit of work and emits it as an OTel span with your own name + tags. Auto-nests sequentially — a sight.span inside another sight.span's callback becomes its child in the trace tree without needing zone.js or a registered OTel context manager.

import { sight } from '@thesight/sdk';

await sight.span('place-bet', async (s) => {
  s.tag({ market: 'sol-perp', size: 10, leverage: 3 });
  const quote  = await fetchQuote();
  const signed = await wallet.signTransaction(tx);
  return connection.sendRawTransaction(signed.serialize());
});

Errors thrown inside the callback mark the span as errored (OTel status.code = ERROR, sight.error = true) and re-throw so your own error handling still runs. For concurrent nesting (Promise.all) pass opts.parent explicitly since the active-span stack is sequential- only.

Wallet sign instrumentation — three integration tiers

Sight exposes three ways to get wallet.sign spans and decoded error kinds (user_rejected, simulation_failed, timeout, etc) out of Phantom / Backpack / Solflare / MWA signing flows. Each tier trades ergonomics for trust surface — pick what matches your threat model.

The doctrine Sight enforces in all three: the SDK never mutates transaction bytes, never intercepts signing, never recommends skipPreflight: true, and never changes send-path semantics. The tiers differ only in how much SDK code sits between your code and the wallet adapter — not in what that code does.

Tier 1 — useSightWallet() (turnkey, heaviest)

Drop-in replacement for @solana/wallet-adapter-react's useWallet. Every sign method on the returned object is wrapped in a wallet.sign / wallet.sign_all / wallet.sign_and_send / wallet.sign_message span, tagged with wallet.name, wallet.method, and wallet.error.kind on failure.

// Before
import { useWallet } from '@solana/wallet-adapter-react';
// After
import { useSightWallet as useWallet } from '@thesight/sdk/react';

function PlaceBetButton() {
  const { signTransaction } = useWallet();     // now auto-instrumented
  return <button onClick={async () => {
    const signed = await signTransaction!(tx); // span emitted here
    ...
  }}>Bet</button>;
}

What the SDK does on the sign path: receives the unsigned transaction, calls wallet.signTransaction(tx) verbatim with byte-identical input, returns the signed result unchanged. No mutation, no inspection, no keypair access.

Trust surface: the wrapper is on the wallet method — a future compromised release could in principle see the unsigned tx before the wallet. Mitigate with lockfile pinning, npm publish --provenance once the source is public, and reading the ~100 LoC implementation in packages/sdk/src/wallet.tsx.

Tier 2 — traceWalletSign() (helper, lighter)

Thunk-based — the caller passes a zero-arg function; Sight wraps the invocation, not the wallet. The SDK never receives a reference to the wallet adapter or to the unsigned transaction.

import { traceWalletSign } from '@thesight/sdk';

const signed = await traceWalletSign(
  () => wallet.signTransaction(tx),
  { method: 'signTransaction', walletName: wallet.wallet?.adapter?.name },
);

What the SDK does: starts a span, runs your closure (which dereferences wallet in your scope — the SDK never sees it), passes the result through by identity, classifies any thrown error via decodeWalletError, re-throws verbatim.

Trust surface: strictly smaller than useSightWallet — the SDK can't read or modify the transaction because you invoked the wallet yourself inside a closure it doesn't receive. The SDK sees only the result (to pass through) or the error (to classify).

Tier 3 — Roll your own with sight.span + decodeWalletError

No helper at all — compose the primitives yourself. This is literally what traceWalletSign does internally; using it directly means the only code paths that run are ones you can see line-by-line at the call site.

import { sight, decodeWalletError } from '@thesight/sdk';

const signed = await sight.span('wallet.sign', async (s) => {
  s.tag({ 'wallet.method': 'signTransaction', 'wallet.name': walletName });
  try {
    return await wallet.signTransaction(tx);
  } catch (err) {
    s.tag('wallet.error.kind', decodeWalletError(err).kind);
    throw err;
  }
});

What the SDK does:

  • sight.span — creates an OTel span, pushes it on the active-span stack, runs your callback, pops on exit. No tx interaction.
  • decodeWalletError — pure function on the thrown Error object's shape (message, code, attached logs arrays). Returns a kind string
    • optional sim logs. Never reads transaction state.

Trust surface: the minimum possible while still emitting decoded spans. You control every line that touches the wallet — Sight code only runs inside the sight.span wrapper and the decodeWalletError classifier on the error object.

Read the source:

Which tier should I use?

| Priority | Pick | |---|---| | Fastest onboarding, minimal code changes | Tier 1 useSightWallet | | Ergonomic + light touch, a good default | Tier 2 traceWalletSign | | Security-conscious / regulated / audit-prone | Tier 3 manual | | Already have your own tracing framework | Tier 3 (just use decodeWalletError) |

All three emit the same wallet.error.kind taxonomy, so dashboard filters and alert rules written against one work for all three.

Anchor IDL registration

Sight never guesses program names from chain. Tell it what you know:

import { Program } from '@coral-xyz/anchor';

const program = new Program(idl, programId, provider);
connection.registerIdl(program.programId.toBase58(), program.idl);

Or pass a map at init:

const connection = new InstrumentedConnection(rpcUrl, {
  idls: {
    [swapProgramId]: swapIdl,
    [lendingProgramId]: lendingIdl,
  },
});

Unknown programs surface as short-ids in the dashboard rather than being fabricated.

Self-host the ingest

By default spans export to https://ingest.thesight.dev/ingest. Override via ingestUrl to point at a self-hosted Sight ingest (or a local Docker stack for development):

initSight({
  dsn:         'http://sk_live_...@localhost:3007',
  serviceName: 'my-service',
});

What's on the span

Every instrumented transaction produces an OTel span with the following attributes when they're available:

  • solana.tx.signature — base58 signature
  • solana.tx.statussubmittedconfirmed / failed / timeout
  • solana.tx.slot
  • solana.tx.cu_used / solana.tx.cu_budget / solana.tx.cu_utilization
  • solana.tx.program — root program name (from registered IDL or the curated known-programs list)
  • solana.tx.instruction — extracted from Program log: Instruction: …
  • solana.tx.error / solana.tx.error_code / solana.tx.error_program — decoded from on-chain return data via the registered IDL's error table or the hardcoded native-error tables
  • error.reason — short human-readable failure string (e.g. "Insufficient funds", "Transaction expired. Please try again.", the extracted Anchor Error Message:). Set on failed sendRawTransaction and useSightWallet sign/send spans. Sight interprets for dashboard filter chips only; the original thrown error is re-thrown untouched so your UI code owns its own banner copy.
  • solana.tx.fee_lamports
  • solana.tx.submit_ms / solana.tx.enrichment_ms — client-observed latencies
  • Per-CPI cpi.invoke events on the span: program, instruction, depth, CU consumed, CU self, percentage

Configuration

InstrumentedConnection accepts these extra options alongside the standard web3.js ConnectionConfig:

new InstrumentedConnection(rpcUrl, {
  // Standard ConnectionConfig fields work as before
  commitment: 'confirmed',

  // Sight-specific
  tracer:                    customTracer,
  skipIdlResolution:         false,  // skip log parsing + IDL decoding
  idls:                      { [programId]: idl },
  allowOnChainIdlFetch:      false,  // off by default — never guess
  enrichmentTimeoutMs:       30_000, // background enrichment wall time
  enrichmentPollIntervalMs:  500,    // base poll interval with 1.5x backoff
  disableAutoSpan:           false,  // escape hatch for tests
});

Shutdown

Call shutdown() on the handle returned by initSight at graceful shutdown time so pending spans flush before the process exits:

const sight = initSight({ dsn: process.env.SIGHT_DSN!, serviceName });

process.on('SIGTERM', async () => {
  await sight.shutdown();
  process.exit(0);
});

License

MIT — see LICENSE.