Staking is a staking backend for ETH that uses the Lido protocol on Ethereum. The system is split into five HTTP services plus one Temporal worker:
ledger— append-only money movements and materialized balancesprotocols— Lido calldata builder, chain reader, and reconciliation endpointrewards— MasterChef-style reward indexpool— request orchestration and Temporal starterrelayer— transaction signing, broadcast, and status trackingpoolworker — Temporal workflows and schedules
User-facing traffic is expected to come through your own API/BFF layer. Staking services only trust internal callers via
X-Internal-Key.
graph TD
bff["Your API / BFF"]
pool["pool"]
ledger["ledger"]
rewards["rewards"]
protocols["protocols"]
relayer["relayer"]
worker["pool worker"]
temporal["Temporal"]
eth["Ethereum RPC + Lido"]
bff --> pool
pool --> ledger
pool --> rewards
pool --> protocols
pool --> relayer
worker --> temporal
temporal --> worker
protocols --> ledger
protocols --> eth
relayer --> eth
See docs/architecture.md for the full flow and concurrency model.
Create one Postgres database per stateful service:
CREATE DATABASE ledger;
CREATE DATABASE protocols;
CREATE DATABASE rewards;
CREATE DATABASE pool;
CREATE DATABASE relayer;From the repo root:
make migrateRoll back:
make migrate-downStart Temporal locally:
make temporalStop it:
make temporal-downBefore running Staking, make sure you have:
- Go
1.25+ - PostgreSQL
15+ - an Ethereum RPC URL for the target network
- a relayer hot wallet private key in
relayer/.env
Redis is not required anymore.
Each service loads .env from its own directory.
DATABASE_URL=postgres://postgres:postgres@localhost:5432/ledger?sslmode=disable
HTTP_PORT=8101
INTERNAL_API_KEY=dev-internal-key-change-meDATABASE_URL=postgres://postgres:postgres@localhost:5432/protocols?sslmode=disable
HTTP_PORT=8102
INTERNAL_API_KEY=dev-internal-key-change-me
LEDGER_BASE_URL=http://127.0.0.1:8101
HTTP_CLIENT_TIMEOUT_SEC=30
ETH_RPC_URL=https://your-rpc
CUSTODY_ETH_ADDRESS=0xyour-custody-address
RELAYER_OPERATOR_ADDRESS=0xyour-relayer-signer
LIDO_STETH_ADDRESS=0x...
LIDO_WITHDRAWAL_QUEUE_ADDRESS=0x...
POOL_ID=eth_nativeDATABASE_URL=postgres://postgres:postgres@localhost:5432/rewards?sslmode=disable
HTTP_PORT=8103
INTERNAL_API_KEY=dev-internal-key-change-meDATABASE_URL=postgres://postgres:postgres@localhost:5432/pool?sslmode=disable
HTTP_PORT=8104
INTERNAL_API_KEY=dev-internal-key-change-me
TEMPORAL_HOST_PORT=localhost:7233
LEDGER_BASE_URL=http://127.0.0.1:8101
REWARDS_BASE_URL=http://127.0.0.1:8103
PROTOCOLS_BASE_URL=http://127.0.0.1:8102
RELAYER_BASE_URL=http://127.0.0.1:8082
HTTP_CLIENT_TIMEOUT_SEC=30
STAKE_BATCH_THRESHOLD_WEI=1000000000000000000DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=relayer
HTTP_PORT=8082
RPC_URL=https://your-rpc
CHAIN_ID=1
SIGNER_KEYS=hex-private-key-without-0x
CONFIRMATIONS=1
GAS_LIMIT=300000
RELAYER_HTTP_API_KEY=
FALLBACK_GAS_TIP_CAP_WEI=1000000000From separate terminals:
go run ./ledger/cmdgo run ./protocols/cmdgo run ./rewards/cmdgo run ./pool/cmdgo run ./relayer/cmdThen start the Temporal worker:
go run ./pool/cmd/workerhttp://127.0.0.1:8101/status/ledgerhttp://127.0.0.1:8102/status/protocolshttp://127.0.0.1:8103/status/rewardshttp://127.0.0.1:8104/status/poolhttp://127.0.0.1:8082/healthz
poolcreates apool_requestand reserves balance inledger.StakeBatchWorkflowclaims pending stake requests under a DB transaction usingFOR UPDATE SKIP LOCKED.protocolsbuilds one batched Lidosubmittransaction.relayersigns and broadcasts it.poolconfirms stake inledgerand updatesrewardsstake deltas.
poolcreates an unstake request, updatesledger, and decreases the user reward position.UnstakeWorkflowasksprotocolsto build the approve/request calldata.relayersends the transactions using stablemessage_ids derived from the Temporal workflow ID.protocolsreads the withdrawal NFT id from the tx receipt and later polls claimability.- Once claimable,
protocolsbuilds the claim tx,relayersends it, andpoolfinalizes the ledger state.
RewardDistributionWorkflowasksprotocolsfor the currentgetTotalPooledEthervalue.poolcompares it with the last stored snapshot inprotocol_metrics.- The delta becomes
new_rewards_weiand is sent torewards.Accrue. rewardsadvancesacc_reward_per_shareusing row locking and immutable event logs.
ReconciliationWorkflow calls protocols which compares:
- on-chain custody ETH balance
ledgertotal staked sum- Lido pooled ether snapshot
and writes the audit result to protocols.reconciliation_runs.
docs/architecture.md— detailed service roles, workflows, race-control, reward index mathdocs/schema.md— database schema referencedocs/example-walkthrough.md— concrete end-to-end example with table snapshots
This repository uses a production-restricted license in LICENSE.
- Non-production use (development, testing, research) is allowed.
- Production use is allowed only if you open-source the full deployed work under AGPL-3.0-or-later, or obtain prior written commercial/enterprise permission from the copyright holder.
- Unauthorized enterprise production use is subject to retroactive fees and penalties as defined in
LICENSE.