npm package: @rockerone/ion-drive
Description: A powerful, type-safe TypeScript client for streaming real-time blockchain data from XPRNetwork using State History Plugin
Author: Rockerone
Latest version: 2.0.0

# Ion Drive

### Isomorphic XPRNetwork Block Stream Client

A powerful, type-safe TypeScript client for streaming real-time blockchain data from XPRNetwork (and other EOSIO chains) using the State History Plugin. Works in both **Node.js** and **browsers** with zero configuration.

## Features

- **Isomorphic** — runs in Node.js and browsers out of the box
- **Real-time block streaming** from EOSIO State History Plugin (SHiP)
- **ABI-powered decoding** — binary data automatically converted to readable JSON
- **Microservice architecture** — composable processing pipeline with `.pipe()`
- **Fork detection** — automatic chain reorganization detection with depth tracking
- **LIB tracking** — Last Irreversible Block info available in every context
- **Transaction tracing** — full transaction ID (`tx_id`) exposure for action traces
- **Advanced filtering** — contract, table, and action whitelisting with wildcard support
- **Pluggable logging** — bring your own logger or use the built-in `ConsoleLogger`
- **Full TypeScript support** — complete type safety with proper EOSIO types

## Installation

```bash
npm install @rockerone/ion-drive
# or
bun add @rockerone/ion-drive
```

For Node.js < 21 (no native WebSocket), also install `ws`:

```bash
npm install ws
```

## Quick Start

### Node.js

```typescript
import {BlockStreamClient} from "@rockerone/ion-drive";

const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  contracts: {
    "eosio.token": {
      tables: ["accounts", "stat"],
      actions: ["transfer", "issue"],
    },
  },
  enableDebug: true,
  logLevel: "info",
});

client.pipe(({$block, $lib, $tx, $action, $delta, $table, $logger}) => {
  if ($action) {
    $logger.info("Action received", {
      contract: $action.account,
      action: $action.name,
      tx_id: $tx?.tx_id,
      block: $block.block_number,
      lib: $lib?.block_num,
    });
  }

  return {$block, $lib, $tx, $action, $delta, $table, $logger};
});

client.start();
```

### Browser (React)

```typescript
import {BlockStreamClient} from "@rockerone/ion-drive";
import type {MicroService} from "@rockerone/ion-drive";

const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  contracts: {
    "eosio.token": {
      actions: ["transfer"],
      tables: ["accounts"],
    },
  },
});

// Uses the browser's native WebSocket and fetch — no polyfills needed
const myService: MicroService = (ctx) => {
  if (ctx.$action?.name === "transfer") {
    console.log("Transfer detected!", ctx.$action.data);
  }
  return ctx;
};

client.pipe(myService).start();
```

## Configuration

### Options

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `socketAddress` | `string` | Yes | — | WebSocket endpoint for State History Plugin |
| `rpcAddress` | `string` | Yes | — | HTTP endpoint for chain RPC API |
| `contracts` | `Record<string, ContractConfig>` | No | `{}` | Contract filtering configuration |
| `enableDebug` | `boolean` | No | `false` | Enable console logging output |
| `logLevel` | `LogLevel` | No | `"error"` | Log level: `error`, `warn`, `info`, `micro`, `socket`, `debug` |
| `logger` | `Logger` | No | `ConsoleLogger` | Custom logger instance (e.g. winston, pino) |
| `webSocketConstructor` | `WebSocketConstructor` | No | `globalThis.WebSocket` | Custom WebSocket constructor |
| `fetchFunction` | `typeof fetch` | No | `globalThis.fetch` | Custom fetch function |

### Contract Filtering

```typescript
const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  contracts: {
    // Specific tables and actions
    eosio: {
      tables: ["voters", "producers"],
      actions: ["voteproducer", "regproducer"],
    },

    // All tables, specific actions
    "eosio.token": {
      tables: ["*"], // Wildcard = all tables
      actions: ["transfer", "issue"],
    },

    // All tables, all actions
    mycontract: {
      tables: ["*"],
      // No actions = all actions
    },
  },
});
```

### Legacy Format (Deprecated)

```typescript
// Still supported but use `contracts` instead
const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  tables: {
    "eosio.token": ["accounts"],
  },
});
```

