Connection
Establish a secure gRPC connection to NextBlock's API using Node.js and @grpc/grpc-js.
Prerequisites
Install the required dependencies:
npm install @grpc/grpc-js @grpc/proto-loader
npm install @solana/web3.js
npm install typescript @types/node # For TypeScript
Generate the gRPC client from proto specs:
# Clone the proto repository
git clone https://github.com/nextblock-ag/nextblock-proto
# Follow the JavaScript/TypeScript generation instructions in the repo
Connection Setup
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { promisify } from 'util';
// Configuration interface
interface NextBlockConfig {
endpoint: string;
apiKey: string;
useTLS: boolean;
timeout: number;
keepaliveTimeMs: number;
keepaliveTimeoutMs: number;
}
// Default configuration
const defaultConfig: NextBlockConfig = {
endpoint: 'fra.nextblock.io:443',
apiKey: process.env.NEXTBLOCK_API_KEY || '',
useTLS: true,
timeout: 30000,
keepaliveTimeMs: 60000, // 60 seconds
keepaliveTimeoutMs: 15000, // 15 seconds
};
// Authentication metadata interceptor
class AuthInterceptor {
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
// Add authorization header to all requests
intercept(options: any, nextCall: any) {
return new grpc.InterceptingCall(nextCall(options), {
start: (metadata: grpc.Metadata, listener: any, next: any) => {
metadata.set('authorization', this.apiKey);
next(metadata, listener);
},
});
}
}
// Create gRPC channel with authentication
export async function createNextBlockChannel(config: NextBlockConfig): Promise<grpc.Channel> {
// Channel options for keepalive and performance
const channelOptions: grpc.ChannelOptions = {
'grpc.keepalive_time_ms': config.keepaliveTimeMs,
'grpc.keepalive_timeout_ms': config.keepaliveTimeoutMs,
'grpc.keepalive_permit_without_stream': 1,
'grpc.http2.max_pings_without_data': 0,
'grpc.http2.min_ping_interval_without_data_ms': 300000, // 5 minutes
};
// Create credentials
const credentials = config.useTLS
? grpc.credentials.createSsl()
: grpc.credentials.createInsecure();
// Create channel
const channel = new grpc.Channel(config.endpoint, credentials, channelOptions);
return channel;
}
// Create authenticated gRPC client
export async function createNextBlockClient(config: NextBlockConfig = defaultConfig) {
// Validate configuration
if (!config.apiKey) {
throw new Error('API key is required');
}
// Create channel
const channel = await createNextBlockChannel(config);
// Create authentication interceptor
const authInterceptor = new AuthInterceptor(config.apiKey);
// Load proto definition (replace with your generated client)
/* Example proto loading - replace with your actual proto
const packageDefinition = protoLoader.loadSync('path/to/your/nextblock.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const ApiClient = protoDescriptor.nextblock.Api;
// Create client with interceptor
const client = new ApiClient(config.endpoint, credentials, {
interceptors: [authInterceptor.intercept.bind(authInterceptor)],
...channelOptions,
});
*/
console.log(`Connected to NextBlock at ${config.endpoint}`);
return {
channel,
// client, // Uncomment when you have the generated client
config,
};
}
// Connection manager with health checking
export class NextBlockConnectionManager {
private config: NextBlockConfig;
private channel?: grpc.Channel;
private client?: any; // Replace with your generated client type
private isConnected: boolean = false;
constructor(config: NextBlockConfig = defaultConfig) {
this.config = config;
}
// Establish connection
async connect(): Promise<boolean> {
try {
const connection = await createNextBlockClient(this.config);
this.channel = connection.channel;
// this.client = connection.client;
// Test the connection
await this.healthCheck();
this.isConnected = true;
console.log(`Successfully connected to NextBlock at ${this.config.endpoint}`);
return true;
} catch (error) {
console.error('Failed to connect to NextBlock:', error);
this.isConnected = false;
return false;
}
}
// Health check
async healthCheck(): Promise<boolean> {
if (!this.channel) {
return false;
}
return new Promise((resolve) => {
// Check channel state
const state = this.channel!.getConnectivityState(false);
if (state === grpc.connectivityState.READY) {
console.log('Connection health check passed');
resolve(true);
} else {
console.log(`Connection state: ${grpc.connectivityState[state]}`);
resolve(false);
}
});
}
// Disconnect
async disconnect(): Promise<void> {
if (this.channel) {
this.channel.close();
this.isConnected = false;
console.log('Disconnected from NextBlock');
}
}
// Getters
get connected(): boolean {
return this.isConnected;
}
get grpcClient(): any {
return this.client;
}
}
// Configuration from environment variables
export function configFromEnv(): NextBlockConfig {
return {
endpoint: process.env.NEXTBLOCK_ENDPOINT || 'fra.nextblock.io:443',
apiKey: process.env.NEXTBLOCK_API_KEY || '',
useTLS: process.env.NEXTBLOCK_USE_TLS !== 'false',
timeout: parseInt(process.env.NEXTBLOCK_TIMEOUT || '30000'),
keepaliveTimeMs: parseInt(process.env.NEXTBLOCK_KEEPALIVE_TIME_MS || '60000'),
keepaliveTimeoutMs: parseInt(process.env.NEXTBLOCK_KEEPALIVE_TIMEOUT_MS || '15000'),
};
}
Usage Examples
// Basic connection example
async function basicConnectionExample() {
const config: NextBlockConfig = {
endpoint: 'fra.nextblock.io:443',
apiKey: '<your-api-key-here>',
useTLS: true,
timeout: 30000,
keepaliveTimeMs: 60000,
keepaliveTimeoutMs: 15000,
};
try {
const connection = await createNextBlockClient(config);
console.log('Connected to NextBlock!');
// Your API calls would go here
// const response = await connection.client.ping({});
// Clean up
connection.channel.close();
} catch (error) {
console.error('Connection failed:', error);
}
}
// Connection manager example (recommended)
async function connectionManagerExample() {
const config = configFromEnv();
const manager = new NextBlockConnectionManager(config);
try {
const connected = await manager.connect();
if (connected) {
console.log('Connection manager established connection');
// Use manager.grpcClient for API calls
// const response = await manager.grpcClient.ping({});
// Keep connection alive for your application
await new Promise(resolve => setTimeout(resolve, 5000));
} else {
console.error('Failed to establish connection');
}
} finally {
await manager.disconnect();
}
}
// Advanced connection with retry logic
class EnhancedConnectionManager extends NextBlockConnectionManager {
private maxRetries: number = 3;
private retryDelay: number = 5000; // 5 seconds
async connectWithRetry(): Promise<boolean> {
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
console.log(`Connection attempt ${attempt}/${this.maxRetries}`);
const success = await this.connect();
if (success) {
return true;
}
if (attempt < this.maxRetries) {
console.log(`Retrying in ${this.retryDelay}ms...`);
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
this.retryDelay *= 2; // Exponential backoff
}
}
console.error('All connection attempts failed');
return false;
}
async callWithRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
// Don't retry on authentication errors
if (error && (error as any).code === grpc.status.UNAUTHENTICATED) {
throw error;
}
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError!;
}
}
// Connection pool for high-throughput applications
class ConnectionPool {
private connections: NextBlockConnectionManager[] = [];
private currentIndex: number = 0;
private config: NextBlockConfig;
private poolSize: number;
constructor(config: NextBlockConfig, poolSize: number = 5) {
this.config = config;
this.poolSize = poolSize;
}
async initialize(): Promise<void> {
console.log(`Initializing connection pool with ${this.poolSize} connections...`);
const connectionPromises = Array.from({ length: this.poolSize }, async (_, i) => {
const manager = new NextBlockConnectionManager(this.config);
const connected = await manager.connect();
if (connected) {
this.connections.push(manager);
console.log(`Connection ${i + 1} established`);
} else {
console.warn(`Failed to create connection ${i + 1}`);
}
});
await Promise.all(connectionPromises);
console.log(`Connection pool initialized with ${this.connections.length} connections`);
}
getConnection(): NextBlockConnectionManager {
if (this.connections.length === 0) {
throw new Error('No available connections in pool');
}
const connection = this.connections[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.connections.length;
return connection;
}
async closeAll(): Promise<void> {
await Promise.all(this.connections.map(conn => conn.disconnect()));
this.connections = [];
console.log('All connections closed');
}
}
// Run examples
async function main() {
console.log('NextBlock Connection Examples');
// Example 1: Basic connection
console.log('\n1. Basic Connection:');
await basicConnectionExample();
// Example 2: Connection manager
console.log('\n2. Connection Manager:');
await connectionManagerExample();
// Example 3: Enhanced connection with retry
console.log('\n3. Enhanced Connection:');
const enhancedManager = new EnhancedConnectionManager(configFromEnv());
const connected = await enhancedManager.connectWithRetry();
if (connected) {
console.log('Enhanced connection successful');
await enhancedManager.disconnect();
}
}
// Export for use in other modules
export {
NextBlockConfig,
NextBlockConnectionManager,
EnhancedConnectionManager,
ConnectionPool,
configFromEnv,
};
if (require.main === module) {
main().catch(console.error);
}
Available Endpoints
Frankfurt:
fra.nextblock.io:443
(Europe)New York:
ny.nextblock.io:443
(US East)Direct NY:
direct-ny.nextblock.io:443
(US East, optimized routing)
Best Practices
Use TypeScript: Leverage type safety for better development experience
Enable TLS: Always use secure connections in production
Implement keepalive: Configure appropriate keepalive settings for persistent connections
Handle errors gracefully: Use retry logic with exponential backoff
Validate configuration: Check all required settings before connecting
Use connection pooling: For high-throughput applications
Monitor connection health: Implement regular health checks
Environment variables: Store sensitive configuration securely
Close connections properly: Always close connections when done
Use interceptors: Implement authentication and logging via gRPC interceptors
Last updated