npm package: @otoplo/electrum-client
Description: Lightweight Electrum protocol client for Node.js and browsers
Author: vgrunner
Latest version: 0.2.2

# @otoplo/electrum-client

[![npm version](https://img.shields.io/npm/v/@otoplo/electrum-client.svg)](https://www.npmjs.com/package/@otoplo/electrum-client)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A lightweight Electrum protocol client for Node.js and browsers.

## Features

- 🌐 **Cross-platform** - Works in Node.js and browsers
- 🔌 **WebSocket Transport** - Supports both `ws://` and `wss://` connections
- 🔄 **Auto-reconnection** - Automatically reconnects on connection loss with keep-alive pings
- 📡 **Subscription Management** - Subscribe to server notifications with automatic resubscription on reconnect
- 🔢 **Lossless JSON** - Handles large numbers without precision loss
- 📨 **Event-driven** - Built on EventEmitter3 for efficient event handling

## Server Compatibility

This client is compatible with servers implementing the Electrum protocol, including:

- [Electrum](https://github.com/spesmilo/electrumx)
- [Rostrum](https://gitlab.com/nexa/rostrum)

## Installation

```bash
npm install @otoplo/electrum-client
```

## Quick Start

### Basic Connection

```typescript
import { ElectrumClient, TransportScheme } from '@otoplo/electrum-client';

// Create a client instance
const client = new ElectrumClient(
  'my-app',           // Application name
  '1.4.3',            // Protocol version
  'rostrum.example.com', // Server host
  20004,              // Port (optional)
  TransportScheme.WSS // Scheme (optional)
);

// Connect to the server
await client.connect();
console.log('Connected to Electrum server');

// Disconnect when done
await client.disconnect();
```

### Making Requests

```typescript
// Get server banner
const banner = await client.request('server.banner');
console.log('Server banner:', banner);

// Get block header
const header = await client.request('blockchain.block.header', 0);
console.log('Genesis block header:', header);

// Get transaction
const tx = await client.request('blockchain.transaction.get', 'txid_here', true);
console.log('Transaction:', tx);
```

### Subscribing to Notifications

```typescript
// Define a callback for blockchain headers
const onNewBlock = (data: unknown) => {
  console.log('New block:', data);
};

// Subscribe to new block headers
await client.subscribe(onNewBlock, 'blockchain.headers.subscribe');

// Subscribe to address changes
const onAddressChange = (data: unknown) => {
  console.log('Address updated:', data);
};
await client.subscribe(
  onAddressChange,
  'blockchain.scripthash.subscribe',
  'your_scripthash_here'
);

// Unsubscribe when no longer needed
await client.unsubscribe(onNewBlock, 'blockchain.headers.subscribe');
```

### Event Handling

```typescript
// Listen for connection events
client.on('connected', () => {
  console.log('Connected to server');
});

client.on('disconnected', () => {
  console.log('Disconnected from server');
});

client.on('error', () => {
  console.error('Connection error');
});
```

## API Reference

### Constructor

```typescript
new ElectrumClient(
  application: string,
  version: string,
  host: string,
  port?: number,
  scheme?: TransportScheme,
  timeout?: number,
  pingInterval?: number
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `application` | `string` | *required* | Application identifier sent to the server |
| `version` | `string` | *required* | Electrum protocol version (e.g., `'1.4.3'`) |
| `host` | `string` | *required* | Server hostname |
| `port` | `number` | `20003` | Server port |
| `scheme` | `TransportScheme` | `TransportScheme.WS` | Transport scheme (`ws` or `wss`) |
| `timeout` | `number` | `120000` | Request timeout in milliseconds |
| `pingInterval` | `number` | `15000` | Keep-alive ping interval in milliseconds |

### Methods

#### `connect(): Promise<void>`

Connects to the remote server and negotiates the protocol version.

```typescript
await client.connect();
```

**Throws:** Error if the socket connection fails or protocol version negotiation fails.

---

#### `disconnect(force?: boolean, retainSubscriptions?: boolean): Promise<boolean>`

Disconnects from the remote server.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `force` | `boolean` | `false` | Disconnect even if connection is not fully established |
| `retainSubscriptions` | `boolean` | `false` | Retain subscription data for restoration on reconnect |

**Returns:** `true` if successfully disconnected, `false` if there was no connection.

```typescript
// Normal disconnect
await client.disconnect();

// Force disconnect, retain subscriptions for reconnect
await client.disconnect(true, true);
```

---

#### `request(method: string, ...parameters: RPCParameter[]): Promise<Error | RequestResponse>`

Calls a method on the remote server.

| Parameter | Type | Description |
|-----------|------|-------------|
| `method` | `string` | Name of the RPC method to call |
| `parameters` | `RPCParameter[]` | Parameters for the method |

**Returns:** Promise resolving to the result or an Error.

**Throws:** Error if the client is disconnected.

```typescript
const result = await client.request('blockchain.block.header', 12345);
```

---

#### `subscribe(callback: SubscribeCallback, method: string, ...parameters: RPCParameter[]): Promise<Error | RequestResponse>`

Subscribes to a server method and attaches a callback for notifications.

| Parameter | Type | Description |
|-----------|------|-------------|
| `callback` | `SubscribeCallback` | Function to receive notification data |
| `method` | `string` | Subscribable method name |
| `parameters` | `RPCParameter[]` | Parameters for the subscription |

**Returns:** Promise resolving to the initial subscription response.

**Throws:** Error if the client is disconnected.

```typescript
const callback = (data) => console.log('Notification:', data);
await client.subscribe(callback, 'blockchain.headers.subscribe');
```

---

#### `unsubscribe(callback: SubscribeCallback, method: string, ...parameters: RPCParameter[]): Promise<true>`

Unsubscribes from a previously subscribed method.

| Parameter | Type | Description |
|-----------|------|-------------|
| `callback` | `SubscribeCallback` | Previously subscribed callback function |
| `method` | `string` | Previously subscribed method |
| `parameters` | `RPCParameter[]` | Original subscription parameters |

**Returns:** Promise resolving to `true` when unsubscribed.

**Throws:** Error if no matching subscription exists or client is disconnected.

```typescript
await client.unsubscribe(callback, 'blockchain.headers.subscribe');
```

### Events

| Event | Description |
|-------|-------------|
| `connected` | Emitted when connection to the server is established |
| `disconnected` | Emitted when connection to the server is lost |
| `error` | Emitted when a connection error occurs |

## Constants & Types

### Exports

```typescript
import {
  ElectrumClient,
  ElectrumTransport,
  TransportScheme,
  ConnectionStatus,
  // Types
  RequestResponse,
  SubscribeCallback,
} from '@otoplo/electrum-client';
```

### TransportScheme

```typescript
enum TransportScheme {
  WS = 'ws',   // WebSocket
  WSS = 'wss'  // WebSocket Secure
}
```

### ElectrumTransport

Pre-configured transport settings:

```typescript
const ElectrumTransport = {
  WS:  { Port: 20003, Scheme: TransportScheme.WS },
  WSS: { Port: 20004, Scheme: TransportScheme.WSS },
};
```

### ConnectionStatus

```typescript
enum ConnectionStatus {
  DISCONNECTED = 0,
  CONNECTED = 1,
  DISCONNECTING = 2,
  CONNECTING = 3,
  RECONNECTING = 4,
}
```

### Types

```typescript
// Response type from requests
type RequestResponse = object | string | number | boolean | null | RequestResponse[];

// Callback function for subscriptions
type SubscribeCallback = (errorOrData: Error | RequestResponse) => void;
```

## Requirements

- Node.js >= 22.12

## License

MIT