## Microservice Architecture

Chain multiple processing functions together using `.pipe()`. Each microservice receives a `MicroServiceContext` and must return one:

```typescript
// Logger microservice
const logger: MicroService = ({$block, $lib, $tx, $action, $delta, $table, $logger}) => {
  if ($action) {
    $logger.info("Action", {
      contract: $action.account,
      action: $action.name,
      tx_id: $tx?.tx_id,
    });
  }
  return {$block, $lib, $tx, $action, $delta, $table, $logger};
};

// Transfer filter with finality check
const transferFilter: MicroService = ({$block, $lib, $tx, $action, ...rest}) => {
  if ($action?.name === "transfer" && $lib) {
    const isFinalized = $block.block_number <= $lib.block_num;
    console.log(`Transfer ${isFinalized ? "finalized" : "pending"}`, {
      from: $action.data.from,
      to: $action.data.to,
      amount: $action.data.quantity,
      tx_id: $tx?.tx_id,
    });
  }
  return {$block, $lib, $tx, $action, ...rest};
};

// Fork handler
const forkHandler: MicroService = ({$fork, $logger, ...rest}) => {
  if ($fork) {
    $logger.warn("Chain reorganization!", {
      fork_block: $fork.fork_block_num,
      depth: $fork.depth,
    });
  }
  return {$fork, $logger, ...rest};
};

// Chain them together
client.pipe(logger).pipe(transferFilter).pipe(forkHandler).start();
```

## MicroService Context

Every microservice receives a `MicroServiceContext`:

```typescript
interface MicroServiceContext {
  $block: BlockData;        // Block info (always present)
  $lib?: LibInfo;           // Last Irreversible Block
  $tx?: TransactionData;    // Transaction data with tx_id
  $fork?: ForkData;         // Fork event (chain reorganization)
  $delta?: TableDelta;      // Table delta
  $action?: ActionData;     // Action trace
  $table?: string;          // Table name shortcut
  $logger: Logger;          // Logger instance
}
```

### BlockData

```typescript
{
  block_number: number;
  block_id: string;
  timestamp: string;
  filtering: {
    contracts: string[];
    tables: Record<string, string[]>;
    enabled: boolean;
  };
}
```

### LibInfo

```typescript
{
  block_num: number;    // LIB block number
  block_id: string;     // LIB block hash
}
```

### TransactionData

```typescript
{
  tx_id: string;            // Transaction hash
  status: number;
  cpu_usage_us: number;
  net_usage_words: number;
}
```

### ForkData

```typescript
{
  fork_block_num: number;   // Block where fork occurred
  old_block_id: string;     // Previous block ID
  new_block_id: string;     // New block ID
  depth: number;            // Blocks rolled back
}
```

### ActionData

```typescript
{
  account: string;          // Contract account
  name: string;             // Action name
  data: {
    decoded: any;           // ABI-decoded action data
    hex: string;            // Raw hex data
    abi_decoded: boolean;   // Whether decoding succeeded
  };
  authorization: Array<{
    actor: string;
    permission: string;
  }>;
  filtered: boolean;
}
```

### TableDelta

```typescript
{
  type: string;
  contract: string;
  table: string;
  data: any;                // ABI-decoded table row
  processed: boolean;
  filtered: boolean;
}
```

## Fork Detection & Finality

Ion Drive automatically tracks block history and detects chain reorganizations (forks).

### Detecting Forks

```typescript
client.pipe(({$fork, $logger, ...rest}) => {
  if ($fork) {
    $logger.warn("Fork detected!", {
      fork_block: $fork.fork_block_num,
      depth: $fork.depth,
    });
    // Rollback any data from blocks >= fork_block_num
  }
  return {$fork, $logger, ...rest};
});
```

### Checking Finality

```typescript
// In a microservice — compare block number against LIB
client.pipe(({$block, $lib, $tx, $action, ...rest}) => {
  if ($action && $lib) {
    const isFinalized = $block.block_number <= $lib.block_num;
    if (isFinalized) {
      // Safe to consider permanent
    }
  }
  return {$block, $lib, $tx, $action, ...rest};
});

// Or use the client helper method
const isFinalized = client.isIrreversible(blockNum);

// Get current LIB info
const lib = client.getLib();
```

