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

@allenhark/slipstream

v0.3.5

Published

Slipstream client SDK for TypeScript/JavaScript - Solana transaction relay

Downloads

1,361

Readme

allenhark.com

Slipstream TypeScript SDK

The official TypeScript/JavaScript client for AllenHark Slipstream, the high-performance Solana transaction relay and intelligence network.

npm License

Features

  • Discovery-based connection -- auto-discovers workers, no manual endpoint configuration
  • Leader-proximity routing -- real-time leader hints route transactions to the lowest-latency region
  • Multi-region support -- connect to workers across regions, auto-route based on leader schedule
  • QUIC transport -- binary protocol for lowest latency on server-side bots (Node.js)
  • 6 real-time streams -- leader hints, tip instructions, priority fees, latest blockhash, latest slot, transaction updates
  • Stream billing -- each stream costs 1 token; 1-hour reconnect grace period
  • Billing tiers -- Free (100 tx/day), Standard, Pro, Enterprise with tier-specific rate limits and pricing
  • Token billing -- check balance, deposit SOL, view usage and deposit history
  • Keep-alive & time sync -- background ping with RTT measurement and NTP-style clock synchronization
  • Protocol fallback -- QUIC -> WebSocket -> HTTP automatic fallback chain
  • Dual entry points -- @allenhark/slipstream (browser) and @allenhark/slipstream/node (server with QUIC)
  • Atomic bundles -- submit 2-5 transactions as a Jito-style atomic bundle
  • Solana RPC proxy -- 22 Solana JSON-RPC methods proxied through Slipstream (accounts, transactions, tokens, fees, cluster info)
  • Full TypeScript types -- complete type safety for all API surfaces
  • TPU submission -- send transactions directly to validator TPU ports via UDP, bypassing senders (0.0001 SOL per TX)

Installation

npm install @allenhark/slipstream

Requires Node.js 23+. Also works in modern browsers via WebSocket.

Quick Start

Browser / General (WebSocket + HTTP)

import { SlipstreamClient, configBuilder } from '@allenhark/slipstream';

const client = await SlipstreamClient.connect(
  configBuilder().apiKey('sk_live_your_key_here').build()
);

const result = await client.submitTransaction(signedTxBytes);
console.log(`TX: ${result.transactionId} (${result.status})`);

const balance = await client.getBalance();
console.log(`Balance: ${balance.balanceTokens} tokens`);

await client.disconnect();

Server-Side Bot (QUIC)

import { SlipstreamClient, configBuilder } from '@allenhark/slipstream/node';

// Automatically uses QUIC when available, otherwise falls back to WebSocket
const client = await SlipstreamClient.connect(
  configBuilder().apiKey('sk_live_your_key_here').build()
);
console.log(`Connected via ${client.connectionStatus().protocol}`); // 'quic'

Configuration

ConfigBuilder Reference

Use configBuilder() to construct a SlipstreamConfig. Only apiKey is required.

import { configBuilder, BackoffStrategy, PriorityFeeSpeed } from '@allenhark/slipstream';

const config = configBuilder()
  .apiKey('sk_live_your_key_here')
  .region('us-east')
  .tier('pro')
  .minConfidence(80)
  .build();

| Method | Type | Default | Description | |--------|------|---------|-------------| | apiKey(key) | string | required | API key (must start with sk_) | | region(region) | string | auto | Preferred region (e.g., 'us-east', 'eu-central') | | endpoint(url) | string | auto | Override discovery with explicit worker endpoint | | discoveryUrl(url) | string | https://discovery.allenhark.network | Custom discovery service URL | | tier(tier) | BillingTier | 'pro' | Billing tier: 'free', 'standard', 'pro', 'enterprise' | | connectionTimeout(ms) | number | 10000 | Connection timeout in milliseconds | | maxRetries(n) | number | 3 | Maximum retry attempts for failed requests | | leaderHints(bool) | boolean | true | Auto-subscribe to leader hint stream on connect | | streamTipInstructions(bool) | boolean | false | Auto-subscribe to tip instruction stream on connect | | streamPriorityFees(bool) | boolean | false | Auto-subscribe to priority fee stream on connect | | streamLatestBlockhash(bool) | boolean | false | Auto-subscribe to latest blockhash stream on connect | | streamLatestSlot(bool) | boolean | false | Auto-subscribe to latest slot stream on connect | | protocolTimeouts(obj) | ProtocolTimeouts | { quic: 2000, websocket: 3000, http: 5000 } | Per-protocol timeout in ms | | quicConfig(obj) | QuicConfig | see below | QUIC transport options (server-side only) | | priorityFee(obj) | PriorityFeeConfig | { enabled: false, speed: 'fast' } | Priority fee optimization (see below) | | retryBackoff(strategy) | BackoffStrategy | 'exponential' | Retry backoff: 'linear' or 'exponential' | | minConfidence(n) | number | 70 | Minimum confidence (0-100) for leader hint routing | | keepAlive(bool) | boolean | true | Enable background keep-alive ping loop | | keepAliveInterval(ms) | number | 5000 | Keep-alive ping interval in milliseconds | | idleTimeout(ms) | number | none | Disconnect after idle period | | webhookUrl(url) | string | none | HTTPS endpoint to receive webhook POST deliveries | | webhookEvents(events) | string[] | ['transaction.confirmed'] | Webhook event types to subscribe to | | webhookNotificationLevel(level) | string | 'final' | Transaction notification level: 'all', 'final', or 'confirmed' |

Billing Tiers

Each API key has a billing tier that determines transaction cost, rate limits, and priority queuing. Set the tier to match your API key's assigned tier:

