# How the Rust Fast-Broadcast Module Integrates with the Node.js Gateway for HFT Arbitrage Race Detection

> Learn how the Rust fast-broadcast module integrates with the Node.js gateway for HFT arbitrage race detection using stdin stdout IPC for sub-millisecond latency without blocking the JS event loop.

- Repository: [AL/CloddsBot](https://github.com/alsk1992/CloddsBot)
- Tags: how-to-guide
- Published: 2026-09-13

---

**The Rust fast-broadcast module integrates with the Node.js gateway via a stdin/stdout IPC bridge, allowing CloddsBot to race signed transactions across multiple RPC endpoints in sub-millisecond latency without blocking the JavaScript event loop.**

In high-frequency trading (HFT) arbitrage, winning the race to submit a transaction to the fastest accepting node can mean the difference between profit and loss. The CloddsBot repository implements a cross-language pipeline where the Node.js gateway orchestrates strategy logic while a compiled Rust binary handles the network-intensive race condition. This architecture eliminates garbage collection pauses and event-loop contention from the hot path of transaction submission.

## Why Rust for High-Frequency Trading Race Detection

The race-to-accept pattern across multiple RPC endpoints represents a **hot-path** where microseconds matter. JavaScript’s garbage collector and single-threaded event loop introduce unpredictable latency spikes that can disqualify an otherwise profitable arbitrage opportunity.

According to the source code in [`rust/fast-broadcast/src/lib.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/lib.rs), the core logic resides in the `broadcast_race` function (lines 104‑166). This implementation leverages Rust’s async runtime without a garbage collector, enabling thousands of concurrent HTTP POSTs to `eth_sendRawTransaction` with minimal overhead. The function performs three critical operations:

1. **Deduplicates** the provided RPC URLs to avoid redundant work.
2. **Spawns** a `tokio::task::JoinSet` of async workers, each issuing a raw-transaction POST via `send_raw_transaction`.
3. **Returns** the first successful response as a `BroadcastRaceResult` or aggregates errors if every endpoint fails.

## The Node.js IPC Wrapper Architecture

The TypeScript side of the integration acts as a thin IPC façade defined in [`src/evm/fast-broadcast.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/fast-broadcast.ts). Rather than implementing race logic in JavaScript, the wrapper spawns the Rust binary as a child process and communicates via JSON lines over stdin/stdout.

### Binary Resolution and Process Spawning

The wrapper first resolves the path to the compiled binary using the `FAST_BROADCAST_BIN` environment variable or falls back to the default release path. The `resolveBinaryPath()` function (lines 35‑41) constructs the path `<repo>/rust/fast-broadcast/target/release/fast-broadcast`, then spawns the process with piped stdio:

```typescript
const child = spawn(binaryPath, [], {
  stdio: ['pipe', 'pipe', 'pipe']
});

```

This invocation occurs at line 57 in [`src/evm/fast-broadcast.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/fast-broadcast.ts).

### Request-Response Protocol

Once spawned, the Node.js gateway writes a JSON-encoded request to the child’s stdin containing the raw transaction, RPC URLs, and timeout:

```typescript
const request = JSON.stringify({ rawTx, rpcUrls, timeoutMs });
child.stdin.write(request + '\n');

```

The Rust binary executes `broadcast_race` and writes the result as a single JSON line to stdout. The TypeScript wrapper parses this response (lines 70‑82) and resolves the promise with a `BroadcastRaceResult` object containing the transaction hash, winning RPC URL, latency metrics, and endpoint count. Error handling surfaces both runtime failures `child.on('error')` and worker-reported errors through a uniform `Error` interface.

## End-to-End Arbitrage Execution Flow

The integration follows a four-step pipeline when executing HFT arbitrage strategies:

1. **Gather RPC endpoints** – `getRaceUrls(chain)` pulls the primary RPC and configured fallbacks from the chain configuration located in [`src/evm/multichain.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/multichain.ts) (lines 57‑60).
2. **Prepare the transaction** – An arbitrage skill (such as the Odos swap integration) constructs a transaction object (`swapTx`).
3. **Invoke the race** – When multiple RPCs are available, the skill calls `signAndBroadcastRace` (see [`src/evm/odos.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/odos.ts), lines 306‑313). The Rust worker races the signed transaction across all endpoints, returning the hash from the fastest accepting node.
4. **Continue processing** – The skill logs the winning URL and latency, then awaits confirmation using the standard provider’s `waitForTransaction` method.

Because the Rust worker runs as an isolated subprocess, the Node.js event loop remains available to process concurrent skills, making the architecture suitable for high-frequency scenarios where every millisecond counts.

## Key Integration Points and Source Files

| Component | Purpose | Source Location |
|-----------|---------|-----------------|
| **Rust core** | Parallel submission of signed raw transactions to many RPCs, returning the first success. | [`rust/fast-broadcast/src/lib.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/lib.rs) |
| **Rust binary entry point** | Reads JSON from stdin, calls `broadcast_race`, writes JSON to stdout. | [`rust/fast-broadcast/src/main.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/main.rs) |
| **Node.js wrapper** | Spawns the binary, marshals request/response JSON, exports `broadcastRace` and `signAndBroadcastRace`. | [`src/evm/fast-broadcast.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/fast-broadcast.ts) |
| **RPC list provider** | Returns primary and fallback RPC URLs for a given chain with deduplication. | [`src/evm/multichain.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/multichain.ts) (lines 57‑60) |
| **Skill usage example** | Odos swap arbitrage leverages `signAndBroadcastRace` when fallbacks are present. | [`src/evm/odos.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/odos.ts) (lines 306‑313) |

## Implementation Examples

### Direct Rust Worker Invocation

For scenarios where the transaction is already signed, use the `broadcastRace` function to invoke the Rust binary directly:

```typescript
import { broadcastRace } from './evm/fast-broadcast';

// rawTx is a 0x-prefixed signed transaction string
const rawTx = '0x...';
const rpcUrls = [
  'https://eth-mainnet.alchemyapi.io/v2/…',
  'https://rpc.ankr.com/eth',
];
const timeoutMs = 5_000;

const result = await broadcastRace(rawTx, rpcUrls, timeoutMs);
console.log('Tx accepted by', result.wonBy, 'in', result.latencyMs, 'ms');

```

### Sign-and-Race Workflow

When the transaction requires signing, the `signAndBroadcastRace` convenience function handles both operations:

```typescript
import { signAndBroadcastRace } from './evm/fast-broadcast';
import { Wallet, type TransactionRequest } from 'ethers';
import { getRaceUrls } from './evm/multichain';

async function executeArbitrage(wallet: Wallet, tx: TransactionRequest) {
  const raceUrls = getRaceUrls('ethereum');
  const { hash, wonBy, latencyMs } = await signAndBroadcastRace(
    wallet,
    tx,
    raceUrls,
    4_000
  );

  console.log(`Arbitrage tx ${hash} won via ${wonBy} (took ${latencyMs} ms)`);
  return hash;
}

```

This pattern appears in the Odos swap integration at lines 306‑313 of [`src/evm/odos.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/odos.ts).

### Rust Entry Point Structure

The Rust binary itself operates as a minimal JSON bridge. The [`main.rs`](https://github.com/alsk1992/CloddsBot/blob/main/main.rs) file creates a Tokio runtime, deserializes the request from stdin, executes `broadcast_race`, and serializes the result to stdout:

```rust
fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();
    let req: Request = serde_json::from_str(&input).unwrap();
    let result = tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(broadcast_race(
            &req.raw_tx,
            &req.rpc_urls,
            Duration::from_millis(req.timeout_ms)
        ));
    println!("{}", serde_json::to_string(&result).unwrap());
}

```

This implementation resides in [`rust/fast-broadcast/src/main.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/main.rs).

## Summary

- The **Rust fast-broadcast module** eliminates GC latency from the transaction submission hot-path by handling the RPC race in a compiled binary using `tokio::task::JoinSet`.
- **Node.js integration** occurs via stdio IPC, where [`src/evm/fast-broadcast.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/fast-broadcast.ts) spawns the Rust process and exchanges JSON-encoded requests and responses.
- The **`broadcast_race`** function in [`rust/fast-broadcast/src/lib.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/lib.rs) (lines 104‑166) deduplicates endpoints and returns the first successful `eth_sendRawTransaction` response.
- **`getRaceUrls`** in [`src/evm/multichain.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/multichain.ts) provides the endpoint list, while skills like the Odos swap ([`src/evm/odos.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/evm/odos.ts)) conditionally invoke the race when multiple RPCs are configured.
- This architecture achieves **sub-millisecond latency** for HFT arbitrage while keeping the Node.js event loop free for concurrent strategy execution.

