A full-stack protocol for staking runes on Bitcoin, developed by Rather Labs and for the Liquidium Foundation.
Liquidium Staking is a protocol that enables users to stake runes on Bitcoin, earning yield and participating in a decentralized staking pool. The project leverages Next.js (App Router), React, React Query, Drizzle ORM, PostgreSQL, and Redis (for distributed UTXO locks, not cache). It integrates with Internet Computer canisters for exchange rate data, mempool.space for Bitcoin blockchain data, Ordiscan and Best In Slot for rune market information, and the Liquidium internal API for rune-aware UTXO data. Transactional and digest email delivery is handled via Mailjet.
The core staking logic runs in the canister smart contract maintained at https://github.com/Liquidium-Inc/staking-canister/.
- Stake/Unstake Runes: Users can stake and unstake supported runes, receiving sLIQUIDIUM as a staking receipt.
- Serverless Backend: All backend logic runs on Next.js API routes (serverless functions).
- Distributed UTXO Locking: Uses Redis to coordinate UTXO usage and prevent
double-spendingdouble-assignation. - Blockchain Data: Integrates with Internet Computer canisters for exchange rates, mempool.space for Bitcoin blockchain data, and Ordiscan/Best In Slot for rune information.
- Email Notifications: Provides opt-in staking email alerts and a weekly digest powered by the Mailjet API.
- Portfolio & Analytics: Users can view their staking portfolio, yields, and historical performance.
src/app/— Main Next.js app, including API routes and UI.src/app/api/— Serverless API endpoints (see below for details).src/app/debug/— Debugging and development tools (see below).src/config/— Runtime configuration derived from environment variables.src/providers/— Integrations with external APIs (Liquidium API, Ordiscan, Best In Slot, mempool, email, etc).src/db/— Database schema and Drizzle ORM setup.public/— Static assets and branding.src/scripts/— Utility scripts for PSBT decoding, rune helpers, and local integration testing. Usenode src/scripts/create_principal.js --reveal-secret(or setPRINT_SECRET=true) if you need the generatedORACLE_PRIVATE_KEY; the default run only prints the principal for safety.
Copy .env.example to .env and fill in the required values:
NEXT_PUBLIC_NETWORK— Network to use (mainnetortestnet4).DUST_AMOUNT— Minimum output value in sats. Recommended to be 546.DESIRED_UTXOS— Limit for the protocol to bisect UTXOs when stakes or unstakes take place.MEMPOOL_BALANCE— Use mempool for balance calculations instead of relying on the Best In Slot balance endpoint (boolean).MEMPOOL_HOST— Mempool API host used for the backend.NEXT_PUBLIC_MEMPOOL_URL— Public mempool explorer URL. Used solely for the front-end.DATABASE_URL— PostgreSQL connection string.REDIS_URL— Redis connection string. Used for the assignation of the UTXOs.CRON_SECRET— Secret for securing cron endpoints.ORACLE_PRIVATE_KEY— Private key used to authorize canister exchange rate operations.CANISTER_*— Canister configuration (CANISTER_ID,CANISTER_ADDRESS,CANISTER_PUBLIC_KEY,CANISTER_SECRET,RETENTION_ADDRESS,RETENTION_SECRET,CANISTER_MOCK, etc).BEST_IN_SLOT_URL/BEST_IN_SLOT_TOKEN— Best In Slot API base URL and token for rune data.ORDISCAN_API_URL/ORDISCAN_API_TOKEN— Ordiscan API base URL and token for rune market data.LIQUIDIUM_API_URL/LIQUIDIUM_API_TOKEN— Liquidium internal API base URL and token for rune-aware UTXO data.NEXT_PUBLIC_STAKED_*— Metadata for the staked rune (ID,NAME,SYMBOL,DECIMALS, etc).NEXT_PUBLIC_STAKED_SUPPLY— Total supply of the staked rune. Used for computing the exchange rate.NEXT_PUBLIC_TOKEN_*— Metadata for the underlying rune (ID,NAME,SYMBOL,DECIMALS, etc).NEXT_PUBLIC_EXPECTED_CONFIRMATIONS— Number of confirmations required to consider a transaction completed.NEXT_PUBLIC_WITHDRAW_TIME— Withdrawal lock time when the user unstake (in seconds).NEXT_PUBLIC_OVERWRITE_TOKEN_CONFIG— Toggle for overriding on-chain token metadata with environment configuration.NEXT_PUBLIC_DEBUG_TOKEN_PRICE/NEXT_PUBLIC_DEBUG_BTC_PRICE— Optional debug overrides for UI previews.MAILJET_API_KEY/MAILJET_API_SECRET/FROM_EMAIL/BASE_URL— Mailjet credentials and default site URL for transactional and digest emails.NEXT_PUBLIC_SITE_URL— Public-facing site URL for canonical links and redirects.NEXT_PUBLIC_POSTHOG_KEY/POSTHOG_API_KEY/POSTHOG_HOST/POSTHOG_SERVER_HOST— PostHog analytics configuration.GENERATE_SOURCEMAPS— Enables source map generation and PostHog uploads during builds.
| Endpoint | Method | Description |
|---|---|---|
/api/stake |
POST | Create a PSBT for staking runes (user must sign with their wallet) |
/api/unstake |
POST | Create a PSBT for unstaking and redeeming sLIQUIDIUM (user must sign with their wallet) |
/api/withdraw |
POST | Create a PSBT for withdrawing tokens after unstaking (user must sign with their wallet) |
/api/stake/confirm |
POST | Validate, send to canister, and broadcast the signed stake transaction |
/api/unstake/confirm |
POST | Validate, send to canister, and broadcast the signed unstake transaction |
/api/withdraw/confirm |
POST | Validate, send to canister, and broadcast the signed withdrawal transaction |
/api/protocol |
GET | Get protocol stats and configuration |
/api/account/balance |
GET | Get wallet balances for runes |
/api/account/txs |
GET | Get wallet transaction history |
/api/fee-rates |
GET | Get recommended fee rates from mempool.space with graceful fallbacks |
/api/stake/pending |
GET | List pending stake transactions |
/api/unstake/pending |
GET | List pending unstake transactions |
/api/email/subscribe |
POST | Subscribe a wallet address to staking digest emails |
/api/email/status |
GET | Check the subscription status for an address |
/api/email/unsubscribe |
GET/POST | Remove an address from staking digest emails |
/api/email/verify |
GET | Verify an email subscription token (redirects with status) |
/api/cron/* |
GET | Internal cron endpoints (require secret) |
Endpoints
/stake,/unstake, and/withdrawonly create the PSBT that the user must sign with their wallet. The corresponding/confirmendpoints validate the signed transaction, interact with the canister, and broadcast it to the Bitcoin network.
This is the most critical cron job in the system because it maintains the exchange rate between the staked tokens and the underlying tokens based on the pool balances.
It retrieves the current exchange rate by calling canister.getExchangeRate() which provides:
- Circulating supply from the canister
- Current balance from the canister
- Block height from mempool.space API
The job calculates the staked balance (total supply - circulating) and stores the pool balances in the database for historical tracking and yield calculations.
This job is crucial because it:
- Ensures accurate conversion rates between staked and unstaked tokens
- Maintains the protocol's economic model
- Provides data for yield calculations
- Uses reliable data sources (canister + mempool.space)
This cron job is responsible for monitoring the status of stake, unstake, and withdraw transactions. It checks the status of the transactions in the blockchain and updates the status in the database.
It also checks if the transactions are confirmed and if they are, it updates the status in the database.
This cron job is responsible for fetching the token holders of the staked rune and storing them in the database. This is only used for analytics and preserving the data for future reference.
Generates and sends a weekly staking digest email to subscribed addresses. It aggregates balances, recent activity, APY snapshots, and rune market data (via Ordiscan with Best In Slot fallback) before delivering through the Mailjet provider.
This folder contains several pages and utilities for development and protocol debugging:
portfolio/— Simulate and visualize staking portfolio and yield scenarios.utxos/— Visualize and debug UTXO distribution and assignment.etcher/— Test rune etching (minting) flows.minter/— Test rune minting flows.signer/— Test wallet signing flows.
These tools are not intended for production use, but are for protocol development and testing.
Prerequisites:
- Node.js 20 (LTS) or newer
- pnpm 10+
- Install dependencies:
pnpm install
- Copy
.env.exampleto.envand configure your environment variables. - Run the development server:
pnpm dev
- Open http://localhost:3000 in your browser.
The project is configured for deployment on Vercel. Push to the main branch and Vercel will auto-deploy.
We welcome community contributions! Please read CONTRIBUTING.md for details on development workflows, coding standards, and how to propose changes. All participants are expected to follow our Code of Conduct.
If you discover a vulnerability, please follow the guidelines in SECURITY.md to report it responsibly. Do not open public issues for security-sensitive reports.
Distributed under the GNU GPL v3.0.
Developed by Liquidium Foundation in collaboration with Rather Labs.