const config = configBuilder()
  .apiKey('sk_live_your_key_here')
  .tier('pro')   // 'free' | 'standard' | 'pro' | 'enterprise'
  .build();

| Tier | Cost per TX | Cost per Stream | Rate Limit | Burst | Priority Slots | Daily Limit | |------|------------|-----------------|------------|-------|----------------|-------------| | free | 0 (counter) | 0 (counter) | 5 rps | 10 | 5 concurrent | 100 tx/day | | standard | 50,000 lamports (0.00005 SOL) | 50,000 lamports | 5 rps | 10 | 10 concurrent | Unlimited | | pro | 100,000 lamports (0.0001 SOL) | 50,000 lamports | 20 rps | 50 | 50 concurrent | Unlimited | | enterprise | 1,000,000 lamports (0.001 SOL) | 50,000 lamports | 100 rps | 200 | 200 concurrent | Unlimited |

  • Free tier: Uses a daily counter instead of token billing. Transactions and stream subscriptions both decrement the counter. Resets at UTC midnight.
  • Standard/Pro/Enterprise: Deducted from token balance per transaction. Stream subscriptions cost 1 token each with a 1-hour reconnect grace period.

PriorityFeeConfig

Controls automatic priority fee optimization for transactions.

const config = configBuilder()
  .apiKey('sk_live_your_key_here')
  .priorityFee({
    enabled: true,
    speed: PriorityFeeSpeed.UltraFast,
    maxTip: 0.01,  // Max 0.01 SOL
  })
  .build();

| Field | Type | Default | Description | |-------|------|---------|-------------| | enabled | boolean | false | Enable automatic priority fee optimization | | speed | PriorityFeeSpeed | 'fast' | Fee tier: 'slow', 'fast', or 'ultra_fast' | | maxTip | number | none | Maximum tip in SOL (caps the priority fee) |

PriorityFeeSpeed tiers:

| Speed | Compute Unit Price | Landing Probability | Use Case | |-------|-------------------|--------------------|---------| | 'slow' | Low | ~60-70% | Cost-sensitive, non-urgent transactions | | 'fast' | Medium | ~85-90% | Default balance of cost and speed | | 'ultra_fast' | High | ~95-99% | Time-critical trading, MEV protection |

ProtocolTimeouts

| Field | Type | Default | Description | |-------|------|---------|-------------| | quic | number | 2000 | QUIC connection timeout (ms) | | websocket | number | 3000 | WebSocket connection timeout (ms) | | http | number | 5000 | HTTP request timeout (ms) |

QuicConfig (Server-Side Only)

| Field | Type | Default | Description | |-------|------|---------|-------------| | timeout | number | 2000 | QUIC connection timeout (ms) | | keepAliveIntervalMs | number | 5000 | Transport-level keep-alive (ms) | | maxIdleTimeoutMs | number | 30000 | Max idle before disconnect (ms) | | insecure | boolean | false | Skip TLS cert verification (dev only) |

Protocol Fallback Chain

| Environment | Fallback Order | |-------------|---------------| | Server (/node) | QUIC (2s) -> WebSocket (3s) -> HTTP (5s) | | Browser (default) | WebSocket (3s) -> HTTP (5s) |


Connecting

Auto-Discovery (Recommended)

import { SlipstreamClient, configBuilder } from '@allenhark/slipstream';

// Minimal -- discovery finds the best worker
const client = await SlipstreamClient.connect(
  configBuilder().apiKey('sk_live_xxx').build()
);

// With region preference
const client = await SlipstreamClient.connect(
  configBuilder().apiKey('sk_live_xxx').region('us-east').build()
);

Direct Endpoint (Advanced)

const client = await SlipstreamClient.connect(
  configBuilder()
    .apiKey('sk_live_xxx')
    .endpoint('http://worker-ip:9000')
    .build()
);

Connection Info

const info = client.connectionInfo();
console.log(`Session: ${info.sessionId}`);
console.log(`Protocol: ${info.protocol}`);  // 'quic', 'ws', 'http'
console.log(`Region: ${info.region}`);
console.log(`Rate limit: ${info.rateLimit.rps} rps (burst: ${info.rateLimit.burst})`);

Transaction Submission

Basic Submit

const result = await client.submitTransaction(signedTxBytes);
console.log(`TX ID: ${result.transactionId}`);
console.log(`Status: ${result.status}`);
if (result.signature) {
  console.log(`Signature: ${result.signature}`);
}

Submit with Options

import { SubmitOptions } from '@allenhark/slipstream';

const result = await client.submitTransactionWithOptions(signedTxBytes, {
  broadcastMode: true,          // Fan-out to multiple regions
  preferredSender: 'nozomi',    // Prefer a specific sender
  maxRetries: 5,                // Override default retry count
  timeoutMs: 10000,             // Custom timeout
  dedupId: 'my-unique-id',     // Custom deduplication ID
});

SubmitOptions Fields

| Field | Type | Default | Description | |-------|------|---------|-------------| | broadcastMode | boolean | false | Fan-out to multiple regions simultaneously | | preferredSender | string | none | Prefer a specific sender (e.g., 'nozomi', '0slot') | | maxRetries | number | 2 | Retry attempts on failure | | timeoutMs | number | 30000 | Timeout per attempt in milliseconds | | dedupId | string | none | Custom deduplication ID (prevents double-submit) | | tpuSubmission | boolean | false | Send directly to validator TPU ports via UDP (bypasses senders, 0.0001 SOL per TX) |

TransactionResult Fields

