Solana Tracker LogoSolana Tracker
Swap
Developers
⌘K
Affiliate

Products

  • Data API
  • Pump.fun API
  • Solana RPC
  • Dedicated Nodes
  • Yellowstone gRPC
  • Raptor Swap API
  • Enterprise

Trading

  • Swap
  • Latest Tokens
  • Trending
  • Top Gainers
  • Memescope
  • Whale Watch
  • KOL Tracker

Tools

  • Wallet Tracker
  • Rugcheck
  • PnL Leaderboard
  • KOLScan
  • Axiom Leaderboard
  • Photon Leaderboard
  • Bloom Leaderboard
  • FOMO Leaderboard
  • GMGN Leaderboard
  • Pump.fun App Leaderboard
  • Terminal Leaderboard
  • Platform Compare
  • My Positions
  • Teams

Resources

  • Developer Guides
  • Blog
  • Documentation
  • API Reference
  • Status
  • Affiliate Program — 25% recurring, uncapped
Solana TrackerSolana Tracker© 2026
Terms of ServicePrivacy PolicyContact
←Back to blog
typescript code examplesolanatypescript sdksolana web3crypto api

Solana API: Practical TypeScript Code Example for Node.js

Discover TypeScript code examples for Solana crypto APIs in Node.js. Master RPC, WebSockets, token swaps, data fetching, and type safety for robust dApps 2026.

September 7, 2026/17 min read

Table of contents

  • Introduction and Setup
  • Keep secrets and imports predictable
  • Overview of Solana Tracker APIs
  • Connecting to Solana RPC
  • Separate declarations from implementation
  • Subscribing to WebSocket Streams
  • Account and program subscriptions
  • Logs and signatures
  • Performing Token Swaps
  • Treat approval as a policy boundary
  • Fetching Token and Wallet Data
  • Ensuring Type Safety with Runtime Checks
  • Use generic constraints where repetition is real
  • Quick Reference Tables
  • Choose the narrowest return type
  • Cross-Reference to Advanced Patterns
  • Glossary of Key Terms
Solana API: Practical TypeScript Code Example for Node.js

You're debugging a Solana dashboard because the wallet balance looks valid in development, then a malformed API payload breaks the production worker. The fix usually isn't another clever type alias. It's a small, explicit TypeScript code example that separates RPC access, runtime validation, and transaction logic.

Solana applications sit between strongly typed application code and data that arrives dynamically from wallets, RPC nodes, WebSocket feeds, third-party APIs, and untrusted token projects. The patterns below keep that boundary narrow. Each example is intentionally concise, crypto-focused, and designed for Node.js services that need predictable behavior without turning every function into a generic-type puzzle.

Introduction and Setup

Start with a small Node.js project and keep infrastructure code separate from trading and analytics logic. Install TypeScript, a runner such as tsx, and the official Solana client:

npm install @solana/web3.js
npm install -D typescript tsx @types/node

Solana's official JavaScript and TypeScript SDK is @solana/web3.js. The Solana documentation describes it as the legacy TypeScript SDK and points developers toward related packages such as @solana/spl-token for Token and Token-2022 workflows.

Use strict compiler settings from the beginning:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "esModuleInterop": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

strict catches unsafe assumptions. noUncheckedIndexedAccess is particularly useful when reading token arrays, account lists, or event batches, because an array lookup can legitimately produce undefined.

Keep secrets and imports predictable

A practical layout might look like this:

src/
  config.ts
  rpc.ts
  types.ts
  validate.ts
  swaps.ts

Keep the RPC endpoint in an environment variable rather than embedding it in source:

const rpcUrl = process.env.SOLANA_RPC_URL;
if (!rpcUrl) throw new Error("SOLANA_RPC_URL is required");

Use a funded signer only in a protected backend or local test environment. Public keys, token mints, and transaction signatures can be logged more freely, but private keys and API credentials shouldn't enter source control or client bundles.

Practical rule: TypeScript protects the code you compile. It doesn't prove that a wallet response, token account, or JSON payload is honest at runtime.

Overview of Solana Tracker APIs

A Solana application usually needs several kinds of access, and each one solves a different problem.

