Skip to content
Merged
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
95 changes: 95 additions & 0 deletions selfhost/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ── Core ──────────────────────────────────────────────────────────────────────
# Directory on the host where all persistent data is stored
DATA_DIR=./data

# Snapshots speed up initial sync by pre-populating data directories.
# You can provide one or both. start.sh downloads and extracts them before
# starting any containers. Each is skipped if its target directory is non-empty.

# URL to a snapshot archive (.tar or .tar.gz) for the mchain data directory
# MCHAIN_SNAPSHOT_URL=https://

# URL to a snapshot archive (.tar or .tar.gz) for the Nitro data directory
# NITRO_SNAPSHOT_URL=https://

# Your appchain's chain ID
APPCHAIN_CHAIN_ID=

# Image versions (override to pin to a specific release)
# SYND_APPCHAINS_VERSION=v1.0.12
# SYND_NITRO_VERSION=eigenda-v3.7.6

# ── Ingestors (WebSocket) ──────────────────────────────────────────────────────
# WebSocket RPC URL(s) for the sequencing chain (comma-separated for redundancy)
SEQUENCING_INGESTOR_WS_URLS=wss://

# Block number to start ingesting from on the sequencing chain
SEQUENCING_INGESTOR_START_BLOCK=0

# WebSocket RPC URL(s) for the settlement chain (comma-separated for redundancy)
SETTLEMENT_INGESTOR_WS_URLS=wss://

# Block number to start ingesting from on the settlement chain
SETTLEMENT_INGESTOR_START_BLOCK=0

# ── Parent Chains Contracts ─────────────────────────────────────────────────
# Address of the sequencing contract on the sequencing chain (used by translator & batch sequencer)
SEQUENCING_CONTRACT_ADDRESS=0x

# Arbitrum bridge and inbox contract addresses on the settlement chain
ARBITRUM_BRIDGE_ADDRESS=0x
ARBITRUM_INBOX_ADDRESS=0x

# ── Batch Sequencer ────────────────────────────────────────────────────────────
# Private key of the wallet that signs and submits transaction batches
BATCHER_PRIVATE_KEY=0x

# HTTP RPC URL(s) for the sequencing chain (comma-separated)
SEQUENCING_RPC_URLS=https://

# ── Nitro ──────────────────────────────────────────────────────────────────────
# Chain info JSON blob consumed by the Nitro node (NITRO__ double-underscore prefix notation)
NITRO_CHAIN_INFO__JSON=

# ── Translator ─────────────────────────────────────────────────────────────────────
TRANSLATOR_SETTLEMENT_START_BLOCK=0
TRANSLATOR_SEQUENCING_START_BLOCK=0

# ── Proposer ───────────────────────────────────────────────────────────────────
# Private key used by the proposer to submit state commitments on-chain (WITHOUT 0x prefix)
PROPOSER_PRIVATE_KEY=

# Ethereum L1 RPC URL (used by the proposer to verify attestations)
ETHEREUM_RPC_URL=https://

# Settlement chain RPC URL and chain ID
SETTLEMENT_RPC_URL=https://
SETTLEMENT_CHAIN_ID=

# HTTP RPC URL for the sequencing chain node (used by the proposer)
SEQUENCING_RPC_URL=https://

# Beacon chain RPC URL (used for blob fetching)
BEACON_RPC_URL=https://

# URL of the TEE enclave that produces signed state commitments
ENCLAVE_RPC_URL=https://

# Contract addresses on the settlement chain
TEE_MODULE_CONTRACT_ADDRESS=0x
APPCHAIN_BRIDGE_ADDRESS=0x
SEQUENCING_BRIDGE_ADDRESS=0x


# ── MChain ─────────────────────────────────────────────────────────────────────
# ── Migration (only needed for chains migrated from vanilla orbit) ──────────
# MCHAIN_GENESIS_CONFIG=
# MIGRATED_SETTLEMENT_START_BLOCK=0
# MIGRATED_BEFORE_BATCH_ACC=0x
# MIGRATED_BATCH_ACC=0x
# MIGRATED_BATCH_COUNT=0
# MIGRATED_DELAYED_MSGS_ACC=0x
# MIGRATED_DELAYED_MSGS_COUNT=0

# ── Logging ────────────────────────────────────────────────────────────────────
RUST_LOG=info
2 changes: 2 additions & 0 deletions selfhost/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
data/
.env
167 changes: 167 additions & 0 deletions selfhost/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
services:

valkey:
image: valkey/valkey:9.0.3-alpine
container_name: valkey
command: ["valkey-server", "--save", "60", "1", "--loglevel", "warning"]
volumes:
- ${DATA_DIR:-./data}/valkey:/data
restart: unless-stopped

maestro:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-maestro:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: maestro
environment:
- VALKEY_URL=redis://valkey:6379
- CHAIN_RPC_URLS={"${APPCHAIN_CHAIN_ID}":["http://nitro:8547"]}
- SKIP_BALANCE_CHECK=true
- RUST_LOG=${RUST_LOG:-info}
depends_on:
- valkey
restart: unless-stopped