| Field | Type | Description | |-------|------|-------------| | requestId | string | Internal request ID | | transactionId | string | Slipstream transaction ID | | signature | string? | Solana transaction signature (base58, when confirmed) | | status | TransactionStatus | Current status (see table below) | | slot | number? | Solana slot (when confirmed) | | timestamp | number | Unix timestamp in milliseconds | | routing | RoutingInfo? | Routing details (region, sender, latencies) | | error | TransactionError? | Error details (on failure) |

TransactionStatus Values

| Status | Description | |--------|-------------| | 'pending' | Received, not yet processed | | 'processing' | Being validated and routed | | 'sent' | Forwarded to sender | | 'confirmed' | Confirmed on Solana | | 'failed' | Failed permanently | | 'duplicate' | Deduplicated (already submitted) | | 'rate_limited' | Rate limit exceeded for your tier | | 'insufficient_tokens' | Token balance too low (or free tier daily limit reached) |

RoutingInfo Fields

| Field | Type | Description | |-------|------|-------------| | region | string | Region that handled the transaction | | sender | string | Sender service used | | routingLatencyMs | number | Time spent in routing logic (ms) | | senderLatencyMs | number | Time spent in sender submission (ms) | | totalLatencyMs | number | Total end-to-end latency (ms) |

TPU Submission

Send transactions directly to Solana validator TPU ports via UDP, bypassing external sender services. Targets the current leader + next 3 leaders for redundancy. Fire-and-forget -- no sender acknowledgment, but transactions are still tracked by signature for confirmation polling.

const result = await client.submitTransaction(txBytes, {
  tpuSubmission: true,
});
console.log(`Signature: ${result.signature}`);
// Use standard confirmation polling to check landing

TPU submission billing: 0.0001 SOL (100,000 lamports) per transaction -- separate from standard sender-based billing.


Sender Discovery

Fetch the list of configured senders with their tip wallets and pricing tiers. This is essential for building transactions in both broadcast and streaming modes.

Get Available Senders

const senders = await client.getSenders();

for (const sender of senders) {
  console.log(`${sender.displayName} (${sender.senderId})`);
  console.log(`  Tip wallets: ${sender.tipWallets.join(', ')}`);
  for (const tier of sender.tipTiers) {
    console.log(`  ${tier.name}: ${tier.amountSol} SOL (~${tier.expectedLatencyMs}ms)`);
  }
}

SenderInfo Fields

| Field | Type | Description | |-------|------|-------------| | senderId | string | Sender identifier (e.g., 'nozomi', '0slot') | | displayName | string | Human-readable name | | tipWallets | string[] | Solana wallet addresses for tips | | tipTiers | TipTier[] | Pricing tiers with speed/cost tradeoffs |

TipTier Fields

| Field | Type | Description | |-------|------|-------------| | name | string | Tier name (e.g., 'standard', 'fast', 'ultra_fast') | | amountSol | number | Tip amount in SOL | | expectedLatencyMs | number | Expected submission latency in milliseconds |


Atomic Bundles

Submit 2-5 transactions as a Jito-style atomic bundle. All transactions execute sequentially and atomically -- either all land or none do.

Basic Bundle

const txs = [tx1Bytes, tx2Bytes, tx3Bytes];
const result = await client.submitBundle(txs);
console.log(`Bundle ID: ${result.bundleId}`);
console.log(`Accepted: ${result.accepted}`);
for (const sig of result.signatures) {
  console.log(`  Signature: ${sig}`);
}

Bundle with Tip

// Explicit tip amount in lamports
const result = await client.submitBundle(txs, 100_000);
console.log(`Sender: ${result.senderId}`);

BundleResult Fields

| Field | Type | Description | |-------|------|-------------| | bundleId | string | Unique bundle identifier | | accepted | boolean | Whether the bundle was accepted by the sender | | signatures | string[] | Transaction signatures (base58) | | senderId | string? | Sender that processed the bundle | | error | string? | Error message if rejected |

Bundle Constraints

| Constraint | Value | |------------|-------| | Min transactions | 2 | | Max transactions | 5 | | Cost | 5 tokens (0.00025 SOL) flat rate per bundle | | Execution | Atomic -- all-or-nothing sequential execution | | Sender requirement | Must have supportsBundles enabled |


Streaming

Real-time data feeds over QUIC (binary on server) or WebSocket (browser). Subscribe with on(), unsubscribe with off().

Billing: Each stream subscription costs 1 token (0.00005 SOL). If the SDK reconnects within 1 hour for the same stream, no re-billing occurs (reconnect grace period). Free-tier keys deduct from the daily counter instead of tokens.

Leader Hints

Which region is closest to the current Solana leader. Emitted every 250ms when confidence >= threshold.

client.on('leaderHint', (hint) => {
  console.log(`Slot ${hint.slot}: best region = ${hint.preferredRegion}`);
  console.log(`  Leader: ${hint.leaderPubkey}`);
  console.log(`  Confidence: ${hint.confidence}%`);
  console.log(`  TPU RTT: ${hint.metadata.tpuRttMs}ms`);
  console.log(`  Backups: ${hint.backupRegions.join(', ')}`);
});

await client.subscribeLeaderHints();

LeaderHint Fields

| Field | Type | Description | |-------|------|-------------| | timestamp | number | Unix millis | | slot | number | Current slot | | expiresAtSlot | number | Slot when this hint expires | | preferredRegion | string | Best region for current leader | | backupRegions | string[] | Fallback regions in priority order | | confidence | number | Confidence score (0-100) | | leaderPubkey | string | Current leader validator pubkey | | metadata.tpuRttMs | number | RTT to leader's TPU from preferred region (ms) | | metadata.regionScore | number | Region quality score | | metadata.leaderTpuAddress | string? | Leader's TPU address (ip:port) | | metadata.regionRttMs | Record<string, number>? | Per-region RTT to leader |

