Prize-linked liquid staking protocol on Injective. Users stake INJ, receive csINJ (a liquid staking token), and are automatically entered into regular and big prize draws funded by staking rewards.
ββββββββββββββββββββ
β User stakes β
β INJ β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β Staking Hub β
β β’ Mints csINJ β
β β’ Delegates to β
β validators β
β β’ Manages epochs β
ββββββββββββββ¬ββββββββββββ
β distribute_rewards
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β Reward Distribution β
β β
β 70% βββΊ Regular Prize Pool (daily) β
β 20% βββΊ Big Jackpot Pool (monthly) β
β 5% βββΊ Base Yield (csINJ rate β) β
β 5% βββΊ Protocol Fee β
ββββββββββββββββββββ¬ββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β Reward Distributor β
β β’ Commit-reveal draws β
β β’ Merkle proof winner β
β verification β
ββββββββββββββ¬ββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β drand Oracle β
β β’ BLS-verified β
β randomness β
β β’ drand quicknet β
ββββββββββββββββββββββββββ
The protocol consists of three on-chain contracts and two off-chain services:
| Contract | Purpose |
|---|---|
| drand-oracle | Stores and verifies drand quicknet beacons (BLS12-381 signatures). Provides verifiable randomness for prize draws. |
| staking-hub | Accepts INJ deposits, mints csINJ via Token Factory, delegates to validators, manages epochs, and splits staking rewards across pools. |
| reward-distributor | Manages the prize draw lifecycle: commit-reveal with drand randomness, Merkle-proof winner verification, and reward payouts. |
Contracts live in chance-staking/contracts/ with shared types in chance-staking/packages/chance-staking-common/.
| Service | Purpose |
|---|---|
| frontend | React SPA for staking, viewing draws, and verifying winners. Connects via Keplr/Leap/MetaMask. |
| operator-node | Automated bot that advances epochs, submits drand beacons, commits and reveals draws, and manages snapshots. |
chance-staking/
βββ chance-staking/ # Smart contracts workspace (Rust/Cargo)
β βββ contracts/
β β βββ drand-oracle/ # Verifiable randomness from drand quicknet
β β βββ staking-hub/ # INJ staking, csINJ minting, epoch management
β β βββ reward-distributor/ # Prize draw commit-reveal, reward payouts
β βββ packages/
β β βββ chance-staking-common/ # Shared types + merkle proof utils
β βββ tests/
β β βββ integration/ # Cross-contract integration tests
β βββ scripts/
β βββ deploy_testnet.sh # Deploys all 3 contracts to injective-888
βββ frontend/ # React + Vite + TypeScript SPA
β βββ src/
β β βββ components/ # UI components (13+)
β β βββ store/ # Zustand state management
β β βββ services/ # Contract query/execute logic
β β βββ config.ts # Network + contract addresses
β βββ Dockerfile
βββ operator-node/ # TypeScript operator bot
β βββ src/
β β βββ services/ # Epoch, draw, drand, merkle, snapshot
β β βββ clients.ts # Injective SDK client setup
β β βββ config.ts # Environment variable config
β β βββ index.ts # Entry point
β βββ Dockerfile
βββ .github/
βββ workflows/
βββ docker.yml # Build & push Docker images
- Epoch advances β Staking hub claims validator rewards and distributes them across pools
- Snapshot taken β Operator builds a Merkle tree of all csINJ holders and submits the root on-chain
- Commit β Operator commits
sha256(secret)and a target drand round - Beacon arrives β drand quicknet produces a BLS-verified random beacon
- Reveal β Operator reveals secret, computes
final_randomness = drand_randomness XOR sha256(secret), identifies the winner via the Merkle tree, and submits a Merkle proof on-chain - Payout β Winner receives INJ from the prize pool
Winner selection: winning_ticket = u128(final_randomness[0..16]) % total_weight
- Rust and Cargo (for smart contracts)
- Docker (for optimized Wasm builds and running services)
- Node.js 20+ (for frontend and operator-node)
- injectived CLI (for contract deployment)
cd chance-staking
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="workspace_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.17.0Optimized .wasm files output to chance-staking/artifacts/.
cd chance-staking
cargo test # all 96 tests
cargo test -p chance-drand-oracle # oracle unit tests
cargo test -p chance-staking-hub # staking hub unit tests
cargo test -p chance-reward-distributor # distributor unit tests
cargo test -p chance-staking-integration-tests # integration testscd chance-staking/scripts
./deploy_testnet.shDeploys in order: drand-oracle β reward-distributor β staking-hub, then updates the distributor config with the real staking hub address.
cd frontend
npm install
npm run dev # Development server on http://localhost:5173
npm run build # Production build to dist/cd operator-node
npm install
cp .env.example .env # Fill in required values
npm run build
npm startRequired environment variables:
| Variable | Description |
|---|---|
MNEMONIC |
Operator wallet seed phrase |
DRAND_ORACLE_ADDRESS |
drand oracle contract address |
STAKING_HUB_ADDRESS |
Staking hub contract address |
REWARD_DISTRIBUTOR_ADDRESS |
Reward distributor contract address |
Optional variables:
| Variable | Default | Description |
|---|---|---|
NETWORK |
testnet |
testnet or mainnet |
EPOCH_CHECK_INTERVAL |
60 |
Seconds between epoch checks |
DRAW_CHECK_INTERVAL |
30 |
Seconds between draw checks |
DRAND_POLL_INTERVAL |
10 |
Seconds between drand beacon polls |
DRAND_API_URL |
https://api.drand.sh |
drand HTTP API endpoint |
Both the frontend and operator-node ship with multi-stage Dockerfiles.
docker build -t chance-staking-frontend ./frontend
docker run -p 3000:80 chance-staking-frontendServes the production SPA on port 80 via nginx.
docker build -t chance-staking-operator ./operator-node
docker run --env-file operator-node/.env chance-staking-operatorservices:
frontend:
build: ./frontend
ports:
- "3000:80"
operator:
build: ./operator-node
env_file: ./operator-node/.env
restart: unless-stopped| Contract | Address | Code ID |
|---|---|---|
| drand-oracle | inj1r6r6xugh3qy483g8z5jn97ssaz067epx3ac6kd |
39232 |
| reward-distributor | inj1nstzftt4tgk6gca5auluftzvzenrr606t6rrsr |
39233 |
| staking-hub | inj1ue9x4varmfaz3c8x07eqrjz4ekz7nflu50ynrk |
39234 |
Chain: injective-888 (Injective Testnet)
- csINJ: Liquid staking token minted via Token Factory. Exchange rate starts at 1.0 and increases as base yield accrues:
rate = total_inj_backing / total_csinj_supply - Epochs: Time periods (configurable, default 24h) after which rewards are harvested and distributed. Epoch duration is enforced on-chain. Users must be staked for
min_epochs_regular/min_epochs_bigepochs to be eligible for draws - Merkle Tree: Sorted-pair hashing with domain separation. Leaf:
sha256(0x00 || address_bytes || cumulative_start_be_u128 || cumulative_end_be_u128). Internal:sha256(0x01 || min(left,right) || max(left,right)) - Commit-Reveal: Two-phase draw to prevent manipulation. Operator commits before randomness is known, reveals after the drand beacon is available
- Unstaking: 21-day unbonding period (Injective native). Users call
unstakethenclaim_unstakedafter the lock expires - Minimum Stake: Configurable
min_stake_amountper transaction (default 0 = no minimum). Re-staking resets the user's epoch eligibility timer - Contract Migration: All three contracts support on-chain migration via
MigrateMsg {}
Two security audits have been completed with all 24 findings remediated:
- Audit V1: 17 findings (2 critical, 5 high, 5 medium, 5 low) β all fixed
- Audit V2: 7 findings (3 medium, 3 low, 1 informational) β all fixed
Key security improvements include: BPS sum validation, epoch duration enforcement, merkle domain separation, validator address validation, reveal deadline bounds, slashing detection (sync_delegations), snapshot overwrite prevention, and contract migration support.
Full reports and fix tracking are in chance-staking/docs/.
MIT