API category Best fit Typical crypto task
RPC Direct chain interaction Read balances, submit transactions, inspect slots
WebSocket Datastream Live updates Track trades, wallet activity, prices, or launches
Data API Indexed queries Load token, wallet, market, and historical data
Raptor Swap Programmatic execution Request routed swap transactions
Risk analytics Decision support Inspect token and wallet risk signals before trading

RPC is the primitive layer. Use it when your code needs Solana-native operations such as getBalance, getAccountInfo, transaction simulation, or transaction submission. It gives you chain access, but it doesn't automatically provide a convenient market-data model.

WebSocket feeds fit applications that must react continuously. A trading interface can subscribe to activity instead of polling. A wallet monitor can process account changes as they arrive. Data APIs are more suitable for indexed views, where the application needs token metadata, wallet history, or market information without reconstructing every record from raw chain data.

Raptor Swap belongs in the execution path. Your service can request a route, validate the returned transaction data, obtain the required approval, and submit the transaction through an RPC connection. Risk analytics should remain a separate decision layer. A quote tells you how a swap may execute. It doesn't tell you whether the token or wallet behavior is acceptable.

A hand-drawn diagram illustrating the Solana ecosystem with core services like RPC, Data API, and Raptor Swap.

Solana Tracker combines these categories through a trading and developer platform, while @solana/web3.js remains the direct SDK choice for native Solana calls. Pick the narrowest interface that matches the job. Don't use a WebSocket stream to answer a historical query, and don't treat an indexed Data API response as a signed transaction.

Connecting to Solana RPC

The smallest useful RPC wrapper should create one connection and expose domain-specific functions. That keeps endpoint configuration out of every call site.

import { Connection, PublicKey, type Commitment } from "@solana/web3.js";

const endpoint = process.env.SOLANA_RPC_URL;
if (!endpoint) throw new Error("SOLANA_RPC_URL is required");

const commitment: Commitment = "confirmed";
export const connection = new Connection(endpoint, commitment);

export async function readWalletBalance(address: string) {
  const owner = new PublicKey(address);
  const lamports = await connection.getBalance(owner);
  return { address, lamports };
}

export async function readBlockTime(slot: number) {
  const blockTime = await connection.getBlockTime(slot);
  return { slot, blockTime };
}

getBalance returns lamports as a number, while getBlockTime can return null when the node has no timestamp for the slot. Preserve that possibility instead of forcing a value with a cast.

Handle failures at the boundary:

export async function safeBalance(address: string) {
  try {
    return await readWalletBalance(address);
  } catch (error) {
    const message = error instanceof Error ? error.message : "RPC request failed";
    throw new Error(`Unable to read ${address}: ${message}`);
  }
}

This wrapper adds context without pretending that every error is recoverable. Retry transport failures in a higher-level worker, but don't blindly retry invalid public keys or rejected transactions.

Separate declarations from implementation

A .d.ts file should describe a contract, not contain executable behavior:

export interface WalletBalance {
  address: string;
  lamports: number;
}

export declare function readWalletBalance(
  address: string
): Promise<WalletBalance>;

TypeScript's declaration-file guidance states that .d.ts files contain only type information, emit no JavaScript, and exist for type-checking. For a package, point consumers to bundled declarations with the types field in package.json.

For a direct Node service, a normal .ts implementation is usually clearer than an ambient declaration. Use .d.ts when describing an external module, a global injected by a runtime, or a published package contract. The Solana RPC documentation is the right place to compare endpoint capabilities before selecting a provider.

Subscribing to WebSocket Streams

A subscription is an event source, not a database. The handler should validate or normalize incoming data, update application state, and remain safe to call repeatedly.

The official Solana WebSocket interface includes accountSubscribe, programSubscribe, logsSubscribe, blockSubscribe, rootSubscribe, and signatureSubscribe. These cover account changes, program-owned accounts, transaction logs, block notifications, root slots, and transaction confirmation respectively, as documented in the Solana WebSocket RPC reference.

Account and program subscriptions

import { PublicKey } from "@solana/web3.js";
import { connection } from "./rpc.js";

const owner = new PublicKey(process.env.WALLET_ADDRESS!);

const accountSubscription = connection.onAccountChange(
  owner,
  ({ lamports }) => {
    console.log({ wallet: owner.toBase58(), lamports });
  },
  "confirmed"
);

const programSubscription = connection.onProgramAccountChange(
  new PublicKey(process.env.PROGRAM_ID!),
  ({ accountId, accountInfo }) => {
    console.log({
      account: accountId.toBase58(),
      lamports: accountInfo.lamports
    });
  },
  "confirmed"
);

