What Youโ€™ll Build

By the end of this guide, youโ€™ll have a working gRPC stream that monitors Solana account updates in real-time with automatic reconnection and error handling.
1

Choose Your Access Method

Select how you want to access Yellowstone gRPC:

LaserStream

Recommended for most users
  • Multi-tenant, highly available
  • Automatic failover and backfill
  • Quick setup with API key
  • Professional plan required
Get LaserStream Access โ†’

Dedicated Nodes

For high-volume or custom needs
  • Exclusive gRPC endpoint
  • Guaranteed resources
Get Dedicated Node โ†’
2

Set Up Your Environment

Create a new project and install dependencies:
mkdir solana-grpc-stream
cd solana-grpc-stream
npm init -y
npm install @triton-one/yellowstone-grpc bs58
npm install typescript ts-node @types/node --save-dev
npx tsc --init
3

Get Your Credentials

Obtain your gRPC endpoint and authentication:
  1. Sign up for a Professional plan at dashboard.helius.dev
  2. Get your API key from the dashboard
  3. Choose your regional endpoint:
Mainnet Endpoints:
  • US East: https://laserstream-mainnet-ewr.helius-rpc.com
  • US West: https://laserstream-mainnet-slc.helius-rpc.com
  • Europe: https://laserstream-mainnet-fra.helius-rpc.com
  • Asia: https://laserstream-mainnet-tyo.helius-rpc.com
Devnet: https://laserstream-devnet-ewr.helius-rpc.com
4

Create Your First Stream