Tip Instructions

Wallet address and tip amount for building transactions in streaming tip mode.

client.on('tipInstruction', (tip) => {
  console.log(`Sender: ${tip.senderName} (${tip.sender})`);
  console.log(`  Wallet: ${tip.tipWalletAddress}`);
  console.log(`  Amount: ${tip.tipAmountSol} SOL (tier: ${tip.tipTier})`);
  console.log(`  Latency: ${tip.expectedLatencyMs}ms, Confidence: ${tip.confidence}%`);
  for (const alt of tip.alternativeSenders) {
    console.log(`  Alt: ${alt.sender} @ ${alt.tipAmountSol} SOL`);
  }
});

await client.subscribeTipInstructions();

// Latest cached tip (no subscription required)
const latestTip = client.getLatestTip();

TipInstruction Fields

| Field | Type | Description | |-------|------|-------------| | timestamp | number | Unix millis | | sender | string | Sender ID | | senderName | string | Human-readable sender name | | tipWalletAddress | string | Tip wallet address (base58) | | tipAmountSol | number | Required tip amount in SOL | | tipTier | string | Tip tier name | | expectedLatencyMs | number | Expected submission latency (ms) | | confidence | number | Confidence score (0-100) | | validUntilSlot | number | Slot until which this tip is valid | | alternativeSenders | AlternativeSender[] | Alternative sender options ({ sender, tipAmountSol, confidence }) |

Priority Fees

Network-condition-based fee recommendations, updated every second.

client.on('priorityFee', (fee) => {
  console.log(`Speed: ${fee.speed}`);
  console.log(`  CU price: ${fee.computeUnitPrice} micro-lamports`);
  console.log(`  CU limit: ${fee.computeUnitLimit}`);
  console.log(`  Est cost: ${fee.estimatedCostSol} SOL`);
  console.log(`  Landing probability: ${fee.landingProbability}%`);
  console.log(`  Congestion: ${fee.networkCongestion}`);
  console.log(`  Recent success rate: ${(fee.recentSuccessRate * 100).toFixed(1)}%`);
});

await client.subscribePriorityFees();

PriorityFee Fields

| Field | Type | Description | |-------|------|-------------| | timestamp | number | Unix millis | | speed | string | Fee speed tier ('slow', 'fast', 'ultra_fast') | | computeUnitPrice | number | Compute unit price in micro-lamports | | computeUnitLimit | number | Recommended compute unit limit | | estimatedCostSol | number | Estimated total priority fee in SOL | | landingProbability | number | Estimated landing probability (0-100) | | networkCongestion | string | Network congestion level ('low', 'medium', 'high') | | recentSuccessRate | number | Recent success rate (0.0-1.0) |

Latest Blockhash

Streams the latest blockhash every 2 seconds. Build transactions without a separate RPC call.

client.on('latestBlockhash', (data) => {
  console.log(`Blockhash: ${data.blockhash}`);
  console.log(`  Valid until block height: ${data.lastValidBlockHeight}`);
});

await client.subscribeLatestBlockhash();

LatestBlockhash Fields

| Field | Type | Description | |-------|------|-------------| | blockhash | string | Latest blockhash (base58) | | lastValidBlockHeight | number | Last valid block height for this blockhash | | timestamp | number | Unix millis when fetched |

Latest Slot

Streams the current confirmed slot on every slot change (~400ms).

client.on('latestSlot', (data) => {
  console.log(`Current slot: ${data.slot}`);
});

await client.subscribeLatestSlot();

LatestSlot Fields

| Field | Type | Description | |-------|------|-------------| | slot | number | Current confirmed slot number | | timestamp | number | Unix millis |

Transaction Updates

Real-time status updates for submitted transactions.

client.on('transactionUpdate', (update) => {
  console.log(`TX ${update.transactionId}: ${update.status}`);
  if (update.routing) {
    console.log(`  Routed via ${update.routing.region} -> ${update.routing.sender}`);
  }
});

Auto-Subscribe on Connect

Enable streams at configuration time so they activate immediately:

const config = configBuilder()
  .apiKey('sk_live_your_key_here')
  .leaderHints(true)                  // default: true
  .streamTipInstructions(true)        // default: false
  .streamPriorityFees(true)           // default: false
  .streamLatestBlockhash(true)        // default: false
  .streamLatestSlot(true)             // default: false
  .build();

const client = await SlipstreamClient.connect(config);
// All 5 streams are active immediately -- just register listeners
client.on('leaderHint', onHint);
client.on('tipInstruction', onTip);
client.on('priorityFee', onFee);
client.on('latestBlockhash', onBlockhash);
client.on('latestSlot', onSlot);

All Events

| Event | Payload | Description | |-------|---------|-------------| | leaderHint | LeaderHint | Region recommendation update (every 250ms) | | tipInstruction | TipInstruction | Tip wallet/amount update | | priorityFee | PriorityFee | Priority fee recommendation (every 1s) | | latestBlockhash | LatestBlockhash | Latest blockhash (every 2s) | | latestSlot | LatestSlot | Current confirmed slot (~400ms) | | transactionUpdate | TransactionResult | Transaction status change | | connected | -- | Transport connected | | disconnected | -- | Transport disconnected | | ping | PingResult | Keep-alive ping result (RTT, clock offset) | | error | Error | Transport error |


Keep-Alive & Time Sync

Background keep-alive mechanism providing latency measurement and NTP-style clock synchronization.