batch_sequencer:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-batch-sequencer:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: batch_sequencer
environment:
- VALKEY_URL=redis://valkey:6379
- CHAIN_ID=${APPCHAIN_CHAIN_ID}
- SEQUENCING_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS}
- BATCHER_PRIVATE_KEY=${BATCHER_PRIVATE_KEY}
- SEQUENCING_RPC_URLS=${SEQUENCING_RPC_URLS}
- RUST_LOG=${RUST_LOG:-info}
depends_on:
- valkey
- maestro
restart: unless-stopped

settlement_ingestor:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-chain-ingestor:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: settlement_ingestor
command:
- "--db-file=/data/settlement.db"
- "--start-block=${SETTLEMENT_INGESTOR_START_BLOCK:-0}"
- "--ws-urls=${SETTLEMENT_INGESTOR_WS_URLS}"
environment:
- RUST_LOG=${RUST_LOG:-info}
volumes:
- ${DATA_DIR:-./data}/settlement:/data
restart: unless-stopped

sequencing_ingestor:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-chain-ingestor:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: sequencing_ingestor
command:
- "--db-file=/data/sequencing.db"
- "--start-block=${SEQUENCING_INGESTOR_START_BLOCK:-0}"
- "--ws-urls=${SEQUENCING_INGESTOR_WS_URLS}"
environment:
- RUST_LOG=${RUST_LOG:-info}
volumes:
- ${DATA_DIR:-./data}/sequencing:/data
restart: unless-stopped

mchain:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-mchain:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: mchain
command:
- "--datadir=/data"
environment:
- APPCHAIN_CHAIN_ID=${APPCHAIN_CHAIN_ID}
- GENESIS_CONFIG
- SNAPSHOT_URL
- SETTLEMENT_START_BLOCK
- MIGRATED_BEFORE_BATCH_ACC
- MIGRATED_BATCH_ACC
- MIGRATED_BATCH_COUNT
- MIGRATED_DELAYED_MSGS_ACC
- MIGRATED_DELAYED_MSGS_COUNT
- RUST_LOG=${RUST_LOG:-info}
volumes:
- ${DATA_DIR:-./data}/mchain:/data
restart: unless-stopped

translator:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-translator:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: translator
environment:
- SETTLEMENT_WS_URL=ws://settlement_ingestor:8545
- SEQUENCING_WS_URL=ws://sequencing_ingestor:8545
- APPCHAIN_CHAIN_ID=${APPCHAIN_CHAIN_ID}
- SEQUENCING_CONTRACT_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS}
- ARBITRUM_BRIDGE_ADDRESS=${ARBITRUM_BRIDGE_ADDRESS}
- ARBITRUM_INBOX_ADDRESS=${ARBITRUM_INBOX_ADDRESS}
- SETTLEMENT_START_BLOCK=${TRANSLATOR_SETTLEMENT_START_BLOCK:-}
- SEQUENCING_START_BLOCK=${TRANSLATOR_SEQUENCING_START_BLOCK:-}
- SETTLEMENT_DELAY=60
- MCHAIN_WS_URL=ws://mchain:8545
- RUST_LOG=${RUST_LOG:-info}
depends_on:
- settlement_ingestor
- sequencing_ingestor
- mchain
restart: unless-stopped

proposer:
image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-proposer:${SYND_APPCHAINS_VERSION:-v1.0.12}
container_name: proposer
environment:
- APPCHAIN_RPC_URL=http://nitro:8547
- PRIVATE_KEY=${PROPOSER_PRIVATE_KEY}
- ETHEREUM_RPC_URL=${ETHEREUM_RPC_URL}
- SETTLEMENT_RPC_URL=${SETTLEMENT_RPC_URL}
- SETTLEMENT_CHAIN_ID=${SETTLEMENT_CHAIN_ID}
- SEQUENCING_RPC_URL=${SEQUENCING_RPC_URL}
- BEACON_RPC_URL=${BEACON_RPC_URL:-}
- ENCLAVE_RPC_URL=${ENCLAVE_RPC_URL}
- TEE_MODULE_CONTRACT_ADDRESS=${TEE_MODULE_CONTRACT_ADDRESS}
- APPCHAIN_BRIDGE_ADDRESS=${APPCHAIN_BRIDGE_ADDRESS}
- SEQUENCING_BRIDGE_ADDRESS=${SEQUENCING_BRIDGE_ADDRESS}
- SEQUENCING_CONTRACT_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS}
- MTLS_ENABLED_ENCLAVE=false
- EIGEN_RPC_URL="noop.com"
depends_on:
- nitro
restart: unless-stopped

