Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

10 changes: 0 additions & 10 deletions .eslintrc

This file was deleted.

40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Check formatting
run: yarn format:check

- name: Lint
run: yarn lint

- name: Type check
run: yarn typecheck

- name: Build
run: yarn build

- name: Test
run: yarn test
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16
22
4 changes: 2 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
}
"tabWidth": 4
}
58 changes: 38 additions & 20 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
FROM node:16
# syntax=docker/dockerfile:1

# Nym version
ARG NYM_VERSION=develop
# ---- Build stage: compile the TypeScript sources ----
FROM node:22-bookworm-slim AS builder

# Install Rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

# Setup Nym
WORKDIR /usr/src/
RUN git clone -b ${NYM_VERSION} https://github.com/nymtech/nym.git --depth 1
WORKDIR /usr/src/nym
RUN cargo build --release
ENV PATH="/usr/src/nym/target/release:${PATH}"

# Setup app
WORKDIR /usr/src/app

# Install dependencies first to leverage Docker layer caching.
COPY package.json yarn.lock ./
RUN yarn install --production --frozen-lockfile
COPY . .
RUN yarn install --frozen-lockfile

# Build the project.
COPY tsconfig.json ./
COPY src ./src
RUN yarn build

# Copy startup script
# ---- Runtime stage ----
FROM node:22-bookworm-slim

# Pinned Nym release. The prebuilt nym-client binary is downloaded directly
# from the release assets, so there is no need to compile Nym from source.
ARG NYM_VERSION=nym-binaries-v2026.11-xynomizithra

# Download the prebuilt nym-client binary.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& curl -fsSL -o /usr/local/bin/nym-client \
"https://github.com/nymtech/nym/releases/download/${NYM_VERSION}/nym-client" \
&& chmod +x /usr/local/bin/nym-client \
&& apt-get purge -y curl \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src/app
COPY ./docker/entry.sh .

CMD [ "./entry.sh" ]
# Install only production dependencies.
COPY package.json yarn.lock ./
RUN yarn install --production --frozen-lockfile && yarn cache clean

# Copy the compiled output and the startup script.
COPY --from=builder /usr/src/app/dist ./dist
COPY docker/entry.sh ./entry.sh

EXPOSE 8545

CMD ["./entry.sh"]
4 changes: 3 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,11 @@ docker build -t spook-entry .
Once you built the image and set up the `env-entry` file, you can run the docker image with the following command:

```text
docker run --env-file env-entry -p 8545:8545 -d spook
docker run --env-file env-entry -p 8545:8545 -d spook-entry
```

The image downloads a pinned, prebuilt `nym-client` binary from the [Nym releases](https://github.com/nymtech/nym/releases), so no Rust toolchain or source build is required. You can override the pinned version at build time with `--build-arg NYM_VERSION=<release-tag>`.

## Usage

### Cast
Expand Down
34 changes: 24 additions & 10 deletions docker/entry.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail

# Initialize Nym node client
nym-client init --id node --nym-apis https://validator.nymtech.net/api/ --port 3000
NYM_CLIENT_ID="${NYM_CLIENT_ID:-node}"
NYM_CLIENT_PORT="${NYM_CLIENT_PORT:-3000}"

# Start Nym node client
nym-client run --id node &
# Initialize the Nym client only once; re-running init on an existing config fails.
if [ ! -f "${HOME}/.nym/clients/${NYM_CLIENT_ID}/config/config.toml" ]; then
echo "Initializing Nym client '${NYM_CLIENT_ID}'..."
nym-client init --id "${NYM_CLIENT_ID}" --port "${NYM_CLIENT_PORT}"
fi

# Delay
sleep 5
# Start the Nym client in the background.
echo "Starting Nym client '${NYM_CLIENT_ID}'..."
nym-client run --id "${NYM_CLIENT_ID}" &
nym_pid=$!

# Forward termination signals to the Nym client so the container shuts down cleanly.
trap 'kill -TERM "${nym_pid}" 2>/dev/null || true' TERM INT

# Start
yarn start:entry
# Give the Nym client a moment to connect to the network.
sleep 5

wait -n
# Start the entry utility. The Nym client keeps the connection to the mixnet.
echo "Starting Spook entry..."
node dist/entry/index.js &
entry_pid=$!

# Exit as soon as either process exits.
wait -n "${nym_pid}" "${entry_pid}"
exit $?
7 changes: 7 additions & 0 deletions env-entry.sample
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# Address of the exit node, printed by the exit utility when it starts.
EXIT_NODE_ADDRESS={ADD-EXIT-NODE-ADDRESS-HERE}

# Nym websocket client the entry connects to (optional, defaults to ws://localhost:3000).
# NYM_HOST_URL=ws://localhost:3000

# Port the entry listens on for incoming RPC requests (optional, defaults to 8545).
# ETH_RPC_PORT=8545
5 changes: 5 additions & 0 deletions env-exit.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ethereum RPC provider the exit forwards requests to.
ETH_RPC_URL=https://eth.llamarpc.com

# Nym websocket client the exit connects to (optional, defaults to ws://localhost:3001).
# NYM_HOST_URL=ws://localhost:3001
16 changes: 16 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{
ignores: ['dist/**', 'node_modules/**'],
},
eslint.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
);
44 changes: 29 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,42 @@
"name": "spook",
"version": "1.0.0",
"description": "Mixing service using the Nym network to anonymize Ethereum RPC calls",
"main": "index.js",
"main": "dist/entry/index.js",
"author": "Daniel Luca (CleanUnicorn)",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"dependencies": {
"@types/node": "^18.11.18",
"axios": "^1.2.2",
"express": "^4.18.2",
"typescript": "^4.9.4",
"ws": "^8.11.0"
"axios": "^1.17.0",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"ws": "^8.21.0"
},
"scripts": {
"build": "tsc",
"start:entry": "npm run build && node dist/entry/index.js",
"start:exit": "npm run build && node dist/exit/index.js",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write"
"start:entry": "node dist/entry/index.js",
"start:exit": "node dist/exit/index.js",
"dev:entry": "node --import tsx --watch src/entry/index.ts",
"dev:exit": "node --import tsx --watch src/exit/index.ts",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.ts\""
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint": "^8.31.0",
"prettier": "^2.8.1"
"@eslint/js": "^10.0.1",
"@types/express": "^5.0.6",
"@types/node": "^22.10.0",
"@types/ws": "^8.18.1",
"eslint": "^10.5.0",
"prettier": "^3.8.4",
"tsx": "^4.20.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.61.0",
"vitest": "^4.1.8"
}
}
Loading
Loading