The callback types come from @solana/web3.js, so you don't need to recreate the SDK's account structures. You should still decode program-specific account data separately. Raw bytes aren't a business object until the correct layout and discriminator have been checked.

Logs and signatures

const logsSubscription = connection.onLogs(
  "all",
  ({ signature, logs, err }) => {
    if (err) return console.error("Failed transaction", signature);
    console.log({ signature, logs });
  },
  "confirmed"
);

const signature = "TRANSACTION_SIGNATURE";
const confirmationSubscription = connection.onSignature(
  signature,
  ({ err }) => console.log({ signature, confirmed: !err }),
  "confirmed"
);

Use a program filter when your provider supports it, rather than processing every transaction in a backend worker. For an account monitor, deduplicate by signature or slot before writing to storage. For a long-running process, keep the subscription IDs and remove them during shutdown:

async function closeStreams() {
  await connection.removeAccountChangeListener(accountSubscription);
  await connection.removeProgramAccountChangeListener(programSubscription);
  await connection.removeOnLogsListener(logsSubscription);
  await connection.removeSignatureListener(confirmationSubscription);
}

process.once("SIGTERM", () => void closeStreams());

WebSocket connections can drop. Reconnect with bounded backoff, resubscribe after reconnecting, and treat the stream as a live view that may need reconciliation through RPC. Don't assume an event was received merely because the socket was previously open.

Performing Token Swaps

Swap execution has three distinct stages: obtain a route, verify the transaction request, and sign only after policy checks pass. The exact Raptor Swap SDK method names depend on the installed client version, so keep the provider adapter isolated instead of spreading provider-specific calls through your application.

A typed application-level request can stay small:

type SwapRequest = {
  inputMint: string;
  outputMint: string;
  amount: string;
  slippageBps: number;
  userPublicKey: string;
};

type SwapQuote = {
  transaction: string;
  inputMint: string;
  outputMint: string;
  amount: string;
  minOutputAmount: string;
};

The adapter can expose a stable interface:

interface SwapRouter {
  quote(request: SwapRequest): Promise<SwapQuote>;
}

Your route validation should reject mismatched mints, empty transaction payloads, invalid public keys, and unacceptable slippage policy before signing:

import { PublicKey } from "@solana/web3.js";

function validateSwap(request: SwapRequest) {
  new PublicKey(request.inputMint);
  new PublicKey(request.outputMint);
  new PublicKey(request.userPublicKey);

  if (!/^\d+$/.test(request.amount)) {
    throw new Error("Amount must be an integer string");
  }

  if (!Number.isInteger(request.slippageBps) || request.slippageBps < 0) {
    throw new Error("Invalid slippage policy");
  }
}

A backend can then compose the flow:

async function prepareSwap(
  router: SwapRouter,
  request: SwapRequest
): Promise<SwapQuote> {
  validateSwap(request);
  const quote = await router.quote(request);

  if (!quote.transaction) {
    throw new Error("Router returned no transaction");
  }

  if (quote.inputMint !== request.inputMint) {
    throw new Error("Router input mint mismatch");
  }

  return quote;
}

Treat approval as a policy boundary

Never interpret a successful quote as permission to spend. The signer or wallet must approve the transaction, and the application should display the input mint, output mint, amount, expected minimum output, and recipient before signing.

Signing rule: A quote is data. A serialized transaction is an instruction. Keep user approval between the two.

After signing, submit through sendRawTransaction, then confirm using the returned signature and the same commitment policy used elsewhere in the service. Handle rejected approvals, expired blockhashes, simulation failures, and slippage errors as separate outcomes so the UI can give a useful response.

For production routing, an aggregator such as Raptor Swap can sit behind the SwapRouter interface. That makes it possible to test policy and transaction handling without coupling every domain function to one provider.

Fetching Token and Wallet Data

Indexed APIs suit dashboards because they return prepared views instead of requiring the application to rebuild history from raw transactions. The endpoint name matters less than the contract: define the fields the UI consumes, validate the response at runtime, and retain unknown fields rather than assigning them an incorrect meaning.

type TokenOverview = {
  mint: string;
  symbol?: string;
  name?: string;
  priceUsd?: number;
};