### Transaction Pool Pattern

Track transactions until they become irreversible:

```typescript
const txPool = new Map<string, {block_number: number; status: string}>();

client.pipe(({$block, $lib, $tx, $logger, ...rest}) => {
  if ($tx) {
    txPool.set($tx.tx_id, {
      block_number: $block.block_number,
      status: "pending",
    });
  }

  if ($lib) {
    txPool.forEach((value, key) => {
      if (value.block_number <= $lib.block_num && value.status === "pending") {
        txPool.set(key, {...value, status: "irreversible"});
        $logger.info(`Transaction ${key} is now irreversible`);
      }
    });
  }

  return {$block, $lib, $tx, $logger, ...rest};
});
```

## Logging

### Built-in Logger

Ion Drive ships with `ConsoleLogger`, a lightweight logger that works in both Node.js and browsers:

```typescript
import {ConsoleLogger} from "@rockerone/ion-drive";
// or
import {ConsoleLogger} from "@rockerone/ion-drive/logger";
```

### Log Levels

| Level | Priority | Description |
|-------|----------|-------------|
| `error` | 0 | Critical errors only |
| `warn` | 1 | Warnings and above |
| `info` | 2 | General information |
| `micro` | 3 | Microservice debugging |
| `socket` | 4 | WebSocket/protocol debugging |
| `debug` | 5 | Full debug output |

### Custom Logger

Implement the `Logger` interface to use any logging library:

```typescript
import type {Logger} from "@rockerone/ion-drive";

const myLogger: Logger = {
  error: (msg, meta) => winston.error(msg, meta),
  warn: (msg, meta) => winston.warn(msg, meta),
  info: (msg, meta) => winston.info(msg, meta),
  debug: (msg, meta) => winston.debug(msg, meta),
  socket: (msg, meta) => winston.debug(`[socket] ${msg}`, meta),
  micro: (msg, meta) => winston.debug(`[micro] ${msg}`, meta),
};

const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  logger: myLogger,
});
```

## Isomorphic Support

Ion Drive works in both Node.js and browsers without any configuration:

| Environment | WebSocket | fetch |
|-------------|-----------|-------|
| Browser | Native `WebSocket` | Native `fetch` |
| Node.js 21+ | Native `WebSocket` | Native `fetch` |
| Node.js 18-20 | `ws` package (peer dep) | Native `fetch` |

You can also inject custom implementations:

```typescript
import WebSocket from "ws";

const client = new BlockStreamClient({
  socketAddress: "ws://your-node:8080",
  rpcAddress: "https://your-node",
  webSocketConstructor: WebSocket,
  fetchFunction: customFetch,
});
```

## Examples

### React Transfer Example ([examples/react-transfer](examples/react-transfer))

A React app that demonstrates IonDrive in the browser:

1. Connect a wallet using `@rockerone/xprnkit`
2. Send a transfer on XPR Network
3. IonDrive detects the transfer on-chain in real-time
4. Live head block and LIB tracking
5. Real-time irreversibility status until the transfer is finalized

```bash
cd examples/react-transfer
npm install
npm run dev
```

Key files:
- **[src/useTransferListener.ts](examples/react-transfer/src/useTransferListener.ts)** — React hook wrapping `BlockStreamClient` as a "once" listener with live block/LIB tracking
- **[src/App.tsx](examples/react-transfer/src/App.tsx)** — UI with wallet connection, transfer button, and real-time listener status

## Network Endpoints

- **XPR Network Mainnet**: `ws://api.rockerone.io:8080` (SHiP) / `https://api.rockerone.io` (RPC)

## Requirements

- **Browser**: any modern browser (Chrome, Firefox, Safari, Edge)
- **Node.js**: 18+ (21+ for native WebSocket, or install `ws`)
- **TypeScript**: 5+
- **State History Plugin** enabled on the target blockchain node

## Development

```bash
# Install dependencies
bun install

# Build
bun run build

# Watch mode
bun run dev
```

## License

MIT License - see LICENSE file for details.

## Links

- [XPRNetwork](https://xprnetwork.org/)
- [EOSIO State History Plugin](https://developers.eos.io/manuals/eos/latest/nodeos/plugins/state_history_plugin/)