nitro:
image: ghcr.io/syndicateprotocol/nitro/nitro:${SYND_NITRO_VERSION:-eigenda-v3.7.6}
container_name: nitro
init: true
restart: unless-stopped
environment:
- NITRO_CHAIN_INFO__JSON=${NITRO_CHAIN_INFO__JSON}
command:
- "--conf.env-prefix=NITRO"
- "--init.validate-checksum=false"
- "--parent-chain.connection.url=ws://mchain:8545"
- "--parent-chain.id=511000"
- "--chain.id=${APPCHAIN_CHAIN_ID}"
- "--http.api=arb,eth,net,web3,txpool,arbtrace,debug,synd"
- "--ws.api=arb,eth,net,web3,txpool,arbtrace,debug,synd"
- "--http.addr=0.0.0.0"
- "--http.port=8547"
- "--http.vhosts=*"
- "--http.corsdomain=*"
- "--ws.addr=0.0.0.0"
- "--ws.port=8548"
- "--ws.origins=*"
- "--node.dangerous.disable-blob-reader=true"
- "--node.inbox-reader.check-delay=100ms"
- "--node.staker.enable=false"
- "--node.parent-chain-reader.poll-interval=100ms"
- "--node.parent-chain-reader.old-header-timeout=2540400h"
- "--execution.parent-chain-reader.old-header-timeout=2540400h"
- "--execution.caching.archive=true"
- "--execution.forwarding-target=http://maestro:8080"
- "--ensure-rollup-deployment=false"
- "--persistent.chain=/home/user/data/"
# uncomment to test the setup on arm64 MacOs machine
# - "--execution.stylus-target.amd64=x86_64-linux-unknown+sse4.2+lzcnt"
ports:
- "8545:8547"
- "8548:8548"
volumes:
- ${DATA_DIR:-./data}/nitro:/home/user/data
depends_on:
- mchain
103 changes: 103 additions & 0 deletions selfhost/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Self-Hosting a Syndicate Appchain Node

Run your own Syndicate appchain RPC node with Docker Compose.

## Prerequisites

- [Docker](https://docs.docker.com/get-docker/) with the Compose plugin (v2.20+)
- `curl` and `tar` (for snapshot download)
- Disk space: 1 TB+ recommended
- RAM: 16 GB+ recommended
- CPU: 4+ cores recommended
- Ports `8545` (HTTP RPC) and `8548` (WebSocket RPC) available on the host
- WebSocket RPC access to your sequencing chain and settlement chain (e.g. via Alchemy, Infura, or a self-hosted node)

> [!NOTE]
> For synd-TEE withdrawals to be functional, you'll need to set up a synd-enclave on an AWS Nitro TEE instance and provide that URL as `ENCLAVE_RPC_URL`. See the [TEE enclave setup guide](https://docs.syndicate.io/en/docs/syndicate-stack/guides/run-withdrawals-infra#run-synd-enclave-in-aws-tee) for instructions.

## Setup


### 1. Copy the env template

```bash
cp .env.example .env
```

### 2. Fill in `.env`

Open `.env` and supply your chain-specific values.
All initial values for your specific appchain can be provided by the Syndicate team.


> [!WARNING]
> `BATCHER_PRIVATE_KEY` and `PROPOSER_PRIVATE_KEY` must have funds on the sequencing / settlement chains respectively. Additionally, the BATCHER must be authorized to sequence on the sequencing contract.

To change the location where data is persisted, set `DATA_DIR` in your `.env` before running `start.sh`.

### 3. Start

```bash
bash start.sh
```

The script will:
1. Create local data directories under `DATA_DIR` (default: `./data`)
2. Download and extract the nitro snapshot if `NITRO_SNAPSHOT_URL` is set
3. Start all services with `docker compose` (the mchain container handles its own snapshot download via `MCHAIN_SNAPSHOT_URL` if set)

## Verify

Check that all containers are running:

```bash
docker compose ps
```

Wait for the mchain and nitro to finish syncing, then test the RPC:

```bash
curl -s -X POST http://localhost:8545 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

WebSocket RPC is also available at `ws://localhost:8548`.

## Common commands

```bash
# Follow logs for all services
docker compose logs -f

# Follow logs for a specific service
docker compose logs -f nitro

# Stop all services
docker compose down

# Stop and remove all persisted data (bind-mounted)
docker compose down && rm -rf ${DATA_DIR:-./data}

# Restart a single service
docker compose restart mchain
```

## Data directory layout

```
./data/
├── mchain/ # RocksDB state for the intermediate chain node
├── nitro/ # Nitro node state
├── sequencing/ # filesystem cache for the sequencing ingestor
├── settlement/ # filesystem cache for the settlement ingestor
└── valkey/ # Valkey (Redis-compatible) persistence
```

## Summary

At this point you should have a functional synd-stack rollup deriving state from the parent chains.
You can assert that the rollup node is synced by checking the `eth_blockNumber` rpc call result.
You should also be able to send new transactions by calling `eth_sendRawTransaction` on the rollup node.
Withdrawals should also be functional, you can assert this by monitoring the `TEEModule` contract for `assertionPosted` and `closeChallengeWindow` events ([example](https://basescan.org/address/0xA61C573986bf21D1B93010c8D50909a6c313Dd61#events))

Loading
Loading