// Enabled by default (5s interval)
const config = configBuilder()
  .apiKey('sk_live_your_key_here')
  .keepAlive(true)                // default: true
  .keepAliveInterval(5000)        // default: 5000ms
  .build();

const client = await SlipstreamClient.connect(config);

// Manual ping
const ping = await client.ping();
console.log(`RTT: ${ping.rttMs}ms, Clock offset: ${ping.clockOffsetMs}ms`);

// Derived measurements (median from sliding window of 10 samples)
const latency = client.latencyMs();     // number | null (RTT / 2)
const offset = client.clockOffsetMs();  // number | null
const serverNow = client.serverTime();  // number (unix ms, local time + offset)

// Listen for ping events
client.on('ping', (result) => {
  console.log(`Ping #${result.seq}: RTT ${result.rttMs}ms, offset ${result.clockOffsetMs}ms`);
});

PingResult Fields

| Field | Type | Description | |-------|------|-------------| | seq | number | Sequence number | | rttMs | number | Round-trip time in milliseconds | | clockOffsetMs | number | Clock offset: serverTime - (clientSendTime + rtt/2) (can be negative) | | serverTime | number | Server timestamp at time of pong (unix millis) |


Token Billing

Token-based billing system. Paid tiers (Standard/Pro/Enterprise) deduct tokens per transaction and stream subscription. Free tier uses a daily counter.

Billing Costs

| Operation | Cost | Notes | |-----------|------|-------| | Transaction submission | 1 token (0.00005 SOL) | Per transaction sent to Solana | | TPU submission | 0.0001 SOL (100,000 lamports) | Direct to validator TPU ports, bypasses senders | | Bundle submission | 5 tokens (0.00025 SOL) | Per bundle (2-5 transactions, flat rate) | | Stream subscription | 1 token (0.00005 SOL) | Per stream type; 1-hour reconnect grace period | | Webhook delivery | 0.00001 SOL (10,000 lamports) | Per successful POST delivery; retries not charged | | RPC query | 1 token (0.00005 SOL) | Per rpc() call (simulateTransaction, getTransaction, etc.) | | Bundle simulation | 1 token per TX (0.00005 SOL each) | simulateBundle() calls simulateTransaction for each TX | | Keep-alive ping | Free | Background ping/pong not billed | | Discovery | Free | GET /v1/discovery has no auth or billing | | Balance/billing queries | Free | getBalance(), getUsageHistory(), etc. | | Webhook management | Free | registerWebhook(), getWebhook(), deleteWebhook() not billed | | Free tier daily limit | 100 operations/day | Transactions + stream subs + webhook deliveries + RPC queries all count |

Token Economics

| Unit | Value | |------|-------| | 1 token | 0.00005 SOL = 50,000 lamports | | Initial balance | 200 tokens (0.01 SOL) per new API key | | Minimum deposit | 2,000 tokens (0.1 SOL / ~$10 USD) | | Grace period | -20 tokens (-0.001 SOL) before hard block |

Check Balance

const balance = await client.getBalance();
console.log(`SOL:    ${balance.balanceSol}`);
console.log(`Tokens: ${balance.balanceTokens}`);
console.log(`Lamports: ${balance.balanceLamports}`);
console.log(`Grace remaining: ${balance.graceRemainingTokens} tokens`);

Balance Fields

| Field | Type | Description | |-------|------|-------------| | balanceSol | number | Balance in SOL | | balanceTokens | number | Balance in tokens (1 token = 1 query) | | balanceLamports | number | Balance in lamports | | graceRemainingTokens | number | Grace tokens remaining before hard block |

Get Deposit Address

const deposit = await client.getDepositAddress();
console.log(`Send SOL to: ${deposit.depositWallet}`);
console.log(`Minimum: ${deposit.minAmountSol} SOL`);

Minimum Deposit

Deposits must reach $10 USD equivalent in SOL before tokens are credited. Deposits below this threshold accumulate as pending.

const minUsd = SlipstreamClient.getMinimumDepositUsd(); // 10.0

const pending = await client.getPendingDeposit();
if (pending.pendingCount > 0) {
  console.log(`${pending.pendingSol} SOL pending (${pending.pendingCount} deposits)`);
}

Usage History

const usage = await client.getUsageHistory({ limit: 50, offset: 0 });
for (const entry of usage) {
  console.log(`${entry.txType}: ${entry.amountLamports} lamports (balance: ${entry.balanceAfterLamports})`);
}

Deposit History

const deposits = await client.getDepositHistory({ limit: 20 });
for (const d of deposits) {
  console.log(`${d.amountSol} SOL | $${d.usdValue ?? 0} USD | ${d.credited ? 'CREDITED' : 'PENDING'}`);
}

Free Tier Usage

For free-tier API keys, check the daily usage counter:

const usage = await client.getFreeTierUsage();
console.log(`Used: ${usage.used}/${usage.limit}`);       // e.g. 42/100
console.log(`Remaining: ${usage.remaining}`);              // e.g. 58
console.log(`Resets at: ${usage.resetsAt}`);               // UTC midnight ISO string

FreeTierUsage Fields

| Field | Type | Description | |-------|------|-------------| | used | number | Transactions used today | | remaining | number | Remaining transactions today | | limit | number | Daily transaction limit (100) | | resetsAt | string | UTC midnight reset time (RFC 3339) |


Webhooks

Server-side HTTP notifications for transaction lifecycle events and billing alerts. One webhook per API key.

Setup

Register a webhook via config (auto-registers on connect) or manually:

// Option 1: Via config (auto-registers on connect)
const client = await SlipstreamClient.connect(
  configBuilder()
    .apiKey('sk_live_12345678')
    .webhookUrl('https://your-server.com/webhooks/slipstream')
    .webhookEvents(['transaction.confirmed', 'transaction.failed', 'billing.low_balance'])
    .webhookNotificationLevel('final')
    .build()
);

// Option 2: Manual registration
const webhook = await client.registerWebhook(
  'https://your-server.com/webhooks/slipstream',
  ['transaction.confirmed', 'billing.low_balance'],  // events (optional)
  'final'                                             // level (optional)
);

console.log(`Webhook ID: ${webhook.id}`);
console.log(`Secret: ${webhook.secret}`);  // Save this -- shown only once

Manage Webhooks

// Get current webhook config
const webhook = await client.getWebhook();
if (webhook) {
  console.log(`URL: ${webhook.url}`);
  console.log(`Events: ${webhook.events.join(', ')}`);
  console.log(`Active: ${webhook.isActive}`);
}

// Remove webhook
await client.deleteWebhook();

Event Types

| Event | Description | Payload | |-------|-------------|---------| | transaction.sent | TX accepted and sent to Solana | signature, region, sender, latencyMs | | transaction.confirmed | TX confirmed on-chain | signature, confirmedSlot, confirmationTimeMs, full getTransaction response | | transaction.failed | TX timed out or errored | signature, error, elapsedMs | | bundle.sent | Bundle submitted to sender | bundleId, region, sender, latencyMs | | bundle.confirmed | All transactions in bundle confirmed on-chain | bundleId, signatures, confirmedSlot, confirmationTimeMs | | bundle.failed | Bundle timed out with partial confirmations | bundleId, error, elapsedMs | | billing.low_balance | Balance below threshold | balanceTokens, thresholdTokens | | billing.depleted | Balance at zero / grace period | balanceTokens, graceRemainingTokens | | billing.deposit_received | SOL deposit credited | amountSol, tokensCredited, newBalanceTokens |

Notification Levels (transaction and bundle events)

| Level | Events delivered | |-------|-----------------| | 'all' | transaction.sent + transaction.confirmed + transaction.failed + bundle.sent + bundle.confirmed + bundle.failed | | 'final' | transaction.confirmed + transaction.failed + bundle.confirmed + bundle.failed (terminal states only) | | 'confirmed' | transaction.confirmed + bundle.confirmed only |

Billing events are always delivered when subscribed (no level filtering).

Webhook Payload

Each POST includes these headers:

  • X-Slipstream-Signature: sha256=<hex> -- HMAC-SHA256 of body using webhook secret
  • X-Slipstream-Timestamp: <unix_seconds> -- for replay protection
  • X-Slipstream-Event: <event_type> -- event name
  • Content-Type: application/json
{
  "id": "evt_01H...",
  "type": "transaction.confirmed",
  "created_at": 1707000000,
  "api_key_prefix": "sk_live_",
  "data": {
    "signature": "5K8c...",
    "transaction_id": "uuid",
    "confirmed_slot": 245678902,
    "confirmation_time_ms": 450,
    "transaction": { "...full Solana getTransaction response..." }
  }
}

Verifying Webhook Signatures (Node.js)

import crypto from 'crypto';

function verifyWebhook(body: string, signatureHeader: string, secret: string): boolean {
  const expected = signatureHeader.replace('sha256=', '');
  const computed = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(expected));
}