Create a robust stream manager with the following complete example:
Create stream-manager.ts:
import Client, { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc";
import * as bs58 from 'bs58';

export class StreamManager {
  private client: Client;
  private stream: any;
  private isConnected = false;
  private reconnectAttempts = 0;
  private readonly maxReconnectAttempts = 10;
  private readonly baseReconnectDelay = 1000; // 1 second

  constructor(
    private endpoint: string,
    private apiKey: string,
    private onData: (data: any) => void,
    private onError?: (error: any) => void
  ) {
    this.client = new Client(endpoint, apiKey, {
      "grpc.max_receive_message_length": 64 * 1024 * 1024
    });
  }

  async connect(subscribeRequest: SubscribeRequest): Promise<void> {
    try {
      console.log(`Connecting to ${this.endpoint}...`);
      this.stream = await this.client.subscribe();
      this.isConnected = true;
      this.reconnectAttempts = 0;

      // Set up event handlers
      this.stream.on("data", this.handleData.bind(this));
      this.stream.on("error", this.handleStreamError.bind(this));
      this.stream.on("end", () => this.handleDisconnect(subscribeRequest));
      this.stream.on("close", () => this.handleDisconnect(subscribeRequest));

      // Send subscription request
      await this.writeRequest(subscribeRequest);
      
      // Start keepalive
      this.startKeepalive();
      
      console.log("โœ… Connected and subscribed successfully");
    } catch (error) {
      console.error("Connection failed:", error);
      await this.reconnect(subscribeRequest);
    }
  }

  private async writeRequest(request: SubscribeRequest): Promise<void> {
    return new Promise((resolve, reject) => {
      this.stream.write(request, (err: any) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  private handleData(data: any): void {
    try {
      // Convert buffers to readable format
      const processedData = this.processBuffers(data);
      this.onData(processedData);
    } catch (error) {
      console.error("Error processing data:", error);
    }
  }

  private processBuffers(obj: any): any {
    if (!obj) return obj;
    
    if (Buffer.isBuffer(obj) || obj instanceof Uint8Array) {
      return bs58.default.encode(obj);
    }
    
    if (Array.isArray(obj)) {
      return obj.map(item => this.processBuffers(item));
    }
    
    if (typeof obj === 'object') {
      return Object.fromEntries(
        Object.entries(obj).map(([k, v]) => [k, this.processBuffers(v)])
      );
    }
    
    return obj;
  }

  private handleStreamError(error: any): void {
    console.error("Stream error:", error);
    this.isConnected = false;
    if (this.onError) this.onError(error);
  }

  private async handleDisconnect(subscribeRequest: SubscribeRequest): Promise<void> {
    if (this.isConnected) {
      console.log("Stream disconnected, attempting to reconnect...");
      this.isConnected = false;
      await this.reconnect(subscribeRequest);
    }
  }

  private async reconnect(subscribeRequest: SubscribeRequest): Promise<void> {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error("Max reconnection attempts reached. Giving up.");
      return;
    }

    this.reconnectAttempts++;
    const delay = this.baseReconnectDelay * Math.pow(2, Math.min(this.reconnectAttempts - 1, 5));
    
    console.log(`Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms...`);
    
    setTimeout(() => {
      this.connect(subscribeRequest).catch(console.error);
    }, delay);
  }

  private startKeepalive(): void {
    setInterval(() => {
      if (this.isConnected) {
        const pingRequest: SubscribeRequest = {
          ping: { id: Date.now() },
          accounts: {},
          accountsDataSlice: [],
          transactions: {},
          slots: {},
          blocks: {},
          blocksMeta: {},
          entry: {},
          transactionsStatus: {}
        };
        
        this.writeRequest(pingRequest).catch(console.error);
      }
    }, 30000); // 30 seconds
  }

  disconnect(): void {
    if (this.stream) {
      this.stream.end();
    }
    this.client.close();
    this.isConnected = false;
  }
}
Create main.ts:
import { StreamManager } from './stream-manager';
import { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc";

// Configuration
const ENDPOINT = "your-grpc-endpoint"; // LaserStream or Dedicated Node endpoint
const API_KEY = "your-api-key";

// USDC Token Mint for example
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

async function main() {
  const streamManager = new StreamManager(
    ENDPOINT,
    API_KEY,
    handleAccountUpdate,
    handleError
  );

  // Subscribe to USDC mint account updates
  const subscribeRequest: SubscribeRequest = {
    accounts: {
      accountSubscribe: {
        account: [USDC_MINT],
        owner: [],
        filters: []
      }
    },
    accountsDataSlice: [],
    commitment: CommitmentLevel.CONFIRMED,
    slots: {},
    transactions: {},
    transactionsStatus: {},
    blocks: {},
    blocksMeta: {},
    entry: {}
  };

  console.log("๐Ÿš€ Starting USDC mint account monitoring...");
  await streamManager.connect(subscribeRequest);

  // Handle graceful shutdown
  process.on('SIGINT', () => {
    console.log('\n๐Ÿ›‘ Shutting down...');
    streamManager.disconnect();
    process.exit(0);
  });
}

function handleAccountUpdate(data: any): void {
  if (data.account) {
    const account = data.account.account;
    console.log('\n๐Ÿ“Š Account Update:');
    console.log(`  Account: ${account.pubkey}`);
    console.log(`  Owner: ${account.owner}`);
    console.log(`  Lamports: ${account.lamports}`);
    console.log(`  Data Length: ${account.data?.length || 0} bytes`);
    console.log(`  Slot: ${data.account.slot}`);
    console.log(`  Timestamp: ${new Date().toISOString()}`);
  }
  
  if (data.pong) {
    console.log(`๐Ÿ’“ Keepalive pong received (id: ${data.pong.id})`);
  }
}

function handleError(error: any): void {
  console.error('โŒ Stream error:', error.message);
}

main().catch(console.error);
Run your stream:
npx ts-node main.ts
5

Test Your Stream

Run your application and verify itโ€™s working:
  1. Start your stream using the command for your language
  2. Look for connection confirmation in the console
  3. Wait for account updates - you should see periodic updates to the USDC mint account
  4. Test reconnection by temporarily disconnecting your internet
  5. Verify keepalive by watching for pong messages every 30 seconds
Expected output:
๐Ÿš€ Connected! Monitoring USDC mint account...
โœ… Connected and subscribed successfully
๐Ÿ’“ Keepalive pong received (id: 1703123456789)

๐Ÿ“Š Account Update:
  Account: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  Owner: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
  Lamports: 1461600
  Data Length: 82 bytes
  Slot: 275123456
  Timestamp: 2024-01-15T10:30:45.123Z

Whatโ€™s Next?

Now that you have a working gRPC stream, explore these monitoring guides:

Troubleshooting

Best Practices

Production Readiness Checklist:
  • โœ… Implement exponential backoff for reconnections
  • โœ… Use keepalive pings every 30 seconds
  • โœ… Handle all stream events (data, error, end, close)
  • โœ… Process data asynchronously to avoid blocking
  • โœ… Monitor connection health and alert on failures
  • โœ… Use appropriate commitment levels for your use case
  • โœ… Filter data as specifically as possible to reduce bandwidth