async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
  const response = await fetch(url, init);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status} from token API`);
  }
  return response.json() as Promise<T>;
}

The generic return type improves editor support and downstream code, but it does not validate JSON. Apply a runtime parser before trusting the result. Keep the API function narrow so callers receive a checked token object:

export async function getTokenOverview(
  baseUrl: string,
  mint: string,
  apiKey: string
) {
  const data = await fetchJson<unknown>(
    `${baseUrl}/tokens/${encodeURIComponent(mint)}`,
    { headers: { Authorization: `Bearer ${apiKey}` } }
  );

  return parseTokenOverview(data);
}

Normalize wallet balances before rendering. Token accounts may use different decimal scales, so raw integer units and display values must remain separate:

type Balance = {
  mint: string;
  rawAmount: string;
  decimals: number;
};

function displayAmount(balance: Balance): string {
  const raw = BigInt(balance.rawAmount);
  const scale = 10n ** BigInt(balance.decimals);
  const whole = raw / scale;
  const fraction = (raw % scale).toString().padStart(balance.decimals, "0");
  return `${whole}.${fraction}`.replace(/\.?0+$/, "");
}

Store raw quantities as strings or bigint. JavaScript numbers can lose precision for large balances, and formatting code must never change the amount later submitted in a transaction. This separation keeps wallet views readable while preserving exact values for crypto workflows.

Ensuring Type Safety with Runtime Checks

A TypeScript annotation doesn't transform an API response. This code compiles, but it can still fail:

const token = await fetchJson<TokenOverview>(url);
console.log(token.mint);

The server could return an object without mint, a number encoded as a string, or an entirely different error payload with a successful HTTP status. The gap between static types and runtime data is especially important for API responses, event payloads, and forms, an issue highlighted in practical TypeScript generics guidance.

A small type guard works when the shape is simple:

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function parseTokenOverview(value: unknown): TokenOverview {
  if (!isRecord(value) || typeof value.mint !== "string") {
    throw new Error("Invalid token overview");
  }

  return {
    mint: value.mint,
    symbol: typeof value.symbol === "string" ? value.symbol : undefined,
    name: typeof value.name === "string" ? value.name : undefined,
    priceUsd: typeof value.priceUsd === "number" ? value.priceUsd : undefined
  };
}

This parser deliberately treats optional fields as optional. It doesn't invent a symbol or coerce an arbitrary value into a price.

Use generic constraints where repetition is real

A generic helper makes sense when several endpoints share an envelope:

type ApiEnvelope<T> = {
  data: T;
  nextPage?: string;
};

function unwrap<T>(payload: ApiEnvelope<T>): T {
  return payload.data;
}

The generic expresses a stable relationship. It doesn't claim that unknown JSON already matches ApiEnvelope<T>. Parse the envelope first, then pass the validated value to unwrap.

Avoid any for convenience. TypeScript's official style guidance recommends primitive lowercase types such as string and number, discourages broad any except during legacy migration, and favors the simplest type that accurately expresses the code. Prefer a union when a parameter accepts a small set of modes, and don't create overloads when one union-typed function communicates the behavior clearly.

Runtime checks should also cover transaction policy. Validate mint identity, amount format, signer ownership, and expected destination before a wallet prompt. Type safety is valuable here because it makes the checks visible, but the actual protection comes from executing those checks against runtime values.

Quick Reference Tables

Use this table as a compact lookup while writing a Solana service. The return descriptions reflect the application-level meaning you should preserve, including nullable values and asynchronous behavior.

Action Method Parameters Returns
Read wallet balance connection.getBalance PublicKey, optional commitment Promise<number> lamports
Read block time connection.getBlockTime slot number Promise<number | null>
Read account data connection.getAccountInfo PublicKey, optional commitment account info or null
Send a signed transaction connection.sendRawTransaction serialized transaction bytes Promise<string> signature
Confirm a transaction connection.confirmTransaction signature or confirmation strategy confirmation response
Watch one account connection.onAccountChange public key, callback, commitment numeric listener ID
Watch program accounts connection.onProgramAccountChange program public key, callback, commitment numeric listener ID
Watch transaction logs connection.onLogs filter, callback, commitment numeric listener ID
Watch a signature connection.onSignature signature, callback, commitment numeric listener ID
Remove account stream connection.removeAccountChangeListener listener ID Promise<void>
Fetch token data Data API client or fetch mint and request options validated token model
Fetch wallet data Data API client or fetch wallet address and pagination validated wallet model
Request a swap route Raptor Swap adapter input mint, output mint, amount, slippage, owner typed quote or transaction request
Format token amount local domain function raw amount and decimals display string

Choose the narrowest return type

Don't expose a raw RPC response to every part of your application. Convert it at the edge:

type WalletSnapshot = {
  address: string;
  lamports: number;
  observedAt: Date;
};

async function walletSnapshot(address: string): Promise<WalletSnapshot> {
  const result = await readWalletBalance(address);
  return {
    address: result.address,
    lamports: result.lamports,
    observedAt: new Date()
  };
}

The domain model is easier to test and less likely to leak provider-specific details into UI code. For paginated APIs, return both the records and the next cursor rather than hiding pagination inside an unbounded loop.

type Page<T> = {
  items: T[];
  nextCursor?: string;
};

That shape keeps backpressure under the caller's control. A worker can persist one page, retry it, and continue from the cursor without loading an entire wallet history into memory.

Cross-Reference to Advanced Patterns

A wallet monitor, token dashboard, or controlled swap service can start with compact snippets. Production Solana applications need stricter boundaries for decoding, signing, risk checks, and event recovery.

For program accounts, decode raw bytes only after verifying ownership and the expected layout. Anchor projects can use generated clients and account types. Lower-level clients should keep decoding behind a typed adapter, returning a domain model instead of exposing provider-specific data across the application. Short, focused patterns are available in the Solana web3.js examples repository.

Model every required signer explicitly:

type Approval = {
  publicKey: string;
  role: "payer" | "owner" | "delegate";
  approved: boolean;
};

Approval should reflect the transaction message, signer role, and user-facing policy. A connected wallet alone does not prove that the required authorization is complete.

Run risk checks before execution. Solana Tracker provides a unified data API, real-time WebSocket feeds, RPC access, Raptor Swap routing, and tools including Rugcheck and wallet tracking. Keep these capabilities in separate adapters, so a risk response cannot be treated as a swap quote or RPC confirmation.

Use a durable event pipeline for high-throughput ingestion. Persist the signature, slot, source, and processing status, then reconcile missed events with an indexed query or RPC read. WebSocket callbacks provide delivery signals, not durable state. This separation lets workers retry safely and keeps dynamic blockchain data within explicit TypeScript types.

Glossary of Key Terms

  • RPC: A request interface for reading Solana state and submitting transactions.
  • WebSocket: A persistent connection used to receive live blockchain notifications.
  • Subscription: A registered WebSocket listener for account, program, log, block, root, or signature events.
  • Public key: The address that identifies a wallet, token mint, account, or program.
  • Mint: The Solana account that defines a token and its supply rules.
  • Lamport: The smallest unit used to represent SOL in RPC responses.
  • Slippage: The allowed difference between expected and executed swap pricing.
  • Runtime validation: Checks performed on actual values while the program runs.
  • Generic constraint: A rule limiting which types a generic function can accept.
  • Ambient declaration: Type information declared externally in a .d.ts file.
  • Paginated response: Data returned in batches, usually with a cursor for the next batch.
  • Type guard: A function that proves a value has a specific runtime shape to TypeScript.
  • Transaction signature: The identifier returned for a submitted Solana transaction.
  • Commitment: The confirmation level requested from an RPC provider.

Solana Tracker offers RPC infrastructure, indexed token and wallet data, live streams, Raptor Swap routing, and risk-analysis tools that fit the typed boundaries in these examples. Use Solana Tracker to explore the APIs and build your next Solana Node.js workflow with explicit validation from request to transaction confirmation.

More articles

Crypto WebSocket API Guide for Real-Time Trading
crypto websocket apisolana websocketreal-time crypto data

Crypto WebSocket API Guide for Real-Time Trading

September 6, 2026·14 min read
Real Time Crypto Data API: What It Is and How to Choose One
real time crypto data apicrypto apiwebsocket crypto api

Real Time Crypto Data API: What It Is and How to Choose One

September 5, 2026·13 min read
DeFi Trading Terminal: What It Is and How It Works
defi trading terminalsolana dexcrypto swap

DeFi Trading Terminal: What It Is and How It Works

September 4, 2026·16 min read