## Frequently Asked Questions

### How does the Node.js gateway communicate with the Rust fast-broadcast binary?

The Node.js gateway spawns the Rust binary as a child process using `child_process.spawn` with piped stdio. It writes JSON requests to the child’s stdin and parses JSON responses from stdout. This stdin/stdout protocol allows the TypeScript wrapper to remain agnostic of the internal Rust implementation while maintaining minimal latency overhead.

### Why is Rust used instead of native Node.js for the RPC race?

Rust provides a garbage-collector-free async runtime that can spawn thousands of concurrent HTTP requests without event-loop blocking. In HFT arbitrage, even a few milliseconds of GC pause or event-loop contention in JavaScript can cause a race loss. The Rust implementation in [`rust/fast-broadcast/src/lib.rs`](https://github.com/alsk1992/CloddsBot/blob/main/rust/fast-broadcast/src/lib.rs) uses `tokio::task::JoinSet` to parallelize submissions across RPC endpoints deterministically.

### What happens if all RPC endpoints reject the transaction?

If every endpoint returns an error, the `broadcast_race` function aggregates all failure messages into a single error response. The Node.js wrapper detects the `!parsed.ok` condition and surfaces this as a standard JavaScript `Error`, allowing the calling arbitrage skill to implement fallback logic or logging as needed.

### How does the system determine which RPC endpoint won the race?

The Rust worker tracks the completion order of the `JoinSet` tasks. The first successful HTTP response to `eth_sendRawTransaction` returns its transaction hash and the corresponding RPC URL as the `wonBy` field in the `BroadcastRaceResult`. The Node.js gateway logs this latency and endpoint data for performance analysis while continuing with receipt monitoring.