// Express example
app.post('/webhooks/slipstream', (req, res) => {
  const signature = req.headers['x-slipstream-signature'] as string;
  const timestamp = req.headers['x-slipstream-timestamp'] as string;

  // Reject if timestamp is too old (>5 min)
  if (Date.now() / 1000 - parseInt(timestamp) > 300) {
    return res.status(400).send('Timestamp too old');
  }

  if (!verifyWebhook(JSON.stringify(req.body), signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;
  switch (event.type) {
    case 'transaction.confirmed':
      console.log(`TX ${event.data.signature} confirmed at slot ${event.data.confirmed_slot}`);
      break;
    case 'billing.low_balance':
      console.log(`Low balance: ${event.data.balance_tokens} tokens`);
      break;
  }

  res.status(200).send('OK');
});

Billing

Each successful webhook delivery costs 0.00001 SOL (10,000 lamports). Failed deliveries (non-2xx or timeout) are not charged. Free tier deliveries count against the daily limit.

Retry Policy

  • Max 3 attempts: immediate, then 30s, then 5 minutes
  • Webhook auto-disabled after 10 consecutive failed deliveries

WebhookConfig Fields

| Field | Type | Description | |-------|------|-------------| | id | string | Webhook ID | | url | string | Delivery URL | | secret | string? | HMAC signing secret (only shown on registration) | | events | string[] | Subscribed event types | | notificationLevel | string | Transaction notification level | | isActive | boolean | Whether webhook is active | | createdAt | string | Creation timestamp (ISO 8601) |


Multi-Region Routing

MultiRegionClient connects to workers across multiple regions and automatically routes transactions to the region closest to the current Solana leader.

Auto-Discovery

import { MultiRegionClient, configBuilder } from '@allenhark/slipstream';

const multi = await MultiRegionClient.connect(
  configBuilder().apiKey('sk_live_your_key_here').build()
);

// Transactions auto-route to the best region
const result = await multi.submitTransaction(signedTxBytes);

console.log(`Connected regions: ${multi.connectedRegions().join(', ')}`);

// Listen for routing changes
multi.on('routingUpdate', (routing) => {
  console.log(`Now routing to ${routing.bestRegion} (confidence: ${routing.confidence}%)`);
  console.log(`Leader: ${routing.leaderPubkey}`);
});

await multi.disconnectAll();

Manual Worker Configuration

import { MultiRegionClient, configBuilder, WorkerEndpoint } from '@allenhark/slipstream';

const workers: WorkerEndpoint[] = [
  { id: 'w1', region: 'us-east', http: 'http://10.0.1.1:9000', websocket: 'ws://10.0.1.1:9000/ws' },
  { id: 'w2', region: 'eu-central', http: 'http://10.0.2.1:9000', websocket: 'ws://10.0.2.1:9000/ws' },
  { id: 'w3', region: 'ap-tokyo', http: 'http://10.0.3.1:9000', websocket: 'ws://10.0.3.1:9000/ws' },
];

const multi = await MultiRegionClient.create(
  configBuilder().apiKey('sk_live_your_key_here').build(),
  workers,
  {
    autoFollowLeader: true,
    minSwitchConfidence: 70,
    switchCooldownMs: 5000,
    broadcastHighPriority: false,
    maxBroadcastRegions: 3,
  }
);

MultiRegionConfig Fields

| Field | Type | Default | Description | |-------|------|---------|-------------| | autoFollowLeader | boolean | true | Auto-switch region based on leader hints | | minSwitchConfidence | number | 60 | Minimum confidence (0-100) to trigger region switch | | switchCooldownMs | number | 5000 | Cooldown between region switches (ms) | | broadcastHighPriority | boolean | false | Broadcast high-priority transactions to all regions | | maxBroadcastRegions | number | 3 | Maximum regions for broadcast mode |

Routing Recommendation

Ask the server for the current best region:

const rec = await client.getRoutingRecommendation();
console.log(`Best: ${rec.bestRegion} (${rec.confidence}%)`);
console.log(`Leader: ${rec.leaderPubkey}`);
console.log(`Fallbacks: ${rec.fallbackRegions.join(', ')} (${rec.fallbackStrategy})`);
console.log(`Valid for: ${rec.validForMs}ms`);

RoutingRecommendation Fields

| Field | Type | Description | |-------|------|-------------| | bestRegion | string | Recommended region | | leaderPubkey | string | Current leader validator pubkey | | slot | number | Current slot | | confidence | number | Confidence score (0-100) | | expectedRttMs | number? | Expected RTT to leader from best region (ms) | | fallbackRegions | string[] | Fallback regions in priority order | | fallbackStrategy | FallbackStrategy | 'sequential', 'broadcast', 'retry', or 'none' | | validForMs | number | Time until this recommendation expires (ms) |


Solana RPC Proxy

Slipstream proxies a curated set of Solana JSON-RPC methods through its infrastructure, billed at 1 token per call. The client.rpc property provides a fully typed interface -- no need for a separate RPC provider for common read operations.

Typed Methods

Every supported Solana RPC method has a typed wrapper on client.rpc:

// Cluster info
const slot = await client.rpc.getSlot();
const height = await client.rpc.getBlockHeight();
const epoch = await client.rpc.getEpochInfo();

// Fees & blockhash
const bh = await client.rpc.getLatestBlockhash();
console.log(`Blockhash: ${bh.value.blockhash}`);
const fees = await client.rpc.getRecentPrioritizationFees();

// Account data
const lamports = await client.rpc.getBalance('So11111111111111111111111111111111');
const acct = await client.rpc.getAccountInfo(pubkey, { encoding: 'base64' });
const accounts = await client.rpc.getMultipleAccounts([pk1, pk2]);

// Tokens
const tokenBal = await client.rpc.getTokenAccountBalance(tokenAccount);
const supply = await client.rpc.getTokenSupply(mintAddress);

// Transactions
const statuses = await client.rpc.getSignatureStatuses([sig1, sig2]);
const tx = await client.rpc.getTransaction(sig, { maxSupportedTransactionVersion: 0 });

Generic Escape Hatch

For any method not covered by the typed wrappers, use client.rpc.call():

const response = await client.rpc.call('getVersion', []);
console.log(response.result);

Simulate Transaction

const simulation = await client.simulateTransaction(signedTxBytes);
if (simulation.err) {
  console.error('Simulation failed:', simulation.err);
  console.log('Logs:', simulation.logs);
} else {
  console.log('Units consumed:', simulation.unitsConsumed);
}

Simulate Bundle

Simulates each transaction in a bundle sequentially. Stops on first failure. Costs 1 token per transaction simulated.

const results = await client.simulateBundle([tx1Bytes, tx2Bytes, tx3Bytes]);
for (const sim of results) {
  if (sim.err) {
    console.error(`TX failed:`, sim.err);
    break;
  }
  console.log(`TX OK — ${sim.unitsConsumed} CUs`);
}

Typed Method Reference

Network

| Method | Signature | Returns | |--------|-----------|---------| | getHealth() | () => string | "ok" |

Cluster

| Method | Signature | Returns | |--------|-----------|---------| | getSlot() | (commitment?) => number | Current slot | | getBlockHeight() | (commitment?) => number | Current block height | | getEpochInfo() | (commitment?) => SolanaEpochInfo | Epoch details | | getSlotLeaders() | (startSlot, limit) => string[] | Leader pubkeys |

Fees

| Method | Signature | Returns | |--------|-----------|---------| | getLatestBlockhash() | (commitment?) => SolanaLatestBlockhash | { value: { blockhash, lastValidBlockHeight } } | | getFeeForMessage() | (message, commitment?) => number \| null | Lamports or null | | getRecentPrioritizationFees() | (accounts?) => SolanaPrioritizationFee[] | [{ slot, prioritizationFee }] |

Accounts

| Method | Signature | Returns | |--------|-----------|---------| | getAccountInfo() | (pubkey, opts?) => SolanaAccountInfo | Account data or null value | | getMultipleAccounts() | (pubkeys, opts?) => { value: [] } | Array of account data | | getBalance() | (pubkey, commitment?) => number | Lamports (unwrapped) | | getMinimumBalanceForRentExemption() | (dataSize, commitment?) => number | Lamports |

Tokens

| Method | Signature | Returns | |--------|-----------|---------| | getTokenAccountBalance() | (pubkey, commitment?) => SolanaTokenBalance | Token balance with decimals | | getTokenSupply() | (mint, commitment?) => SolanaTokenBalance | Token supply | | getSupply() | (commitment?) => SolanaSupply | SOL supply breakdown | | getTokenLargestAccounts() | (mint, commitment?) => SolanaTokenLargestAccount[] | Top 20 holders |

Transactions

| Method | Signature | Returns | |--------|-----------|---------| | sendTransaction() | (tx, opts?) => string | Signature | | simulateTransaction() | (tx, opts?) => SimulationResult | Logs, CUs, errors | | getSignatureStatuses() | (sigs, opts?) => { value: [] } | Status per signature | | getTransaction() | (sig, opts?) => unknown | Full transaction data |

Blocks

| Method | Signature | Returns | |--------|-----------|---------| | getBlockCommitment() | (slot) => SolanaBlockCommitment | Commitment + total stake | | getFirstAvailableBlock() | () => number | First available slot |

Escape Hatch

| Method | Signature | Returns | |--------|-----------|---------| | call() | (method, params?) => RpcResponse | Raw JSON-RPC response |

SimulationResult Fields

| Field | Type | Description | |-------|------|-------------| | err | object \| null | Error if simulation failed, null on success | | logs | string[] | Program log messages | | unitsConsumed | number | Compute units consumed | | returnData | object \| null | Program return data (if any) |

RpcResponse Fields

| Field | Type | Description | |-------|------|-------------| | jsonrpc | string | Always "2.0" | | id | number | Request ID | | result | any | RPC-method-specific result | | error | object \| null | JSON-RPC error (if any) |


Deduplication

Prevent duplicate submissions with dedupId:

const result = await client.submitTransactionWithOptions(txBytes, {
  dedupId: 'unique-tx-id-12345',
  maxRetries: 5,
});

// Same dedupId across retries = safe from double-spend

Connection Status & Metrics

// Connection status
const status = client.connectionStatus();
console.log(`State: ${status.state}`);       // 'connected', 'disconnected', etc.
console.log(`Protocol: ${status.protocol}`); // 'quic', 'ws', 'http'
console.log(`Region: ${status.region}`);
console.log(`Latency: ${status.latencyMs}ms`);

// Connection events
client.on('connected', () => console.log('Connected'));
client.on('disconnected', () => console.log('Disconnected'));
client.on('error', (err) => console.error('Error:', err));

// Performance metrics
const metrics = client.metrics();
console.log(`Submitted: ${metrics.transactionsSubmitted}`);
console.log(`Confirmed: ${metrics.transactionsConfirmed}`);
console.log(`Avg latency: ${metrics.averageLatencyMs.toFixed(1)}ms`);
console.log(`Success rate: ${(metrics.successRate * 100).toFixed(1)}%`);

Error Handling

import { SlipstreamError } from '@allenhark/slipstream';

try {
  const result = await client.submitTransaction(txBytes);
} catch (err) {
  if (err instanceof SlipstreamError) {
    switch (err.code) {
      case 'INSUFFICIENT_TOKENS':
        const balance = await client.getBalance();
        const deposit = await client.getDepositAddress();
        console.log(`Low balance: ${balance.balanceTokens} tokens`);
        console.log(`Deposit to: ${deposit.depositWallet}`);
        break;
      case 'RATE_LIMITED':
        console.log('Slow down -- rate limited for your tier');
        break;
      case 'TIMEOUT':
        console.log('Request timed out');
        break;
      case 'CONNECTION':
        console.log('Connection error:', err.message);
        break;
      default:
        console.log(`Error [${err.code}]: ${err.message}`);
    }
  }
}

Error Codes

| Code | Description | |------|-------------| | CONFIG | Invalid configuration | | CONNECTION | Connection failure | | AUTH | Authentication failure (invalid API key) | | PROTOCOL | Protocol-level error | | TRANSACTION | Transaction submission error | | TIMEOUT | Operation timed out | | RATE_LIMITED | Rate limit exceeded for your tier | | NOT_CONNECTED | Client not connected | | STREAM_CLOSED | Stream closed unexpectedly | | INSUFFICIENT_TOKENS | Token balance too low (or free tier daily limit reached) | | ALL_PROTOCOLS_FAILED | All connection protocols failed | | INTERNAL | Internal SDK error |


Examples

| Example | Description | |---------|-------------| | basic.ts | Connect and submit a transaction | | streaming.ts | Leader hints, tips, priority fees, blockhash, slot streams | | billing.ts | Balance, deposits, and usage history | | multi-region.ts | Auto-routing with MultiRegionClient | | advanced-config.ts | All ConfigBuilder options | | submit-transaction.ts | Transaction submission with options | | priority-fees.ts | Priority fee configuration and streaming | | deduplication.ts | Deduplication patterns |

Contact

  • Website: https://allenhark.com
  • Contact: https://allenhark.com/contact
  • Discord: https://discord.gg/JpzS72MAKG

License

Apache-2.0