💻Baremetal
Authors: [man4ela | catapulta.eth]
System Requirements
8+ cores CPU
Debian 12/Ubuntu 22.04
=> 16 GB RAM
1,5TB+ (SSD or NVMe)
Unichain
Unichain operates within Optimism Superchain ecosystem. It is powered by the OP Stack, in collaboration with Optimism, leveraging the scalability and security of Optimism's Layer 2 infrastructure.
In this guide, we are walking through the process of setting up a Unichain Mainnet archive node using Optimism's op-geth and op-node
.
Before you start, make sure that you have your own synced Ethereum L1 RPC URL (e.g. Erigon) and L1 Consensus Layer Beacon endpoint with all historical blobs data
(e.g. Lighthouse) ready. A beacon endpoint meeting this criteria is essential for syncing to start.
Pre-Requisites
sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove -y
sudo apt install -y git make wget aria2 gcc pkg-config libusb-1.0-0-dev libudev-dev jq gcc g++ curl libssl-dev screen apache2-utils build-essential pkg-configv
Setting up Firewall
Set explicit default UFW rules
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH
sudo ufw allow 22/tcp
Allow remote RPC connections with Mode Node
sudo ufw allow from ${REMOTE.HOST.IP} to any port 3545
Allow remote P2P connections with Mode Node
sudo ufw allow 9222
sudo ufw allow 30303
Not advised to allow all or unknown IP address to RPC port
Enable Firewall
sudo ufw enable
To check the status of UFW and see the current rules
sudo ufw status verbose
Install dependencies
Required Software Dependencies
go
^1.23
go version
docker
^28
docker version
Install GO
sudo rm -rf /usr/local/go
wget https://go.dev/dl/go1.23.9.linux-amd64.tar.gz
sudo tar -xzf go1.23.9.linux-amd64.tar.gz -C /usr/local/
rm -f go1.23.9.linux-amd64.tar.gz
#Setup Go language export paths:
sudo tee /etc/profile.d/golang.sh > /dev/null <<EOT
export GOROOT=/usr/local/go
export GOPATH=\$HOME/go
export PATH=\$PATH:\$GOROOT/bin:\$GOPATH/bin
EOT
#Load Go environment variables into the current shell session:
source /etc/profile.d/golang.sh
Install Docker
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Check if Go and all dependencies are installed
go version
docker version
Build the Rollup Node (op-node)
Create working directory
mkdir unichain && cd unichain
Install Op-node
docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.5
CONTAINER_ID=$(docker create us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.5)
docker cp $CONTAINER_ID:/usr/local/bin/op-node ./op-node
docker rm $CONTAINER_ID
chmod +x ./op-node
Build the Execution Engine (op-geth)
Build op-geth
git clone https://github.com/ethereum-optimism/op-geth.git
cd op-geth
git checkout v1.101511.1
make geth
Download genesis.json and create jwt.hex into database directory:
mkdir -p /root/data/unichain/unichain-op-geth/ && cd /root/data/unichain/unichain-op-geth/
openssl rand -hex 32 | tr -d "\n" > /root/data/unichain/unichain-op-geth/jwt.hex
wget https://raw.githubusercontent.com/Uniswap/unichain-node/refs/heads/main/chaincon
Create systemd service for op-node
sudo nano /etc/systemd/system/unichain-op-node.service
Save by entering ctrl+X
and Y+ENTER
[Unit]
Description=Unichain OP Node Service
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=3
[Service]
Type=simple
Restart=on-failure
RestartSec=5
TimeoutSec=900
User=root
Nice=0
LimitNOFILE=200000
WorkingDirectory=/root/unichain/
Environment=OP_NODE_L2_ENGINE_AUTH=/root/data/unichain/unichain-op-geth/jwt.hex
Environment=OP_NODE_L2_ENGINE_RPC=http://0.0.0.0:8551
Environment=OP_NODE_NETWORK=unichain-mainnet
Environment=OP_NODE_LOG_LEVEL=info
Environment=OP_NODE_LOG_FORMAT=logfmt
Environment=OP_NODE_METRICS_ADDR=0.0.0.0
Environment=OP_NODE_METRICS_ENABLED=true
Environment=OP_NODE_METRICS_PORT=7007
Environment=OP_NODE_P2P_LISTEN_IP=0.0.0.0
Environment=OP_NODE_P2P_LISTEN_TCP_PORT=9222
Environment=OP_NODE_P2P_LISTEN_UDP_PORT=9222
Environment=OP_NODE_RPC_ADDR=0.0.0.0
Environment=OP_NODE_RPC_PORT=6545
Environment=OP_NODE_P2P_ADVERTISE_IP={SERVER IP}
Environment=OP_NODE_SNAPSHOT_LOG=/tmp/op-node-snapshot-log
Environment=OP_NODE_VERIFIER_L1_CONFS=4
ExecStart=/root/unichain/op-node \
--network=unichain-mainnet \
--l1={L1 RPC} \
--l1.beacon={L1 BEACON RPC} \
--l2=http://0.0.0.0:8551 \
--l1.trustrpc=true \
--syncmode=execution-layer
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
Create systemd service for op-geth
sudo nano /etc/systemd/system/unichain-op-geth.service
Paste the following configs:
[Unit]
Description=Unichain OP GETH Service
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=3
[Service]
Type=simple
Restart=on-failure
RestartSec=5
TimeoutSec=900
User=root
Nice=0
LimitNOFILE=200000
WorkingDirectory=/root/unichain/op-geth/
Environment=GETH_OP_NETWORK=unichain-mainnet
Environment=GETH_ROLLUP_SEQUENCERHTTP=https://mainnet-sequencer.unichain.org
Environment=GETH_LOG_FORMAT=logfmt
Environment=GETH_ROLLUP_DISABLETXPOOLGOSSIP=true
Environment=GETH_TXPOOL_NOLOCALS=true
Environment=GETH_GCMODE=archive
Environment=OP_NODE_L2_ENGINE_AUTH=/root/data/unichain/unichain-op-geth/jwt.hex
ExecStart=/root/unichain/op-geth/build/bin/geth \
--datadir=/root/data/unichain/unichain-op-geth/ \
--verbosity=3 \
--op-network=unichain-mainnet \
--state.scheme=hash \
--http \
--http.corsdomain="*" \
--http.vhosts="*" \
--http.addr=0.0.0.0 \
--http.port=3545 \
--http.api=eth,net,web3,debug,txpool,engine,admin,rpc \
--authrpc.addr=0.0.0.0 \
--authrpc.port=8551 \
--authrpc.vhosts="*" \
--authrpc.jwtsecret=/root/data/unichain/unichain-op-geth/jwt.hex \
--ws \
--ws.addr=0.0.0.0 \
--ws.port=3546 \
--ws.origins="*" \
--ws.api=eth,net,web3,debug,txpool,engine,admin,rpc \
--metrics \
--metrics.addr=0.0.0.0 \
--metrics.port=7900 \
--syncmode=full \
--gcmode=archive \
--maxpeers=100 \
--port=33303
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
Initialize the node:
/root/unichain/op-geth/build/bin/geth init --datadir=/root/data/unichain/unichain-op-geth --state.scheme hash /root/data/unichain/unichain-op-geth/genesis-l2.json
Launch Unichain
Start op-geth
sudo systemctl daemon-reload #refresh systemd configuration when changes made
sudo systemctl enable unichain-op-geth.service #enable unichain-op-geth service at system startup
sudo systemctl start unichain-op-geth.service #start unichain-op-geth
sudo nano /etc/systemd/system/unichain-op-geth.service #make changes in unichain-op-geth.service file
Start op-node
sudo systemctl daemon-reload #refresh systemd configuration when changes made
sudo systemctl enable unichain-op-node.service #enable unichain-op-node service at system startup
sudo systemctl start unichain-op-node.service #start unichain-op-node
sudo nano /etc/systemd/system/unichain-op-node.service #make changes in unichain-op-node.service file
Monitor the logs for errors
sudo journalctl -fu unichain-op-node.service #follow logs of unichain-op-node.service
sudo journalctl -fu unichain-op-geth.service #follow logs of unichain-op-geth.service
You are expected to get following log messages from op-node
Aug 15 13:05:58 Dawon op-node[3778198]: t=2025-08-15T13:05:58+0200 lvl=info msg="Optimistically queueing unsafe L2 execution payload" id=0x23309a24f044324a8af64eaaa823c04c9c8bc3e17826073adc62639aa7f03e8c:24507584
Aug 15 13:05:58 Dawon op-node[3778198]: t=2025-08-15T13:05:58+0200 lvl=info msg="Inserted new L2 unsafe block (synchronous)" hash=0x23309a24f044324a8af64eaaa823c04c9c8bc3e17826073adc62639aa7f03e8c number=24507584 newpayload_time=12.952ms fcu2_time=1.140ms total_time=14.093ms mgas=6.736009 mgasps=477.94030990442695
Aug 15 13:05:58 Dawon op-node[3778198]: t=2025-08-15T13:05:58+0200 lvl=info msg="Sync progress" reason="new chain head block" l2_finalized=0x7595a2fe9f3f2bb1ed9dc51ac704acff353f190b38ebf9ec3c5a1a3d037c6ba7:24465143 l2_safe=0x7595a2fe9f3f2bb1ed9dc51ac704acff353f190b38ebf9ec3c5a1a3d037c6ba7:24465143 l2_pending_safe=0x7595a2fe9f3f2bb1ed9dc51ac704acff353f190b38ebf9ec3c5a1a3d037c6ba7:24465143 l2_unsafe=0x23309a24f044324a8af64eaaa823c04c9c8bc3e17826073adc62639aa7f03e8c:24507584 l2_backup_unsafe=0x0000000000000000000000000000000000000000000000000000000000000000:0 l2_time=1755255943
Aug 15 13:05:58 Dawon op-node[3778198]: t=2025-08-15T13:05:58+0200 lvl=info msg="successfully processed payload" ref=0x23309a24f044324a8af64eaaa823c04c9c8bc3e17826073adc62639aa7f03e8c:24507584 txs=12
The expected log messages from op-geth:
Aug 15 14:49:52 Dawon geth[3778023]: t=2025-08-15T14:49:52+0200 lvl=info msg="Imported new potential chain segment" number=24513817 hash=0x09a4bb509b2dec55d9d836f898c97e0231df024af5ebaf261ef6a309ced179d4 blocks=1 txs=9 mgas=1.306784 elapsed=9.150ms mgasps=142.81143064971036 snapdiffs="1.62 MiB" triedirty="0.00 B"
Aug 15 14:49:52 Dawon geth[3778023]: t=2025-08-15T14:49:52+0200 lvl=info msg="Chain head was updated" number=24513817 hash=0x09a4bb509b2dec55d9d836f898c97e0231df024af5ebaf261ef6a309ced179d4 root=0x1189a2de7dc35f1e11d7e9e95b5fd202d36406bd49fa2e685b965ec0e1aec12c elapsed=331.435µs
Aug 15 14:49:52 Dawon geth[3778023]: t=2025-08-15T14:49:52+0200 lvl=info msg="Imported new potential chain segment" number=24513818 hash=0x236968614141c40e8983b3513fdf00c3cc1c948fa98d778ea01e15836a47a7ea blocks=1 txs=9 mgas=1.1686 elapsed=6.441ms mgasps=181.41939954929248 snapdiffs="1.62 MiB" triedirty="0.00 B"
Run curl
command in the terminal to check the status of your node
curl
command in the terminal to check the status of your nodecurl -H "Content-type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' http://localhost:3545
If it returns false
then your node is fully synchronized with the network
Sync speed depends on your L1 node, as the majority of the chain is derived from data submitted to the L1.
You can check your syncing status using the optimism_syncStatus
RPC on the op-node
optimism_syncStatus
RPC on the op-node
command -v jq &> /dev/null || { echo "jq is not installed" 1>&2 ; }
echo Latest synced block behind by: \
$((($( date +%s )-\
$( curl -s -d '{"id":0,"jsonrpc":"2.0","method":"optimism_syncStatus"}' -H "Content-Type: application/json" http://localhost:6545 |
jq -r .result.unsafe_l2.timestamp))/60)) minutes
References
Last updated
Was this helpful?