Skip to content
Logo

TCP Transport

The TCP transport provides newline-delimited JSON-RPC communication with optional TLS encryption. Node.js only.

Installation

npm i @rpckit/tcp

Basic Usage

import { tcp } from '@rpckit/tcp'
 
const transport = tcp('tcp://example.com:50001')
 
await transport.connect()
const result = await transport.request('server.version', 'client', '1.4')
await transport.close()

Configuration

URL String

// Plain TCP
const transport = tcp('tcp://example.com:50001')
 
// TCP with TLS
const transport = tcp('tcp+tls://example.com:50002')

Configuration Object

const transport = tcp({
  host: 'example.com',
  port: 50002,
  tls: true,
  timeout: 10000,
  batch: { wait: 10, batchSize: 50 },
  keepAlive: { interval: 30000, method: 'server.ping' },
  reconnect: { delay: 1000, attempts: 5 }
})

Options

OptionTypeDefaultDescription
urlstring-TCP URL (tcp:// or tcp+tls://)
hoststring-Server hostname (alternative to url)
portnumber-Server port (alternative to url)
tlsboolean | TLSOptionsfalseEnable TLS encryption
timeoutnumber30000Request timeout in milliseconds
connectTimeoutnumber-Connection timeout in milliseconds
batchBatchConfig | false{ batchSize: 100 }Batching configuration
keepAliveKeepAliveConfig-Keep-alive ping configuration
reconnect{ delay, attempts }-Auto-reconnect after disconnect

TLS Configuration

Simple TLS:

const transport = tcp({
  host: 'example.com',
  port: 50002,
  tls: true
})

Custom TLS options:

const transport = tcp({
  host: 'example.com',
  port: 50002,
  tls: {
    ca: fs.readFileSync('ca.pem'),
    cert: fs.readFileSync('client.pem'),
    key: fs.readFileSync('client-key.pem'),
    rejectUnauthorized: true
  }
})

Electrum Cash Variant

For Electrum Cash servers, use the electrum-cash subpath:

import { tcp } from '@rpckit/tcp/electrum-cash'
 
const transport = tcp('tcp+tls://electrum.example.com:50002', {
  keepAlive: 60000,        // Uses server.ping automatically
  clientName: 'myapp',     // Client name in handshake (default: 'rpckit')
  protocolVersion: '1.6',  // Default
})
 
// server.version handshake is sent automatically
// onUnsubscribe derives method from subscribe method

Subscriptions

TCP transport supports subscriptions like WebSocket:

const unsubscribe = await transport.subscribe(
  'events.subscribe',
  'channel-1',
  (data) => {
    console.log('Event:', data)
  }
)
 
// Later, unsubscribe
await unsubscribe()

Subscription Sharing

Multiple callers subscribing to the same method+params share a single server subscription. New subscribers receive the most recent notification data (not stale initial data). The server unsubscribe is only sent when the last listener unsubscribes.

// Both callbacks share one server subscription
const unsub1 = await transport.subscribe('events', callback1)
const unsub2 = await transport.subscribe('events', callback2)
 
await unsub1() // callback1 removed, server subscription stays active
await unsub2() // callback2 removed, NOW server unsubscribe is sent

Protocol

The TCP transport uses newline-delimited JSON:

  • Each JSON-RPC message is a single line terminated by \n
  • Messages are UTF-8 encoded
  • Batch requests are sent as a single JSON array on one line

Extended Interface

interface TcpTransport<S extends Schema> extends Transport<S> {
  getSocket(): Socket | TLSSocket | null
  getSocketAsync(): Promise<Socket | TLSSocket>
}

Example: Fulcrum Connection

import { tcp } from '@rpckit/tcp/electrum-cash'
 
const transport = tcp({
  host: 'electrum.example.com',
  port: 50002,
  tls: true,
  keepAlive: 60000
})
 
await transport.connect()
 
// Get chain tip (handshake already sent)
const tip = await transport.request('blockchain.headers.get_tip')
console.log('Block height:', tip.height)
 
// Subscribe to block headers
await transport.subscribe(
  'blockchain.headers.subscribe',
  (header) => {
    console.log('New block:', header.height)
  }
)