RougeChain Documentation
Welcome to RougeChain — a post-quantum secure Layer 1 blockchain built with real NIST-approved cryptography.
What is RougeChain?
RougeChain is among the first blockchains designed from the ground up to be resistant to quantum computer attacks. It uses:
- ML-DSA-65 (CRYSTALS-Dilithium) for digital signatures
- ML-KEM-768 (CRYSTALS-Kyber) for key encapsulation
- SHA-256 for hashing
All cryptographic primitives are NIST FIPS 204/203 compliant.
Key Features
| Feature | Description |
|---|---|
| Post-Quantum Security | Protected against both classical and quantum attacks |
| Client-Side Signing | Private keys never leave your browser |
| AMM/DEX | Uniswap V2-style liquidity pools and token swaps |
| Token Burning | Official burn address with on-chain tracking |
| Proof of Stake | Energy-efficient consensus with validator staking |
| P2P Network | Decentralized peer-to-peer block and transaction propagation |
| qETH Bridge | Bridge ETH from Base Sepolia to qETH on RougeChain |
| Custom Tokens | Create your own tokens on the network |
| RC-721 NFTs | NFT collections with royalties, batch minting, and freezing |
| Encrypted Messenger | E2E encrypted messaging with PQC, media support, self-destruct |
| PQC Mail | Encrypted email with @rouge.quant addresses and threading |
| Browser Extensions | Chrome/Firefox wallet extensions with vault lock |
| PWA Support | Installable progressive web app for mobile and desktop |
| Social Layer | Posts, timeline, reposts, likes, follows, comments, tips |
| CLI Wallet | Command-line wallet with full chain access and social commands |
| SDK | @rougechain/sdk v1.4.0 npm package for building dApps |
| EIP-1559 Dynamic Fees | Base fee auto-adjusts per block, fee burning for deflationary pressure |
| Token Mint Authority | Ongoing minting for custom tokens with supply cap enforcement |
| Validator Slashing | Slash penalties for misbehavior, unbonding queue with 500-block delay |
| BFT Finality Proofs | Serializable quorum certificates with ≥2/3 validator stake |
| WebSocket Subscriptions | Topic-based real-time event streaming (blocks, txs, accounts, tokens) |
| HD Wallet Derivation | BIP-44-like PQC key derivation from master seed (HMAC-SHA256) |
| Open Source | Apache 2.0 licensed node software |
Quick Links
- Getting Started
- Running a Node
- API Reference
- P2P Networking
- Staking & Validators
- Browser Extensions
- CLI Wallet
- SDK
- Architecture
- GitHub (Node Source)
Network Info
Mainnet is live (chain-id rougechain-mainnet-1) alongside the public testnet.
| Network | Chain ID | API Endpoint |
|---|---|---|
| Mainnet | rougechain-mainnet-1 | https://api.rougechain.io/api |
| Testnet | — | https://testnet.rougechain.io/api |
| Devnet (local) | — | http://127.0.0.1:5101/api |
A local node's default
--api-portis 5101.
Tokens
XRGE
XRGE is the native token that powers transactions, secures the network through staking, and fuels the post-quantum economy.
| Role | How it works |
|---|---|
| Gas Token | Every transaction pays fees in XRGE. 50% of the base fee is burned; the remaining tip pool is split proposer 20% / validators 70% (stake-weighted) / treasury 10%. |
| Staking Primitive | Validators must stake XRGE to propose blocks. More stake = more proposals = more rewards. |
| DeFi Base Pair | AMM liquidity pools trade against XRGE. It's the default quote currency on the built-in DEX. |
| Bridge Asset | XRGE exists on both RougeChain (native) and Base (ERC-20) via the cross-chain bridge. |
qETH
qETH is a bridged representation of ETH on RougeChain. It uses 6 decimal places and can be bridged in from Base Sepolia or withdrawn back.
| Property | Value |
|---|---|
| Decimals | 6 |
| Bridge Source | Base Sepolia |
| Bridge Contract | Configured per-node via --bridge-custody-address |
Fees
| Action | Fee |
|---|---|
| Transfer | ~0.1 XRGE (base fee, adjusts per block) |
| Token Creation | 100 XRGE |
| Pool Creation | 10 XRGE |
| Swap | 0.3% (to LPs) |
| Minimum Stake | 10,000 XRGE |
| Unbonding Period | 500 blocks |
EIP-1559 Fee Model: The base fee adjusts ±12.5% per block based on transaction volume (target: 10 txs/block). 50% of the base fee is burned; the remaining tip pool is split proposer 20% / validators 70% (stake-weighted) / treasury 10%. Check current fees via
GET /api/fee.
Burn Address
Tokens can be permanently burned by sending to the official burn address:
XRGE_BURN_0x000000000000000000000000000000000000000000000000000000000000DEAD
Burned tokens are tracked on-chain and can be queried via the /api/burned endpoint.
Security
Client-Side Signing
RougeChain uses a secure v2 API where all transactions are signed client-side:
- Your wallet creates a transaction payload
- The payload is signed locally using ML-DSA-65
- Only the signature and public key are sent to the server
- Your private key never leaves your browser
This ensures maximum security even when interacting with untrusted nodes.
Getting Started
This guide will help you get up and running with RougeChain in minutes.
Prerequisites
- A modern web browser (Chrome, Firefox, Edge, Safari)
- For running a node: Rust toolchain (1.80+)
Quickest Path
- Visit rougechain.io
- Click "Wallet" in the sidebar
- Create (or import) a wallet and set a password (min 8 chars) — your keys are encrypted at rest with AES-256-GCM (PBKDF2, 600k iterations) and the decrypted key lives only in memory, never on disk
- Use the faucet to get free testnet XRGE
That's it! You now have a quantum-secure wallet.
What's Next?
- Quick Start Guide - Detailed walkthrough
- Create a Wallet - Understanding your keys
- Get Test Tokens - Using the faucet
- Running a Node - Participate in the network
Quick Start
Get started with RougeChain in 5 minutes.
Step 1: Access the Web App
Visit rougechain.io or run locally:
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node
npm install
npm run dev
Open http://localhost:5173 in your browser.
Step 2: Create Your Wallet
Create (or import) a wallet and set a password (min 8 characters) at creation. It includes:
- Address (
rouge1...) — Your compact Bech32m address, share freely - Private Key (ML-DSA-65) — Never share this!
- Encryption Key (ML-KEM-768) — For secure messaging
Your keys are encrypted at rest with AES-256-GCM (PBKDF2, 600k iterations). The decrypted key lives only in memory (chrome.storage.session) and is never written to disk — only the encrypted blob is persisted. (Legacy plaintext wallets are force-migrated to encrypted storage on next unlock.)
Step 3: Get Test Tokens
- Click Wallet in the sidebar
- Click Request from Faucet
- Receive 10,000 XRGE instantly (24-hour cooldown per address)
Step 4: Send Your First Transaction
- Click Send
- Enter recipient address
- Enter amount
- Click Send XRGE
Transaction is signed with your ML-DSA-65 key and broadcast to the network.
Step 5: View on Blockchain
- Click Blockchain in sidebar
- See your transaction in the latest block
- Verify the PQC signature
What's Next?
- Run your own node
- Stake and become a validator
- Create custom tokens
- Use encrypted messenger
- Send encrypted mail
- Install the browser extension
- Use the SDK
Troubleshooting
"Failed to fetch" Error
- Check if you're connected to the right network (Testnet vs Devnet)
- Ensure the node is running if using local devnet
"Insufficient balance"
- Transaction requires amount + 0.1 XRGE fee
- Use faucet to get more tokens
Wallet not loading
- Clear browser cache
- Check browser console for errors
Create a Wallet
You explicitly create (or import) your RougeChain wallet and set a password (minimum 8 characters) at creation. Here's what you need to know about it.
Wallet Components
| Component | Algorithm | Purpose |
|---|---|---|
| Signing Key | ML-DSA-65 | Sign transactions, prove ownership |
| Encryption Key | ML-KEM-768 | Encrypt/decrypt messages |
Your Address
RougeChain uses compact Bech32m addresses with the rouge1 prefix, derived from your ML-DSA-65 public key:
address = bech32m("rouge", SHA-256(signing_public_key))
Example address:
rouge1q8f3x7k2m4n9pvj5dz6ywl2cg8hs0kw9...
Addresses are ~63 characters — much shorter than the raw 3,904-char hex public key. The wallet, extension, and explorer all display this format.
Note: API endpoints still use the raw hex public key internally. The
rouge1address is for display, sharing, and QR codes.
Backup Your Wallet
CRITICAL: Your private key is only stored locally. If you clear browser data or lose your device, you lose access to your funds.
Encrypted Backup (.pqcbackup)
All RougeChain wallets use password-protected encrypted backups. Plaintext key exports are disabled across all platforms.
How to Export
| Platform | Steps |
|---|---|
| Web (rougechain.io) | Wallet → Settings → Backup → Enter password → Download .pqcbackup file |
| Browser Extension (RougeChain Wallet) | Settings → Export Encrypted Backup → Enter password → Download |
| Mobile (Qwalla) | Settings → Export Encrypted Backup → Enter password → Share/Save |
How to Restore
- Go to Wallet page (or Settings in the extension)
- Click Restore or Import Wallet
- Select your
.pqcbackupfile - Enter the password you used when exporting
- Your wallet keys are decrypted and restored
Seed Phrase (Mnemonic)
Wallets also support 24-word BIP-39 mnemonic backup (256-bit entropy for post-quantum security):
- Go to Settings → Reveal Seed Phrase
- Write down all 24 words in order
- Store securely — anyone with your seed phrase has full access to your wallet
Tip: Use the seed phrase as your primary backup method. The
.pqcbackupfile is best for transferring between devices.
.pqcbackup File Format
The encrypted backup file uses industry-standard cryptography:
| Component | Algorithm | Details |
|---|---|---|
| Key Derivation | PBKDF2-SHA256 | 600,000 iterations |
| Encryption | AES-256-GCM | 256-bit key, 96-bit IV |
| Salt | Random | 16 bytes per export |
| IV | Random | 12 bytes per export |
File Structure
{
"version": 1,
"salt": "<hex-encoded 16-byte random salt>",
"iv": "<hex-encoded 12-byte random IV>",
"ciphertext": "<hex-encoded AES-256-GCM encrypted wallet data>",
"algorithm": "PBKDF2-SHA256-AES-256-GCM"
}
How It Works
- Your password is stretched with PBKDF2-SHA256 (600,000 rounds) using a random salt
- The derived 256-bit key encrypts your wallet data with AES-256-GCM
- The salt, IV, and ciphertext are bundled into a
.pqcbackupJSON file - Without the correct password, the file cannot be decrypted
Warning: There is no password recovery. If you forget your backup password, use your seed phrase to restore instead.
Security Best Practices
- Never share your private key or seed phrase
- Backup your wallet immediately after creation
- Use a strong, unique password for your
.pqcbackupfile - Store backups in multiple locations (password manager, USB, etc.)
- Never screenshot your seed phrase or keys
- Verify addresses before sending
Technical Details
Key Generation
// 24-word mnemonic (256-bit entropy — post-quantum safe)
const mnemonic = bip39.generateMnemonic(256);
// Derive 32-byte seed via HKDF-SHA256
const seed = hkdf(mnemonicToSeed(mnemonic), "rougechain-pqc-v1");
// Signing keypair (ML-DSA-65 / FIPS 204)
const signingKeypair = ml_dsa65.keygen(seed);
// Encryption keypair (ML-KEM-768 / FIPS 203)
const encryptionKeypair = ml_kem768.keygen(seed);
Key Storage
Keys are encrypted at rest with AES-256-GCM (PBKDF2, 600,000 iterations). Only the encrypted blob is persisted to disk; the decrypted key is held only in memory for the active session.
| Platform | Storage |
|---|---|
| Web (rougechain.io) | encrypted blob in localStorage; decrypted key in memory only |
| Browser Extension (RougeChain Wallet) | encrypted AES-256-GCM vault in chrome.storage.local; decrypted key in chrome.storage.session, never written to disk |
| Mobile (Qwalla) | expo-secure-store → device keychain |
Terminal EVM Wallet (tools/evm-cli)
For Base/EVM operations there's a companion terminal wallet, rougechain-evm (tools/evm-cli). It derives your Base account from the same 24-word mnemonic (read from the ROUGECHAIN_MNEMONIC env var) and supports new, address, balance, send-eth, and send-token.
Get Test Tokens
The faucet distributes free XRGE tokens for testing on testnet.
Using the Web UI
- Go to the Wallet page
- Click Request from Faucet
- Receive 10,000 XRGE instantly
Using the API
curl -X POST https://testnet.rougechain.io/api/faucet \
-H "Content-Type: application/json" \
-d '{"recipientPublicKey": "your-hex-public-key-here"}'
The field is
recipientPublicKeyand must be a raw hex public key, not arouge1…address. The faucet is OFF by default and must be explicitly enabled with--faucet-enabled(orQV_FAUCET_ENABLED=1) — it is for dev/testnet only, and mainnet must never set it. When disabled, requests return"Faucet is disabled on this network."
Response:
{
"success": true,
"amount": 10000,
"txId": "abc123..."
}
Rate Limits
| Condition | Limit |
|---|---|
| Per address | 1 request / 24 hours |
There is no per-IP rate limit. A request is also rejected if the recipient already has a pending faucet transaction, or if their balance exceeds the faucet threshold.
Whitelisting
The whitelist gates faucet access — it does not grant a higher rate. When QV_FAUCET_WHITELIST is set, only the listed addresses may use the faucet; everyone else is blocked. Leave it unset to allow any address (subject to the 24-hour cooldown).
# Start node with the faucet enabled and a whitelist
./quantum-vault-daemon --mine --faucet-enabled \
--faucet-whitelist "pubkey1,pubkey2,pubkey3"
# Or via environment variables
export QV_FAUCET_ENABLED=1
export QV_FAUCET_WHITELIST="pubkey1,pubkey2"
./quantum-vault-daemon --mine
Troubleshooting
"Rate limited"
Each address can request once every 24 hours. Wait for the cooldown to elapse or use a different address.
"Faucet disabled"
The node may not have faucet enabled. Check node configuration.
Transaction not appearing
- Check the block explorer for your tx
- Verify you're on the correct network
- Wait for the next block (1-2 seconds)
Running a Node
Run your own RougeChain node to sync the chain, validate transactions, serve an API to others, and — if you stake — produce blocks and earn rewards.
The node software is open source on GitHub.
Node Types
| Type | What it does | Key flags |
|---|---|---|
| Full node | Syncs and validates all blocks from peers | --peers |
| Mining / validator node | Produces blocks (requires ≥ 10,000 XRGE staked to be selected) | --mine |
| Public node | Serves an API/RPC to other users and apps | --host 0.0.0.0 + reverse proxy |
A single node can be all three at once.
Minimum System Requirements
RougeChain's node is deliberately lightweight — the Rust daemon is a single ~23 MB binary and the live mainnet validator holds a steady ~40 MB of RAM. The heaviest part is building it, not running it.
To run a node
| Minimum | Recommended (public / validator) | |
|---|---|---|
| CPU | 1 vCPU | 2+ vCPU |
| RAM | 1 GB | 2–4 GB |
| Disk | 5 GB SSD | 20+ GB SSD (chain data grows over time; SSD matters for the embedded sled DB) |
| Network | Any stable broadband | ≥ 100 Mbps, static IP or a domain with TLS |
| OS | Linux (Ubuntu 22.04+), Windows, or Docker | Linux |
The daemon itself uses tens of MB of RAM; the extra headroom is for the OS, chain-data growth, and serving concurrent API traffic.
To build from source
| Requirement | |
|---|---|
| Rust | Recent stable toolchain (1.80+); the project builds on 1.94 |
| RAM during compile | ~4 GB |
Free disk for target/ | ~6 GB |
| Tools | git, a C toolchain (build-essential on Debian/Ubuntu) |
A clean release build takes roughly 2–5 minutes depending on cores. If you only want to run a node and not build it, you can also use the Docker image.
Quick Start
# Clone and enter the workspace
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node/core
# Build the daemon (release)
cargo build --release -p quantum-vault-daemon
Join Mainnet
Mainnet requires the mainnet genesis file (shipped in the repo) and the mainnet chain-id:
./target/release/quantum-vault-daemon \
--genesis daemon/genesis-mainnet.json \
--chain-id rougechain-mainnet-1 \
--api-port 5100 \
--data-dir ~/.quantum-vault/mainnet \
--peers "https://api.rougechain.io/api" \
--node-name my-node
Add --mine if you intend to validate (you'll only be selected once you've staked ≥ 10,000 XRGE — see Staking).
Join Testnet
Testnet uses default parameters (no genesis file needed):
./target/release/quantum-vault-daemon \
--chain-id rougechain-devnet-1 \
--api-port 5101 \
--data-dir ~/.quantum-vault/testnet \
--peers "https://testnet.rougechain.io/api" \
--node-name my-testnet-node
Running a Public Node (for other people)
If you want your node to serve an API/RPC to users, wallets, or apps, run it as a public node. Do not expose the daemon port directly — put a TLS reverse proxy (nginx) in front and keep the daemon bound to localhost or firewalled.
Recommended operator setup:
- Bind and advertise — run with
--host 0.0.0.0 --api-port 5100(firewalled) and set--public-url https://your-domain/apiso peers can reach you. - Terminate TLS at nginx — proxy
https://your-domain/api→http://127.0.0.1:5100. A full nginx example is in Running a Public Node. - Protect writes — set
--api-keys "<key1>,<key2>"so only holders of a key can hit write endpoints. (Signed/api/v2/*and public/api/faucet,/api/bridge/*handle their own auth.) - Rate-limit — e.g.
--rate-limit-per-minute 120(plus--rate-limit-read/write-per-minutefor finer control) to keep a single client from overwhelming the node. - Run under systemd — so it restarts on crash/reboot. Example unit and hardening in Public Node and Configuration.
- Monitor — poll
/api/health(liveness) and/api/stats(height, peers, mining) from your uptime checker.
Security note: legacy
v1write endpoints that accept a raw private key are development-only (enabled with--dev) and return410 Gonein a normal build — never run a public node with--dev.
Verify It's Working
curl http://127.0.0.1:5100/api/health
Expected response (mainnet):
{
"status": "ok",
"chain_id": "rougechain-mainnet-1",
"height": 12345
}
Check sync progress and peers:
curl http://127.0.0.1:5100/api/stats
Next Steps
- Installation — detailed setup, per-OS
- Configuration — every CLI flag and environment variable
- Running a Public Node — full nginx + systemd operator guide
- Mining — block production and validator selection
- Staking — the 10,000 XRGE minimum and rewards
Installation
System Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2+ vCPU |
| RAM | 1 GB (run) · ~4 GB (build) | 2–4 GB |
| Disk | 5 GB SSD | 20 GB SSD |
| Network | 10 Mbps | 100 Mbps |
| OS | Linux (Ubuntu 22.04+, Debian 12), macOS, Windows | Ubuntu 22.04 LTS |
The daemon is a single Rust binary with low overhead. A $5/month VPS (Hetzner CX22, DigitalOcean Basic Droplet, Vultr Cloud Compute) is more than enough for testnet.
Docker (Fastest)
Run a node with a single command — no Rust toolchain needed:
docker run -d \
--name rougechain-node \
-p 5100:5100 \
-v qv-data:/data \
rougechain/node \
--host 0.0.0.0 --api-port 5100 \
--mine --peers https://testnet.rougechain.io/api
Verify it's running:
curl http://127.0.0.1:5100/api/health
docker-compose
For a persistent setup, create a .env file:
API_PORT=5100
QV_PEERS=https://testnet.rougechain.io/api
CHAIN_ID=rougechain-devnet-1
Then start:
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node
docker compose up -d
View logs:
docker compose logs -f node
Build the image locally
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node
docker build -t rougechain/node .
Build from Source
Install and build manually if you prefer not to use Docker.
Prerequisites
Linux / macOS
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Install build dependencies (Ubuntu/Debian)
sudo apt update && sudo apt install -y build-essential pkg-config libssl-dev
# Install build dependencies (macOS)
xcode-select --install
Windows
- Install Rust
- Install Visual Studio Build Tools
- Ensure
cargois in your PATH
Compile
# Clone the repository
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node/core
# Build release binary
cargo build --release -p quantum-vault-daemon
# Binary location
./target/release/quantum-vault-daemon --help
Verify Installation
./target/release/quantum-vault-daemon --version
Expected output:
quantum-vault-daemon 0.1.0
First Run
Option A: Connect to Testnet
./target/release/quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
Option B: Start Local Devnet
./target/release/quantum-vault-daemon --mine --api-port 5100
Verify Node is Running
curl http://127.0.0.1:5100/api/health
Expected response:
{"status":"ok","chain_id":"rougechain-devnet-1","height":0}
Directory Structure
After first run, data is stored at:
| OS | Default Location |
|---|---|
| Linux/macOS | ~/.quantum-vault/core-node/ |
| Windows | C:\Users\<you>\.quantum-vault\core-node\ |
Updating
Bring your node up to the latest main with one command — it fetches, fast-forwards,
rebuilds the release binary, restarts the service, and health-checks:
cd rougechain-node
./scripts/update-node.sh
It refuses to run on a dirty working tree and only fast-forwards, so it never clobbers local changes. Override the defaults with env vars if your setup differs (systemd unit name, port, etc.):
ROUGECHAIN_SERVICE=my-node ROUGECHAIN_API_PORT=5100 ./scripts/update-node.sh
Manual equivalent
cd rougechain-node
git pull --ff-only
cd core
cargo build --release -p quantum-vault-daemon
sudo systemctl restart rougechain # your service name
Troubleshooting
"cargo not found"
source ~/.cargo/env
# or restart your terminal
"OpenSSL not found"
# Ubuntu/Debian
sudo apt install libssl-dev pkg-config
# Fedora/RHEL
sudo dnf install openssl-devel
# macOS
brew install openssl
"Address already in use"
Another process is using port 5100:
# Find and kill it
lsof -i :5100
kill -9 <PID>
# Or use a different port
./quantum-vault-daemon --api-port 5101
Build fails on Windows
Ensure you have Visual Studio Build Tools with C++ workload installed.
Environment Variables
All CLI flags can also be set via environment variables:
| Variable | CLI Flag | Default | Description |
|---|---|---|---|
QV_PEERS | --peers | — | Comma-separated peer URLs |
QV_PUBLIC_URL | --public-url | — | This node's public URL for peer discovery |
QV_CORS_ORIGINS | — | localhost only | Comma-separated allowed CORS origins |
QV_API_KEYS | --api-keys | — | Comma-separated API keys for authenticated access |
QV_BRIDGE_CUSTODY_ADDRESS | --bridge-custody-address | — | EVM custody address (enables bridge) |
QV_BASE_SEPOLIA_RPC | --base-sepolia-rpc | https://sepolia.base.org | Base Sepolia RPC URL |
Common CLI-only flags:
| Flag | Default | Description |
|---|---|---|
--host | 127.0.0.1 | Bind address. Use 0.0.0.0 for public nodes |
--api-port | 5101 | REST API port |
--mine | off | Enable block production |
--data-dir | ~/.quantum-vault/core-node/ | Chain data directory |
--chain-id | rougechain-devnet-1 | Network chain ID |
--block-time-ms | 400 | Target block time in milliseconds |
Running a Node on Windows
A step-by-step guide for building and running a RougeChain node on Windows.
Prerequisites
1. Install Rust
Download and run the installer from rustup.rs. Use the default options.
After installation, open a new PowerShell window and verify:
cargo --version
rustc --version
2. Install Visual Studio Build Tools
Download Visual Studio Build Tools and install the "Desktop development with C++" workload.
This provides the MSVC compiler and Windows SDK needed to build native Rust projects.
3. Install Git (if needed)
Download from git-scm.com or install via winget:
winget install Git.Git
Build the Node
Open PowerShell and run:
git clone https://github.com/cyberdreadx/rougechain-node.git
cd rougechain-node\core
cargo build --release -p quantum-vault-daemon
The first build takes 5-10 minutes. The binary will be at:
target\release\quantum-vault-daemon.exe
Run the Node
Connect to Testnet
.\target\release\quantum-vault-daemon.exe --api-port 5100 --peers "https://testnet.rougechain.io/api"
Run a Mining Node
.\target\release\quantum-vault-daemon.exe --mine --api-port 5100 --peers "https://testnet.rougechain.io/api"
Name Your Node
.\target\release\quantum-vault-daemon.exe --api-port 5100 --node-name "MyWindowsNode" --peers "https://testnet.rougechain.io/api"
Verify It's Working
In a separate PowerShell window:
Invoke-RestMethod http://127.0.0.1:5100/api/health
Expected output:
{
"status": "ok",
"chain_id": "rougechain-devnet-1",
"height": 123
}
Visit http://localhost:5100 in your browser to see the built-in node dashboard.
Run as a Background Service
Option A: Task Scheduler
- Open Task Scheduler → Create Task
- General: Name it "RougeChain Node", check "Run whether user is logged on or not"
- Trigger: At startup
- Action: Start a program
- Program:
C:\path\to\quantum-vault-daemon.exe - Arguments:
--mine --api-port 5100 --peers "https://testnet.rougechain.io/api" - Start in:
C:\path\to\rougechain-node\core
- Program:
- Settings: Check "Restart if task fails", set to every 1 minute
Option B: NSSM (Non-Sucking Service Manager)
# Install NSSM
winget install NSSM.NSSM
# Create the service
nssm install rougechain "C:\path\to\quantum-vault-daemon.exe"
nssm set rougechain AppParameters "--mine --api-port 5100 --peers ""https://testnet.rougechain.io/api"""
nssm set rougechain AppDirectory "C:\path\to\rougechain-node\core"
# Start
nssm start rougechain
Windows Firewall
If you want your node to be publicly accessible:
# Allow inbound connections to port 5100
New-NetFirewallRule -DisplayName "RougeChain Node" -Direction Inbound -Protocol TCP -LocalPort 5100 -Action Allow
Docker Alternative
If you prefer Docker, install Docker Desktop for Windows and run:
docker run -d --name rougechain-node -p 5100:5100 -v qv-data:/data rougechain/node --mine --peers https://testnet.rougechain.io/api
Troubleshooting
"cargo not found"
Close and reopen PowerShell. Rust adds itself to PATH but it takes a new session to pick up.
Build errors about MSVC
Make sure Visual Studio Build Tools is installed with the C++ desktop workload. Restart your terminal after installing.
"Access denied" or antivirus blocking
- Run PowerShell as Administrator
- Add an antivirus exclusion for the
rougechain-nodedirectory - Windows Defender may flag the newly built binary — allow it through
Node doesn't respond
- Check Windows Firewall isn't blocking the port
- Make sure you're using
http://127.0.0.1:5100(notlocalhost, which may resolve to IPv6)
Updating
cd rougechain-node
git pull
cd core
cargo build --release -p quantum-vault-daemon
# Restart the node
Docker
Run a RougeChain node without installing Rust or any build dependencies.
Requirements
Any VPS or machine with Docker installed. Minimum specs:
- 1 vCPU, 1 GB RAM, 5 GB SSD, 10 Mbps
- Recommended: 2 vCPU, 1–2 GB RAM, 20 GB SSD
- A $5/month VPS is enough for testnet
Quick Start
docker run -d \
--name rougechain-node \
-p 5100:5100 \
-v qv-data:/data \
rougechain/node \
--mine --peers https://testnet.rougechain.io/api
Your node will:
- Sync with the testnet
- Produce blocks (
--mine) - Persist chain data to the
qv-dataDocker volume - Serve the REST API on port
5100
Verify:
curl http://127.0.0.1:5100/api/stats | python3 -m json.tool
docker-compose
For a persistent production setup, clone the repo and use docker-compose:
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node
Optionally create a .env file to override defaults:
API_PORT=5100
QV_PEERS=https://testnet.rougechain.io/api
QV_CORS_ORIGINS=https://yourdapp.com,https://rougechain.io
CHAIN_ID=rougechain-devnet-1
Start:
docker compose up -d
View logs:
docker compose logs -f node
Stop:
docker compose down
Building the Image
Build locally instead of pulling from the registry:
docker build -t rougechain/node .
The Dockerfile uses a multi-stage build:
- Builder stage — compiles the Rust daemon in a full Rust image
- Runtime stage — copies only the binary into a minimal Debian image (~50 MB)
Data Persistence
Chain data is stored at /data inside the container. Mount a volume to keep it across restarts:
# Named volume (recommended)
-v qv-data:/data
# Host directory
-v /srv/rougechain-data:/data
Data includes:
- Block database
- Validator state
- Pool/DEX state
- NFT collections
- Node keys (
node-keys.json)
Custom Configuration
Pass CLI flags after the image name:
docker run -d \
-p 5100:5100 \
-v qv-data:/data \
rougechain/node \
--mine \
--peers https://testnet.rougechain.io/api \
--chain-id rougechain-devnet-1 \
--block-time-ms 400
Set CORS origins via environment variable:
docker run -d \
-p 5100:5100 \
-v qv-data:/data \
-e QV_CORS_ORIGINS="https://yourdapp.com" \
rougechain/node \
--mine --peers https://testnet.rougechain.io/api
Becoming a Validator
Once your Docker node is running and synced:
- Open the Validators page in your browser
- Connect your wallet
- Stake XRGE to register as a validator
- Your node will begin participating in block production
Your node earns:
- 20% of priority tips when selected as block proposer
- A share of 70% of priority tips, weighted by your stake
- A minimum tip floor of 0.1 XRGE/block is guaranteed from staking reserves
Health Checks
# Node health
curl http://127.0.0.1:5100/api/health
# Network stats (peers, height, mining status)
curl http://127.0.0.1:5100/api/stats
# Validator list
curl http://127.0.0.1:5100/api/validators
Updating
cd rougechain-node
git pull
docker compose build
docker compose up -d
Or if using docker run:
docker build -t rougechain/node .
docker stop rougechain-node
docker rm rougechain-node
docker run -d --name rougechain-node -p 5100:5100 -v qv-data:/data rougechain/node --mine --peers https://testnet.rougechain.io/api
The qv-data volume persists across container rebuilds, so your chain data is preserved.
Configuration
All node configuration is done via command-line flags or environment variables.
CLI Options
| Flag | Env Variable | Default | Description |
|---|---|---|---|
--host | - | 127.0.0.1 | Bind address for API/gRPC. Use 0.0.0.0 for public nodes |
--port | - | 4101 | gRPC port |
--api-port | - | 5101 | HTTP API port |
--chain-id | - | rougechain-devnet-1 | Chain identifier |
--block-time-ms | - | 400 | Block production interval (ms) |
--mine | - | false | Enable block production |
--node-name | QV_NODE_NAME | - | Human-readable name shown on the network globe |
--data-dir | - | ~/.quantum-vault/core-node | Data storage directory |
--peers | QV_PEERS | - | Comma-separated peer URLs |
--public-url | QV_PUBLIC_URL | - | This node's public URL for peer discovery |
--api-keys | QV_API_KEYS | - | Comma-separated API keys |
--rate-limit-per-minute | - | 0 (unlimited) | Rate limit for API requests |
--dev | - | false | Dev mode: enables legacy v1 unsigned write endpoints and allows any CORS origin |
| — | QV_CORS_ORIGINS | (built-in list) | Comma-separated origins allowed to call the API from a browser |
| — | QV_FAUCET_ENABLED | false | Enable the faucet endpoints (testnet/dev only — never set on mainnet) |
Browser access (CORS)
By default a node only accepts browser requests from a built-in allowlist (localhost:5173, localhost:4173, and the official rougechain.io / rougee.app origins). A dApp served from any other origin will get a CORS error in the browser (curl is unaffected). To allow your own front-end:
# Allowlist specific origins:
export QV_CORS_ORIGINS="https://mydapp.example.com,http://localhost:3000"
# Or, for local development, allow any origin:
./quantum-vault-daemon --dev --api-port 5100
QV_CORS_ORIGINStakes an explicit comma-separated list — setting it to"*"does not produce a wildcard. To serve a trueAccess-Control-Allow-Origin: *publicly (asapi.rougechain.iodoes), terminate at a reverse proxy (nginx) that strips the node's CORS header and emits*itself.
Examples
Local Development Node
./quantum-vault-daemon --mine --api-port 5100
Syncing Node (No Mining)
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
Public Mining Node
./quantum-vault-daemon \
--mine \
--host 0.0.0.0 \
--api-port 5100 \
--node-name "MyNode" \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.example.com"
Once running, visit http://localhost:5100 in your browser to see the built-in node dashboard with live stats, peer list, and block height.
Multiple Peers
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://node1.example.com,https://node2.example.com,https://node3.example.com"
Custom Data Directory
./quantum-vault-daemon \
--mine \
--api-port 5100 \
--data-dir "/var/lib/rougechain"
Environment Variables
You can also use environment variables:
export QV_PEERS="https://testnet.rougechain.io/api"
export QV_PUBLIC_URL="https://mynode.example.com"
export QV_NODE_NAME="MyNode"
export QV_API_KEYS="key1,key2,key3"
./quantum-vault-daemon --mine --api-port 5100
Data Directory Structure
~/.quantum-vault/core-node/
├── chain.jsonl # Block data (append-only)
├── tip.json # Current chain tip
├── validators-db/ # Validator state (RocksDB)
└── messenger-db/ # Messenger data (RocksDB)
Running with Systemd (Linux)
Create /etc/systemd/system/rougechain.service:
[Unit]
Description=RougeChain Node
After=network.target
[Service]
Type=simple
User=rougechain
ExecStart=/opt/rougechain/quantum-vault-daemon --mine --host 0.0.0.0 --api-port 5100 --node-name "MyNode" --public-url "https://mynode.example.com" --peers "https://testnet.rougechain.io/api"
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable rougechain
sudo systemctl start rougechain
sudo journalctl -u rougechain -f # View logs
Mining (Block Production)
RougeChain uses Proof of Stake, so "mining" refers to block production by validators rather than proof-of-work mining.
Enable Mining
Start your node with the --mine flag:
./quantum-vault-daemon --mine --api-port 5100
This tells the node to produce blocks at the configured interval (default: 400ms).
Requirements
| Requirement | Value |
|---|---|
| Staked XRGE | Minimum 10,000 XRGE |
| Node uptime | Must be online to produce blocks |
| Network sync | Node must be synced to chain tip |
How Block Production Works
- Validator selection — Each block slot, the network selects a proposer based on stake weight and quantum entropy sourced from ANU QRNG (with local CSPRNG fallback)
- Block assembly — The selected validator collects pending transactions from the mempool
- Signing — The block is signed with the validator's ML-DSA-65 key
- Voting — Validators automatically submit prevote and precommit attestations for each block
- Propagation — The signed block is broadcast to all peers via
POST /api/blocks/import - Verification — Receiving nodes verify the signature and block validity before accepting
Block Timing
| Parameter | Default | Flag |
|---|---|---|
| Block time | 400ms | --block-time-ms |
# Slower blocks (5 seconds)
./quantum-vault-daemon --mine --block-time-ms 5000
# Faster blocks (500ms, for local dev)
./quantum-vault-daemon --mine --block-time-ms 500
Mining with Peers
To mine as part of the network (not solo):
./quantum-vault-daemon \
--mine \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.example.com"
The --public-url flag is important — it lets other nodes discover and sync from you.
Monitoring
Check your validator status:
curl https://testnet.rougechain.io/api/validators
Check blocks produced:
curl https://testnet.rougechain.io/api/blocks?limit=10
Rewards
Validators earn transaction fees from blocks they produce. Fees are credited immediately when a block is finalized. See Staking Rewards for details.
Troubleshooting
Node not producing blocks
- Ensure
--mineflag is set - Verify you have enough XRGE staked (min 10,000)
- Check that your node is synced:
curl http://127.0.0.1:5100/api/health
Blocks not propagating
- Ensure
--public-urlis set and accessible from the internet - Check firewall rules for your API port
- Verify peer connections:
curl http://127.0.0.1:5100/api/peers
Producing blocks but no rewards
- Confirm your staking transaction was included in a block
- Check validator status via the API
P2P Networking
RougeChain uses a peer-to-peer network for block propagation, transaction broadcasting, and peer discovery.
How It Works
┌─────────────┐ blocks/txs ┌─────────────┐
│ Node A │ ◄─────────────────► │ Node B │
│ (mining) │ │ (syncing) │
└─────────────┘ └─────────────┘
▲ ▲
│ peer discovery │
└───────────────┬───────────────────┘
│
▼
┌─────────────┐
│ Node C │
│ (new peer) │
└─────────────┘
Features
| Feature | Description |
|---|---|
| Block Sync | Nodes download and verify blocks from peers |
| Transaction Broadcast | Submitted txs propagate to all peers |
| Peer Discovery | Nodes share their peer lists with each other |
| Genesis Reset | New nodes automatically adopt the network's chain |
Connecting to the Network
As a Syncing Node
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
As a Mining Node
./quantum-vault-daemon \
--mine \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
Peer Discovery
Nodes automatically discover new peers every 30 seconds by:
- Querying known peers via
GET /api/peers - Adding any new peers to their list
- Optionally registering themselves via
POST /api/peers/register
Enable Self-Registration
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.example.com"
This tells other nodes how to reach you.
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/peers | GET | List known peers |
/api/peers/register | POST | Register as a peer |
/api/blocks/import | POST | Import a block (peer-to-peer) |
/api/tx/broadcast | POST | Receive a broadcasted transaction |
Next Steps
Connecting to Peers
Connect your RougeChain node to the network to sync blocks and broadcast transactions.
Connect to Testnet
The simplest way to join the network:
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
Your node will:
- Download all blocks from the peer
- Verify each block's PQC signatures
- Build the local chain state
- Start receiving new blocks in real-time
⚠️ Important: Your node is syncing but INVISIBLE to the network.
Without
--public-url, your node pulls blocks from peers but never registers itself. Other nodes won't know it exists, and it won't appear on the network globe.To be discoverable, add
--public-urlwith your node's reachable address:./quantum-vault-daemon \ --api-port 5100 \ --node-name "MyNode" \ --public-url "https://your-server.com:5100" \ --peers "https://testnet.rougechain.io/api"If running locally without a public IP, your node works fine for personal use — it just won't be visible to the rest of the network.
Connect to Multiple Peers
For better reliability, connect to multiple peers:
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://api.rougechain.io/api,https://node2.example.com,https://node3.example.com"
Or via environment variable:
export QV_PEERS="https://api.rougechain.io/api,https://node2.example.com"
./quantum-vault-daemon --api-port 5100
Verify Connection
Check that your node has peers:
curl http://127.0.0.1:5100/api/peers
Expected response:
{
"peers": [
"https://testnet.rougechain.io/api"
],
"count": 1
}
Sync Status
Check if your node is synced:
curl http://127.0.0.1:5100/api/health
{
"status": "ok",
"chain_id": "rougechain-devnet-1",
"height": 12345
}
Compare the height with the testnet to confirm you're in sync:
curl https://testnet.rougechain.io/api/health
Connection Flow
Your Node Peer Node
│ │
│── GET /api/health ──────────────►│
│◄─── { height: 12345 } ──────────│
│ │
│── GET /api/blocks?from=0 ───────►│
│◄─── [ block0, block1, ... ] ────│
│ │
│ (verify signatures, apply) │
│ │
│── GET /api/peers ───────────────►│
│◄─── { peers: [...] } ───────────│
│ │
│ (discover new peers) │
Firewall & Exposure
Do not expose the daemon's REST API port (5100) to the public internet — it
serves state-changing endpoints. Bind the daemon to 127.0.0.1 and put a reverse
proxy (nginx + TLS) in front of it; open only the proxy's port 443. See
Public Node Security.
| Port | Bind | Exposure |
|---|---|---|
| 443 (nginx/TLS) | public | ✅ the only port open to the internet |
| 5100 (daemon REST API) | 127.0.0.1 | ❌ never open to the public |
| 4100 (P2P) | as needed | reachable by peers only; restrict where possible |
# Expose ONLY the reverse proxy; keep the daemon API private:
sudo ufw allow 443/tcp
sudo ufw deny 5100/tcp
Troubleshooting
"Connection refused"
- Check that the peer URL is correct and reachable
- Verify the peer node is running
- Check for firewall restrictions
Node stuck syncing
- The initial sync may take time on long chains
- Monitor progress by watching the
heightincrease via/api/health - Try connecting to a different peer
"Chain ID mismatch"
- Ensure your
--chain-idmatches the network you're connecting to - Testnet uses
rougechain-devnet-1
Peer Discovery
RougeChain nodes automatically discover other nodes on the network through a gossip-based peer exchange protocol.
How Discovery Works
1. Node A starts with seed peer(s) via --peers
2. Node A queries GET /api/peers on each known peer
3. Each peer returns its own list of known peers
4. Node A adds any new peers to its local list
5. If --public-url is set, Node A registers itself on peers
6. Repeat every 30 seconds
Over time, all nodes in the network discover each other, forming a mesh.
Discovery Interval
Peer discovery runs automatically every 30 seconds. No configuration is needed.
Seed Peers
The first peer(s) you connect to act as seeds for discovery. From them, your node learns about the rest of the network.
# Single seed
./quantum-vault-daemon --peers "https://testnet.rougechain.io/api"
# Multiple seeds for redundancy
./quantum-vault-daemon --peers "https://api.rougechain.io/api,https://backup.example.com"
Self-Registration
To make your node discoverable by others, set --public-url:
./quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.example.com"
Your node will call POST /api/peers/register on all known peers, announcing its presence. Other nodes will then include your URL in their peer lists.
Discovery API
Get Known Peers
curl http://127.0.0.1:5100/api/peers
{
"peers": [
"https://testnet.rougechain.io/api",
"https://node2.example.com/api",
"https://node3.example.com/api"
],
"count": 3
}
Register as a Peer
curl -X POST https://testnet.rougechain.io/api/peers/register \
-H "Content-Type: application/json" \
-d '{"peerUrl": "https://mynode.example.com"}'
Discovery Diagram
Seed Node
┌─────────┐
│ Node A │ ◄── your --peers target
└────┬────┘
│
GET /api/peers
│
┌────────┼────────┐
▼ ▼ ▼
┌───────┐┌───────┐┌───────┐
│Node B ││Node C ││Node D │ ◄── discovered automatically
└───────┘└───────┘└───────┘
│ │ │
└─────────┼────────┘
│
More peers discovered
from B, C, D...
Privacy Considerations
- Your node's IP/URL is shared with all peers when using
--public-url - Without
--public-url, your node connects outward but is not discoverable by others - Consider using a reverse proxy or domain name instead of exposing raw IP addresses
Running a Public Node
Run a RougeChain node that is publicly accessible and participates fully in the network.
Requirements
| Requirement | Details |
|---|---|
| Server | VPS or dedicated server with a public IP |
| Domain (recommended) | Point a domain to your server |
| SSL certificate | Required for HTTPS (use Let's Encrypt) |
| Open port | API port accessible from the internet |
Setup
1. Build and Install
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node/core
cargo build --release -p quantum-vault-daemon
sudo cp target/release/quantum-vault-daemon /usr/local/bin/
2. Configure Reverse Proxy (nginx)
server {
listen 443 ssl;
server_name mynode.rougechain.example.com;
ssl_certificate /etc/letsencrypt/live/mynode.rougechain.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mynode.rougechain.example.com/privkey.pem;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:5100;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Set client_max_body_size to at least 50M to support messenger media uploads.
3. Start the Node
./quantum-vault-daemon \
--mine \
--host 127.0.0.1 \
--api-port 5100 \
--node-name "MyNode" \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.rougechain.example.com"
Why
--public-urlis required: Without it, your node syncs blocks but never tells other nodes "I exist." It stays invisible — it won't appear on the network globe, other nodes can't sync from it, and it won't receive direct block broadcasts. The--public-urlmust be the URL that other nodes on the internet can reach.
Once running, visit http://127.0.0.1:5100 in your browser to see the built-in node dashboard with live block height, peers, mining status, and fees.
4. Run as a Service
Create /etc/systemd/system/rougechain.service:
[Unit]
Description=RougeChain Node
After=network.target
[Service]
Type=simple
User=rougechain
ExecStart=/usr/local/bin/quantum-vault-daemon --mine --host 127.0.0.1 --api-port 5100 --peers "https://testnet.rougechain.io/api" --public-url "https://mynode.rougechain.example.com"
Restart=always
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
sudo useradd -r -s /bin/false rougechain
sudo systemctl enable rougechain
sudo systemctl start rougechain
Verify Your Node
Health Check
curl https://mynode.rougechain.example.com/api/health
Check Peers Can See You
curl https://testnet.rougechain.io/api/peers
# Your node's URL should appear in the list
Security Hardening
API Keys
Restrict write access with API keys:
./quantum-vault-daemon \
--mine \
--api-port 5100 \
--api-keys "secret-key-1,secret-key-2"
Rate Limiting
The node has built-in rate limiting (disabled by default). Enable it for public-facing nodes:
./quantum-vault-daemon --rate-limit-per-minute 60
Firewall
Only expose the necessary port:
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow 443/tcp
sudo ufw enable
Monitoring
Check Logs
sudo journalctl -u rougechain -f
Health Endpoint
Set up a monitoring tool (e.g., UptimeRobot) to poll:
https://mynode.rougechain.example.com/api/health
Metrics to Watch
| Metric | How to Check |
|---|---|
| Block height | GET /api/health — compare with testnet |
| Peer count | GET /api/peers — should be > 0 |
| Validator status | GET /api/validators — check your stake |
Staking & Validators
RougeChain uses Proof of Stake (PoS) for consensus. Validators stake XRGE tokens to participate in block production and earn rewards.
How It Works
- Stake tokens - Lock XRGE to become a validator
- Propose blocks - Selected validators propose new blocks
- Earn rewards - Collect transaction fees from blocks you produce
- Unstake - Wait for unbonding period to withdraw
Requirements
| Requirement | Value |
|---|---|
| Minimum stake | 10,000 XRGE (enforced on every stake call) |
| Unbonding period | 500 blocks |
| Slashing | 10% per violation; jailed for 20 blocks; auto-slashed after 50 missed blocks |
Become a Validator
Via Web UI
- Go to the Validators page
- Click Stake
- Enter amount (min 10,000 XRGE)
- Confirm transaction
Via SDK (Recommended)
import { RougeChain, Wallet } from '@rougechain/sdk';
const rc = new RougeChain('https://testnet.rougechain.io/api');
const wallet = Wallet.fromKeys(publicKey, privateKey);
await rc.stake(wallet, { amount: 10000 });
Via API (v2 Signed Request)
All write operations require ML-DSA-65 client-side signing. See Staking API for the full request format.
# The payload must be signed client-side with your ML-DSA-65 private key.
# Use the SDK or build the signed request manually:
curl -X POST https://testnet.rougechain.io/api/v2/stake \
-H "Content-Type: application/json" \
-d '{
"payload": {
"amount": 10000,
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "your-public-key-hex"
}'
Check Your Stake
curl "https://testnet.rougechain.io/api/validators?publicKey=your-public-key"
Response:
{
"validators": [
{
"publicKey": "your-public-key",
"stake": 10000.0,
"status": "active",
"blocksProposed": 42
}
]
}
Unstake
Via SDK
await rc.unstake(wallet, { amount: 5000 });
Via API (v2 Signed Request)
curl -X POST https://testnet.rougechain.io/api/v2/unstake \
-H "Content-Type: application/json" \
-d '{
"payload": {
"amount": 5000,
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "your-public-key-hex"
}'
Validator Selection
Block proposers are selected using:
- Stake weight - Higher stake = higher probability
- Quantum entropy - Unpredictable randomness
- Round-robin fallback - Ensures all validators participate
Rewards
Validators earn from an EIP-1559-inspired fee model:
| Component | Distribution |
|---|---|
| Base fee | 50% burned; the remaining 50% flows into the block's tip pool |
| Tip pool | Proposer 20% · validators 70% (stake-weighted) · treasury 10% |
| Minimum tip | 0.1 XRGE per block (subsidized from staking reserves if needed) |
Fees are credited immediately when a block is finalized. See Validator Economics for detailed reward calculations.
PQC Security
All validator operations use ML-DSA-65 signatures:
- Block proposals are signed
- Stake/unstake transactions are signed
- Signatures are verified by all nodes
This ensures quantum-resistant security for the entire consensus process.
Becoming a Validator
Validators propose blocks and earn fees on RougeChain. This guide targets mainnet (rougechain-mainnet-1, real value, no faucet). To practice first, see Testing on testnet at the end.
The one thing you must understand
Your validator identity is a single ML-DSA-65 keypair that does two jobs:
- it holds your stake — staking from a key registers that key as a validator, and
- it signs the blocks your node proposes.
Your node stores this keypair at <data-dir>/node-keys.json. The network rejects any block whose proposer is not a staked validator — so your node's node-keys.json key and your staked key must be the same key. If you stake from one key but run your node with a different (freshly generated) key, your blocks are rejected by every peer and you earn nothing, with no obvious error. This is the most common way to get validator setup wrong.
Security: this key signs blocks on an always-online, internet-facing server. Use a dedicated key that holds only your stake — never your main treasury wallet. If the server is compromised, the blast radius is limited to the staked amount.
Prerequisites
- ≥ 10,000 XRGE (+ ~0.1 XRGE fee) for the standard tier — see tiers.
- A server — see system requirements and installation.
- The daemon (
quantum-vault-daemon) and the CLI (rougechain).
Validator tiers
Your tier is derived automatically from your total stake:
| Tier | Minimum stake | Commission on delegations |
|---|---|---|
| Standard | 10,000 XRGE | 5% |
| Operator | 100,000 XRGE | 10% |
| Genesis | 1,000,000 XRGE | 15% |
Step 1 — Install and generate your node identity
Install the daemon (installation guide), then start it once so it creates its identity keypair and begins syncing:
./quantum-vault-daemon \
--genesis daemon/genesis-mainnet.json \
--chain-id rougechain-mainnet-1 \
--peers https://api.rougechain.io/api \
--data-dir ~/.quantum-vault/mainnet \
--api-port 5100 --port 4100 \
--node-name my-validator
On first boot it logs:
[node] Generated and saved new node keys (pub: <your-node-pubkey>...)
Your identity keypair now lives at ~/.quantum-vault/mainnet/node-keys.json (fields: algorithm, public_key_hex, secret_key_hex). Back this file up, offline. Losing it means losing your validator identity — and the ability to unstake. Let the node sync to the chain tip before continuing.
Step 2 — Point the CLI at your node's key
No key copying needed — the CLI signs directly from your node's identity file with the --node-keys flag, so you act as exactly the key your node signs blocks with:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json whoami
This prints your validator's public key + rouge1… address — confirm it matches the key the daemon logged in Step 1. Every command below uses the same --node-keys flag. (The CLI defaults to the mainnet RPC.)
Step 3 — Fund and stake
Send ≥ 10,000 XRGE (+ ~0.1 XRGE fee) to your validator address (from whoami above) from your main wallet. Then stake it:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json stake 10000
The 10,000 minimum is enforced on every stake call — a smaller top-up is rejected. Each additional stake must itself be ≥ 10,000; totals accumulate.
Verify with the built-in diagnostic — it checks funded / staked / active / producing in one shot:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json validator-status
You want ✓ Staked and ✓ In active set. (It prints the exact fix next to anything that's ✗.)
Step 4 — Start mining
Restart the daemon with --mine (same key, same data-dir), plus a public URL so peers can reach you:
./quantum-vault-daemon \
--mine \
--genesis daemon/genesis-mainnet.json \
--chain-id rougechain-mainnet-1 \
--peers https://api.rougechain.io/api \
--data-dir ~/.quantum-vault/mainnet \
--api-port 5100 --port 4100 \
--node-name my-validator \
--public-url https://my-validator.example.com
Because your node-keys.json key is now a staked validator, peers accept your blocks. Check progress anytime with:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json validator-status
You're fully live once it shows ✓ Producing blocks.
Proposer selection
Selection is stake-weighted, mixed with quantum entropy (ANU QRNG, falling back to a local CSPRNG), plus block context for verifiability. Even a minimum-stake validator proposes blocks — just less often.
Security & slashing — read before you go live
- Dedicated key. Keep only the stake in your validator key; never use your treasury wallet. (Done, if you followed Step 1.)
- One node per key. Never run two nodes with the same key — double-signing is slashable equivocation.
- Back up
node-keys.jsonoffline. It is the only copy of your validator identity. - Don't expose the daemon port. Bind to localhost, front it with nginx + TLS, and firewall the RPC/API port. See public-node security. Do not open port 5100 to the public internet.
- Watch for missed blocks. Missing 50 blocks triggers an auto-slash; each violation costs 10% of stake and jails you for ~20 blocks. Alert on your node being offline or lagging the chain tip (
/api/healthheight vs the network). - Never run
--devon a mainnet node — it enables unsafe key-accepting endpoints. - Avoid unattended auto-restart (e.g. an auto-deploy cron) on a validator: a restart during your proposal slot loses blocks, and auto-pulling unreviewed code is a supply-chain risk. Upgrade deliberately.
Increasing stake / leaving
- Add stake:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json stake 10000again (≥ 10,000 each time). - Leave:
rougechain --node-keys ~/.quantum-vault/mainnet/node-keys.json unstake <amount>enters the ~500-block unbonding queue; dropping below 10,000 removes you from the active set. See Staking.
Testing on testnet first
Practice the whole flow with no real value: swap the mainnet flags for testnet —
--chain-id rougechain-devnet-1, --peers https://testnet.rougechain.io/api, --data-dir ~/.quantum-vault/testnet — point the CLI at it with rougechain --rpc https://testnet.rougechain.io/api …, and use the wallet faucet to get test XRGE. Everything else is identical.
Staking Rewards
Validators earn rewards for producing blocks on RougeChain.
Reward Sources
| Source | Description |
|---|---|
| Transaction fees | Your share of the tip pool from transactions in each block (proposer + stake-weighted validator share) |
| Base block reward | Fixed reward per block (if configured) |
How Rewards Work
- A validator is selected to propose a block
- The validator assembles pending transactions
- Fees are split, not paid entirely to the proposer: 50% of the base fee is burned, and the remaining tip pool is distributed 20% to the proposer, 70% among validators (stake-weighted), and 10% to the treasury
- Rewards are credited immediately upon block finalization
Fee Structure
| Transaction Type | Fee |
|---|---|
| Transfer | 0.1 XRGE |
| Token creation | 100 XRGE |
| Pool creation | 10 XRGE |
| Swap | 0.3% (to LPs, not validators) |
| Stake/Unstake | 0.1 XRGE |
The tip portion of these fees (after the base-fee burn) is split across the proposer, all validators (stake-weighted), and the treasury — see How Rewards Work. Swap fees go to liquidity providers, not validators.
Estimated Returns
Rewards depend on:
- Your stake relative to total staked — determines how often you're selected
- Network activity — more transactions = more fees per block
- Number of validators — fewer validators means more blocks per validator
Example
| Scenario | Value |
|---|---|
| Your stake | 10,000 XRGE |
| Total staked | 100,000 XRGE |
| Your share | 10% |
| Target block time | 400 ms |
| Your share of proposed blocks | ~10% (stake-weighted) |
| Avg fee per block | varies with network activity |
Your earnings are your ~10% share of the fees in the blocks you propose. Blocks are produced as transactions arrive, so daily volume tracks real network activity — on a quiet chain that is low, and there is no fixed "blocks per day." These figures are illustrative, not a yield promise; actual returns vary with usage.
Compounding
Rewards are added to your balance, not your stake. To compound:
- Periodically stake your accumulated rewards
- This increases your proposer probability
- Leading to more blocks and more rewards
Checking Rewards
Via Web UI
Go to the Validators page to see your validator stats including blocks proposed.
Via API
# Check your balance (includes accumulated rewards)
curl "https://testnet.rougechain.io/api/balance/your-public-key"
# Check blocks proposed
curl "https://testnet.rougechain.io/api/validators"
Tax Considerations
Staking rewards may be taxable income in your jurisdiction. Keep records of:
- Amount staked
- Rewards received (block by block)
- Token price at time of receipt
- Unstaking transactions
RougeChain does not provide tax advice. Consult a tax professional.
Bridge
RougeChain supports bridging assets between Base mainnet (EVM, chain id 8453) and the RougeChain L1 network. The bridge uses a lock-and-mint / burn-and-release model with a dedicated smart contract. (Base Sepolia, chain id 84532, is supported for testing via BASE_CHAIN=sepolia.)
Supported Assets
| EVM Asset | RougeChain Asset | Decimals | Direction |
|---|---|---|---|
| ETH | qETH | 6 (L1 units) | Both ways |
| XRGE | XRGE | 18 (EVM) / whole units (L1) | Both ways |
| USDC | qUSDC | 6 | Planned — not yet enabled |
qETH and XRGE are the supported bridge assets today. qUSDC is planned, not yet enabled — USDC deposits are not claimable and USDC withdrawals are not served.
How It Works
Deposit (EVM → RougeChain)
- User deposits ETH or XRGE into the RougeBridge / BridgeVault contract on Base
- The deposit watcher in the relayer detects the deposit event and auto-claims it on L1 — the node verifies the deposit on-chain and mints the wrapped token (qETH or XRGE) to the recipient encoded in the deposit. No manual step is required.
- As a fallback, the user can still call the claim endpoint with the EVM tx hash (e.g. if the watcher is disabled). Claims are deduped, so auto- and manual claims of the same deposit can't double-mint.
Every claim is verified against the actual on-chain Base deposit (the
Transferto the custody contract / vault) — never a caller-supplied amount — and requires a valid EVM signature plus a confirmation depth (QV_BRIDGE_MIN_CONFIRMATIONS, default 6) before minting.
Withdrawal (RougeChain → EVM)
- User submits a signed bridge_withdraw transaction on RougeChain, burning the wrapped token
- The withdrawal is recorded in the pending withdrawals store with its owner key, token,
and
status - The bridge relayer polls for pending withdrawals and releases the corresponding asset on Base
- If a release keeps failing, the relayer reports it; after a threshold the withdrawal is
auto-refunded — the burned tokens are re-minted to the owner on L1. Status
(
pending/failed/refunded) is exposed via the API and the Bridge page.
Security
- Client-side signing — Private keys never leave the browser. Withdraw transactions are signed locally using ML-DSA-65
- RougeBridge contract — Pausable, with guardian role for emergencies, timelock on large withdrawals
- Relayer authentication — The relayer uses a
BRIDGE_RELAYER_SECRETfor API authentication - Replay protection — Claimed transaction hashes are persisted to prevent double-claims; refunds use a
refund:<txId>key so they can't be issued twice - EVM signature verification — manual ETH claims require an EVM
personal_sign; auto-claims re-verify the deposit tx on-chain (sender, amount, confirmations)
Architecture
Base mainnet (EVM) RougeChain L1
┌──────────────────┐ ┌──────────────────┐
│ RougeBridge.sol │ deposit │ Node Daemon │
│ - depositETH() │──watcher─▶│ - /deposit/ │
│ - depositERC20()│ │ auto-claim │
│ - releaseETH() │◀─relayer──│ - /bridge/withdraw│
│ - releaseERC20()│ release │ - withdraw store │
│ BridgeVault.sol │ │ - /…/refund │
│ - deposit() │──watcher─▶│ - /bridge/xrge/* │
│ - release() │◀─relayer──│ │
└──────────────────┘ └──────────────────┘
deposit watcher: Base → L1 auto-claim
relayer release + refund: L1 → Base
ETH Bridge (qETH)
Bridge ETH from Base mainnet to RougeChain as qETH, and back.
Deposit (ETH → qETH)
Step 1: Send ETH to the Bridge
Connect your MetaMask (or other EVM wallet) to Base and call depositETH(rougechainPubkey)
on the RougeBridge contract (the Bridge page does this for you), passing your RougeChain
public key. This emits a BridgeDepositETH event carrying your recipient key.
Step 2: Automatic claim
The relayer's deposit watcher sees the event and claims it for you — qETH is minted
to the RougeChain key you encoded in the deposit, once the deposit reaches the required
confirmation depth (QV_BRIDGE_MIN_CONFIRMATIONS, default 6 Base confirmations).
No manual claim step is needed.
The node verifies before minting:
- The transaction exists on Base and was sent to the correct contract
- The amount and sender match the deposit
- The transaction has at least the required confirmation depth (default 6)
- It hasn't been claimed before (auto-claim and manual claim share one dedup store)
Manual fallback: if the watcher is disabled, use the Bridge In tab — paste the EVM tx hash, sign the claim message with your EVM wallet, and click Claim.
Conversion Rate
1 ETH = 1,000,000 qETH units (6 decimal precision)
For example, depositing 0.01 ETH gives you 10,000 qETH units.
Withdraw (qETH → ETH)
- Go to the Bridge page and select the Bridge Out tab
- Select ETH as the token
- Enter the amount of qETH to bridge out
- Enter the Base address to receive ETH
- Click Bridge Out
The transaction is signed client-side (your private key never leaves the browser), then:
- qETH is burned on RougeChain
- A pending withdrawal is created
- The bridge relayer picks it up and sends ETH to your EVM address
- Release status (
pending/failed/refunded) shows on the Bridge page. If the relayer can't complete the release after repeated attempts, your qETH is automatically refunded back to your RougeChain wallet.
A 0.1 XRGE fee is charged for the withdrawal transaction.
Fees
| Operation | Fee |
|---|---|
| Deposit (ETH → qETH) | Gas on Base only |
| Withdraw (qETH → ETH) | 0.1 XRGE |
USDC Bridge (qUSDC)
Status: planned, not yet enabled.
qUSDC — a wrapped, 1:1 representation of USDC on RougeChain — is planned but not yet enabled on RougeChain. USDC deposits are not claimable and USDC withdrawals are not served by the relayer.
The bridge assets available today are qETH (see ETH Bridge) and XRGE (see XRGE Bridge). This page will be filled in with deposit and withdrawal steps once qUSDC bridging is turned on.
XRGE Bridge
Bridge XRGE tokens between Base (EVM) and RougeChain L1 using the BridgeVault contract.
Overview
Unlike qETH/qUSDC (which are wrapped assets), XRGE is the native token of RougeChain. The XRGE bridge allows moving XRGE between its ERC-20 representation on Base and the L1 network.
XRGE Token on Base: 0x147120faEC9277ec02d957584CFCD92B56A24317
Deposit (Base XRGE → L1 XRGE)
- Approve the BridgeVault contract to spend your XRGE
- Call
deposit(amount, rougechainPubkey)on the BridgeVault - The vault locks your XRGE and emits a
BridgeDepositevent carrying your L1 key - The relayer's deposit watcher detects the event and auto-claims it — XRGE is credited to your L1 wallet after the node verifies the on-chain transfer. No manual claim is needed.
The manual fallback endpoint /api/bridge/xrge/claim requires an evmSignature (sign
the claim message with the wallet that sent the XRGE) and ignores any caller-supplied
amount — the amount and depositor are derived from the actual on-chain
Transfer(from → vault) log emitted by the XRGE token, and the deposit must reach the
required confirmation depth (QV_BRIDGE_MIN_CONFIRMATIONS, default 6) before it mints.
Withdraw (L1 XRGE → Base XRGE)
- Go to the Bridge page and use the XRGE Bridge Out tab
- Enter the amount and your Base EVM address
- Submit the signed withdrawal
- The relayer calls
release()on the BridgeVault to unlock your XRGE on Base - If the release can't be completed after repeated attempts, the withdrawal is auto-refunded — your XRGE is re-minted on L1. Status is shown on the Bridge page.
BridgeVault Contract
The BridgeVault is a lock-and-release contract:
deposit(amount, rougechainPubkey)— Lock XRGE, emit event for relayerrelease(to, amount, l1TxId)— Owner (relayer) releases XRGE back to uservaultBalance()— View how much XRGE the vault holdsemergencyWithdraw(token)— Admin-only emergency recovery
Liquidity in the vault = total XRGE locked by depositors minus released amounts.
RougeBridge Contract
The RougeBridge.sol contract is a multi-asset bridge contract deployed on Base mainnet (0x0c09C764AdC024497729cd452ECfeE8869d35d83) that handles ETH and ERC-20 deposits/releases with enhanced security features.
Features
- Multi-token support — ETH and any ERC-20 token (USDC, etc.)
- Pausable — Guardian can pause all operations in emergencies
- Timelock — Large withdrawals require a delay period before execution
- Guardian role — Separate from owner; can pause but cannot withdraw
- Replay protection — Processed L1 transaction IDs are tracked to prevent double-releases
- Owner = multisig — Deploy with a Gnosis Safe as owner for production
Key Functions
Deposits
function depositETH(string calldata rougechainPubkey) external payable;
function depositERC20(address token, uint256 amount, string calldata rougechainPubkey) external;
Users call these to lock assets in the contract. Events are emitted for the relayer to pick up.
Releases (Owner Only)
function releaseETH(address to, uint256 amount, bytes32 l1TxId) external;
function releaseERC20(address token, address to, uint256 amount, bytes32 l1TxId) external;
The relayer (owner) calls these to release assets when a user burns their wrapped tokens on L1. If the amount exceeds largeWithdrawalThreshold, a timelock is queued instead.
Timelock
function executeTimelock(uint256 requestId) external; // Owner, after delay
function cancelTimelock(uint256 requestId) external; // Guardian
Large releases are queued with a configurable delay (default 24 hours). The guardian can cancel suspicious requests during the delay window.
Admin
function pause() external; // Guardian or Owner
function unpause() external; // Owner only
function setGuardian(address newGuardian) external; // Owner
function setSupportedToken(address token, bool) external;// Owner
function setLargeWithdrawalThreshold(uint256) external; // Owner
Events
| Event | Description |
|---|---|
BridgeDepositETH | ETH deposited for bridging |
BridgeDepositERC20 | ERC-20 deposited for bridging |
BridgeReleaseETH | ETH released to user |
BridgeReleaseERC20 | ERC-20 released to user |
TimelockQueued | Large release queued with delay |
TimelockExecuted | Queued release executed |
TimelockCancelled | Queued release cancelled by guardian |
Deployment
The XRGE bridge is live on Base mainnet (chain ID 8453) as BridgeVaultV2 at 0x7BB10752E99e8872d7D2DE5D92bfd43cd935Cd2D. Its owner is a Gnosis Safe multisig (2-of-3) — no single key can move or drain the vault. Routine releases are signed by a separate hot relayer key that may only call release() within on-chain per-transaction and rolling daily caps and only while unpaused; the multisig alone can rotate that key, change the caps, pause, or (behind a 48-hour timelock) emergency-withdraw. The ETH/USDC custody contract is at 0x0c09C764AdC024497729cd452ECfeE8869d35d83. Base Sepolia (chain ID 84532) is used for testing.
Migration note: the previous single-owner XRGE vault (
0xb3f5…bFAb5) is being retired in favor ofBridgeVaultV2; do not deposit to the old address.
Daemon Withdraw Guardrails
Independent of the on-chain contract, the RougeChain daemon enforces its own withdraw guardrails via environment variables:
QV_BRIDGE_WITHDRAW_PAUSED— emergency kill-switch; blocks all withdrawals whentrueQV_BRIDGE_MAX_WITHDRAW_UNITS— per-transaction withdrawal cap (0/unset = no cap)QV_BRIDGE_MIN_CONFIRMATIONS— required Base confirmation depth for deposit claims (default 6)
Bridge Relayer
The bridge relayer is an off-chain process that connects RougeChain L1 with Base. It fulfills withdrawals (L1 → Base), watches for deposits to auto-claim (Base → L1), and refunds withdrawals that cannot be released.
For the full configuration table and operational notes, see
scripts/README.md.
How It Works
- Polls the node for pending ETH and XRGE withdrawals and releases the corresponding asset on Base
- Marks each release fulfilled; on repeated failure, reports it and auto-refunds the owner on L1
- Deposit watcher: scans the bridge contracts for deposit events and auto-claims them on L1
- Alerts (console + optional webhook) on repeated failures
Running the Relayer
The relayer and the daemon share bridge-relayer.env (copy from bridge-relayer.env.example):
# Key variables (see bridge-relayer.env.example for the full list)
CORE_API_URL="http://localhost:5100"
BRIDGE_CUSTODY_PRIVATE_KEY="0x..." # EVM private key for the bridge wallet
BRIDGE_RELAYER_SECRET="your-secret" # Shared secret for API authentication
BASE_CHAIN="mainnet"
BASE_RPC_URL="https://mainnet.base.org" # NOTE: var name is BASE_RPC_URL
ROUGE_BRIDGE_ADDRESS="0x..." # RougeBridge contract address
XRGE_BRIDGE_VAULT="0x..." # BridgeVault contract address
AUTO_REFUND="true" # Auto-refund failed withdrawals
DEPOSIT_WATCHER="true" # Auto-claim deposits
# ALERT_WEBHOOK_URL= # Optional Slack/Discord webhook
# Production: systemd (replaces the old pm2 process — never run both)
sudo systemctl restart bridge-relayer.service
# Local
npx tsx scripts/bridge-relayer.ts
Authentication
The relayer authenticates with the node using the BRIDGE_RELAYER_SECRET environment variable. This is sent as the x-bridge-relayer-secret HTTP header when marking withdrawals as fulfilled.
Set the same secret on both the relayer and the node:
# On the node
export BRIDGE_RELAYER_SECRET="your-secret"
# On the relayer
export BRIDGE_RELAYER_SECRET="your-secret"
Contract Mode vs Legacy Mode
- With
ROUGE_BRIDGE_ADDRESS— The relayer callsreleaseETH()/releaseERC20()on the RougeBridge contract - Without it — Falls back to raw ETH transfers from the custody wallet (legacy mode)
- With
XRGE_BRIDGE_VAULT— Enables XRGE bridge support via the BridgeVault contract
Daemon Withdraw Guardrails
Beyond the relayer config, the RougeChain daemon enforces withdraw guardrails via environment variables:
QV_BRIDGE_WITHDRAW_PAUSED— emergency kill-switch; blocks all withdrawals whentrueQV_BRIDGE_MAX_WITHDRAW_UNITS— per-transaction withdrawal cap (0/unset = no cap)QV_BRIDGE_MIN_CONFIRMATIONS— required Base confirmation depth for deposit claims (default 6)
Security Considerations
- The relayer's EVM private key should be stored securely (not in code)
- Use a dedicated wallet with limited funds for the relayer
- For production, the RougeBridge contract owner should be a multisig
- The
BRIDGE_RELAYER_SECRETshould be a strong random string
DEX & AMM
RougeChain includes a built-in decentralized exchange (DEX) powered by an Automated Market Maker (AMM) using the constant product formula (x * y = k).
Overview
- Create pools for any token pair
- Swap between tokens with slippage protection
- Provide liquidity and earn fees from trades
- Multi-hop routing for tokens without direct pools
All operations are signed client-side with ML-DSA-65 — your private key never leaves the browser.
Fee Structure
| Operation | Fee |
|---|---|
| Create Pool | 10 XRGE |
| Swap | 0.3% of input + 1 XRGE tx fee |
| Add Liquidity | 1 XRGE |
| Remove Liquidity | 1 XRGE |
The 0.3% swap fee is distributed to liquidity providers proportional to their share of the pool.
Pool Mechanics
Pools use the Constant Product Market Maker model:
reserve_a × reserve_b = k (constant)
When a user swaps token A for token B:
- Token A is added to the pool
- Token B is removed, maintaining the constant product
- 0.3% fee is taken from the input amount
- Price impact increases with larger trades relative to pool size
LP Tokens
When you provide liquidity, you receive LP tokens representing your share of the pool. When you remove liquidity, you burn LP tokens and receive your proportional share of both tokens in the pool.
Liquidity Pools
Creating a Pool
To create a new trading pool:
- Go to the Swap page
- Click Create Pool
- Select two tokens (e.g., XRGE / qETH)
- Enter the initial amounts for each token
- Click Create Pool
The initial token ratio sets the starting price. A 10 XRGE fee is charged for pool creation.
Adding Liquidity
- Go to the pool page
- Click Add Liquidity
- Enter the amount of one token — the other amount auto-calculates based on the current ratio
- Confirm the transaction
You receive LP tokens proportional to your contribution.
Removing Liquidity
- Go to the pool page
- Click Remove Liquidity
- Enter the LP token amount to withdraw
- You receive your proportional share of both tokens
Impermanent Loss
If the price ratio changes significantly from when you deposited, you may experience impermanent loss compared to simply holding the tokens. This is standard AMM behavior.
Pool Stats
Each pool shows:
- Current reserves for both tokens
- Total LP tokens issued
- Your LP share percentage
- 24h volume and fees earned
- Price history chart
Swaps
Making a Swap
- Go to the Swap page
- Select the input token and output token
- Enter the amount to swap
- Review the quote (output amount, price impact, minimum received)
- Click Swap
Slippage Protection
Set your maximum slippage tolerance to protect against price movements during your transaction. If the actual output would be less than your minimum, the transaction is rejected.
Default slippage: 0.5%
Price Impact
Price impact shows how much your trade will move the pool price. Large trades relative to pool size have higher price impact.
| Price Impact | Severity |
|---|---|
| < 1% | Low |
| 1-5% | Medium |
| > 5% | High (warning shown) |
Multi-Hop Routing
If no direct pool exists between your tokens, the router automatically finds a path through XRGE:
TOKEN_A → XRGE → TOKEN_B
This uses two swaps internally but is handled in a single transaction.
Fees
- 0.3% of the input amount goes to liquidity providers
- 1 XRGE base transaction fee
Token Creation
Create custom tokens on RougeChain. Tokens can be traded on the built-in AMM/DEX.
Overview
| Property | Value |
|---|---|
| Creation fee | 100 XRGE |
| Max supply | Set at creation (immutable) |
| Decimals | Configurable |
| Trading | Via AMM liquidity pools |
Create a Token
Via Web UI
- Navigate to the Token Explorer page
- Click Create Token
- Fill in token details:
- Name — Full name (e.g., "My Token")
- Symbol — Ticker symbol (e.g., "MTK")
- Total Supply — Maximum supply
- Logo — Upload an image or paste a URL (optional)
- Confirm and sign the transaction
Uploaded logos are compressed to WebP (max 256×256, ≤100 KB) and stored on-chain as base64 data URIs. They display across the wallet, swap, pools, and explorer.
Via SDK
import { RougeChain, Wallet } from "@rougechain/sdk";
const rc = new RougeChain("https://testnet.rougechain.io/api");
const wallet = Wallet.generate();
await rc.createToken(wallet, {
name: "My Token",
symbol: "MTK",
totalSupply: 1_000_000,
image: "https://example.com/logo.png", // or a data:image/webp;base64,... URI
});
Via v2 API
The request body is the signed envelope { payload, signature, public_key }. The from, timestamp, and nonce fields go inside payload, and from must equal the signing public key.
curl -X POST https://testnet.rougechain.io/api/v2/token/create \
-H "Content-Type: application/json" \
-d '{
"payload": {
"name": "My Token",
"symbol": "MTK",
"totalSupply": 1000000,
"decimals": 8,
"image": "https://example.com/logo.png",
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex"
},
"signature": "your-ml-dsa65-signature-hex",
"public_key": "your-public-key-hex"
}'
Response
{
"success": true,
"txId": "abc123...",
"token": {
"symbol": "MTK",
"name": "My Token",
"totalSupply": 1000000,
"decimals": 8,
"creator": "your-public-key"
}
}
After Creation
Once created, the entire supply is credited to the creator's wallet. You can then:
- Transfer tokens to other wallets
- Create a liquidity pool to enable trading
- Burn tokens by sending to the burn address
Creating a Liquidity Pool
To make your token tradeable on the DEX:
curl -X POST https://testnet.rougechain.io/api/v2/pool/create \
-H "Content-Type: application/json" \
-d '{
"payload": {
"tokenA": "XRGE",
"tokenB": "MTK",
"amountA": 1000,
"amountB": 10000,
"from": "your-public-key-hex",
"timestamp": 1706745600001,
"nonce": "random-hex"
},
"signature": "your-signature-hex",
"public_key": "your-public-key-hex"
}'
This creates an XRGE/MTK pool with an initial price of 0.1 XRGE per MTK.
Pool creation costs 10 XRGE.
Token Burning
Send tokens to the burn address to permanently remove them from circulation:
XRGE_BURN_0x000000000000000000000000000000000000000000000000000000000000DEAD
Burned amounts are tracked on-chain and queryable via GET /api/burned.
Listing on the DEX
Tokens are automatically listed on the DEX once a liquidity pool is created. Users can then:
- Swap between your token and XRGE
- Add/remove liquidity
- View price charts and pool stats
Token Metadata
Every token has on-chain metadata that the creator can manage:
| Field | Description |
|---|---|
image | Logo URL or base64 data URI |
description | Token description |
website | Project website |
twitter | X/Twitter handle |
discord | Discord invite link |
Updating Metadata
Only the original creator can update metadata:
await rc.updateTokenMetadata(wallet, {
symbol: "MTK",
image: "data:image/webp;base64,UklGR...",
description: "A community token for...",
website: "https://mytoken.io",
twitter: "@mytoken",
discord: "discord.gg/mytoken",
});
Logo images can be:
- URLs —
https://...,ipfs://... - Data URIs —
data:image/webp;base64,...(stored directly on-chain, persists forever)
The web UI provides an Upload button that compresses images to WebP and stores them as base64 on-chain.
Token Standards
RougeChain tokens are native protocol-level assets (not smart contract tokens). This means:
- No ERC-20 compatibility (different chain architecture)
- Transfers are first-class transactions
- All token operations are signed with ML-DSA-65
- Quantum-resistant by default
NFTs
RougeChain supports on-chain NFTs with collections, minting, transfers, and marketplace features — all secured by post-quantum cryptography.
Features
- Collections — Create NFT collections with configurable max supply, royalties, and metadata
- Minting — Mint single or batch NFTs with metadata URIs and custom attributes
- Transfers — Send NFTs to any RougeChain address with optional sale price tracking
- Burning — Permanently destroy NFTs
- Locking — Lock individual NFTs to prevent transfers
- Freezing — Freeze entire collections to prevent further minting
Fee Structure
| Operation | Fee (XRGE) |
|---|---|
| Create Collection | 50 |
| Mint NFT | 5 |
| Batch Mint | 5 per NFT |
| Transfer | 1 |
| Burn | 0.1 |
| Lock/Unlock | 0.1 |
| Freeze Collection | 0.1 |
Security
All NFT operations use the v2 signed transaction API — transactions are signed client-side with ML-DSA-65 and verified on-chain. Only collection creators can mint, and only token owners can transfer or burn.
NFT Collections
Creating a Collection
- Go to the NFT Explorer page
- Click Create Collection
- Fill in:
- Symbol — Short identifier (e.g., "ROGUE")
- Name — Full collection name
- Max Supply — Maximum number of NFTs (optional, 0 = unlimited)
- Royalty — Royalty percentage in basis points (e.g., 500 = 5%)
- Royalty recipient — (optional) wallet that receives royalties. Defaults to the creator when omitted.
- Image — Collection cover image URL
- Description — Collection description
- Click Create
The collection ID is generated from creator_pubkey:SYMBOL.
Royalties
Set a royaltyBps (basis points — 500 = 5%, 1000 = 10%) when creating a collection to earn on secondary sales.
- When it's paid: on an
nft_transferthat declares asalePrice > 0. The royalty issalePrice × royaltyBps / 10000, in XRGE. - Who pays: the sender (the current owner initiating the transfer). It is deducted from their XRGE balance on top of the 1 XRGE transfer fee.
- Who receives: the collection's
royaltyRecipient. If you didn't set one, this is the creator. If you set one at creation, it is that wallet. The recipient is fixed at creation and cannot be changed afterward.
Routing royalties to another wallet
Pass royaltyRecipient at creation to send royalties somewhere other than the creator — for example a shared payout wallet or a treasury. Splitting between multiple collaborators is done off-chain by the receiving app after the royalty lands in that wallet.
⚠️ Never set
royaltyRecipientto a smart-contract address. Contracts cannot spend XRGE credited to them (contract balances are not persisted back to the ledger, and a contract address is a hash with no private key). Any royalty sent to a contract address is permanently lost. Use a normal wallet address only.
Note on enforcement:
salePriceis self-declared in the transfer payload — there is no on-chain marketplace escrow. A transfer sent with nosalePrice(or0) pays no royalty. Royalties are honored by marketplaces/apps that populatesalePrice, not enforced against every possible transfer.
Collection Properties
| Property | Description |
|---|---|
id | Unique identifier (creator:symbol) |
symbol | Short token symbol |
name | Full collection name |
creator | Creator's public key |
max_supply | Max tokens (0 = unlimited) |
royalty_bps | Royalty in basis points |
royalty_recipient | Wallet that receives royalties (defaults to creator) |
frozen | Whether minting is frozen |
total_minted | Number of tokens minted |
Freezing a Collection
Collection creators can freeze their collections to permanently prevent further minting. This is irreversible and signals to holders that the supply is final.
Browsing Collections
The NFT Explorer page shows all collections with their stats, floor price, and total items. Click any collection to view its individual tokens.
Minting & Trading NFTs
Minting
Single Mint
- Open a collection page
- Click Mint NFT
- Enter the token name and optional metadata URI / attributes
- Click Mint
Only the collection creator can mint new tokens.
Batch Mint
Mint multiple NFTs at once:
- Open a collection page
- Click Batch Mint
- Provide a list of names (and optional URIs/attributes for each)
- Click Mint All
Fee is 5 XRGE per NFT in the batch.
Token Properties
Each NFT has:
| Property | Description |
|---|---|
token_id | Sequential ID within the collection |
name | Token name |
owner | Current owner's address (rouge1...) |
creator | Original minter |
metadata_uri | Link to off-chain metadata (IPFS, HTTP) |
attributes | On-chain key-value attributes |
locked | Whether the token is locked (non-transferable) |
created_at | Timestamp of minting |
Transferring
- Open the NFT detail page
- Click Transfer
- Enter the recipient's RougeChain address (
rouge1...) - Optionally set a sale price (for marketplace tracking)
- Confirm
Locked NFTs cannot be transferred until unlocked by the owner.
Burning
Permanently destroy an NFT:
- Open the NFT detail page
- Click Burn
- Confirm
Only the current owner can burn. This action is irreversible.
Locking
Lock an NFT to prevent it from being transferred:
- Open the NFT detail page
- Toggle Lock
Only the owner can lock/unlock their NFTs.
Block Explorer
RougeChain includes a built-in block explorer at rougechain.io for browsing blocks, transactions, addresses, tokens, and NFTs.
Features
- Block details — View block height, hash, timestamp, proposer, and all transactions
- Transaction details — View transaction type, sender, recipient, amount, fee, and status
- Address pages — View any address's balances, token holdings, and transaction history
- Token list — Browse all tokens created on RougeChain
- NFT Explorer — Browse NFT collections and individual tokens
- Global search — Search by address, transaction hash, or block height
Navigation
The explorer is integrated into the main RougeChain web app:
| Page | URL | Description |
|---|---|---|
| Blockchain | /blockchain | Block list with live updates |
| Block Detail | /block/:height | Individual block |
| Transaction | /tx/:hash | Individual transaction |
| Address | /address/:pubkey | Address details |
| Transactions | /transactions | All transactions list |
| NFT Explorer | /nft-explorer | NFT collections browser |
| Tokens | /tokens | All tokens list |
Block Explorer — Blocks
Block List
The Blockchain page shows all blocks in reverse chronological order with:
- Block height (index)
- Block hash (truncated)
- Timestamp
- Number of transactions
- Proposer address
Click any block to view its full details.
Block Detail Page
URL: /block/:height
Shows:
| Field | Description |
|---|---|
| Height | Block number in the chain |
| Hash | SHA-256 hash of the block |
| Previous Hash | Hash of the parent block |
| Timestamp | When the block was created |
| Proposer | Public key of the block proposer |
| Transaction Count | Number of transactions in the block |
Below the header, all transactions in the block are listed with their type, sender, recipient, amount, and fee.
API
GET /api/blocks — List all blocks (paginated)
GET /api/block/:height — Get a specific block by height
GET /api/blocks/summary — Block summary for charts
Block Explorer — Transactions
Transaction List
The Transactions page shows all transactions across the network with:
- Transaction hash
- Type (transfer, swap, stake, bridge, NFT, etc.)
- Sender and recipient
- Amount
- Block number
- Timestamp
Transaction Detail Page
URL: /tx/:hash
Shows the full transaction details:
| Field | Description |
|---|---|
| Hash | Transaction hash (SHA-256) |
| Type | Transaction type |
| From | Sender public key |
| To | Recipient public key |
| Amount | Transaction amount |
| Fee | Transaction fee (XRGE) |
| Token | Token symbol (if applicable) |
| Block | Block height containing this tx |
| Timestamp | When the transaction was processed |
| Signature | PQC signature (ML-DSA-65) |
Transaction Types
| Type | Description |
|---|---|
transfer | Token transfer between addresses |
create_token | New token creation |
stake | Staking tokens |
unstake | Unstaking tokens |
create_pool | Creating a liquidity pool |
add_liquidity | Adding liquidity to a pool |
remove_liquidity | Removing liquidity from a pool |
swap | Token swap via AMM |
bridge_mint | Minting bridged tokens (qETH, qUSDC) |
bridge_withdraw | Burning bridged tokens for withdrawal |
nft_create_collection | Creating an NFT collection |
nft_mint | Minting an NFT |
nft_transfer | Transferring an NFT |
nft_burn | Burning an NFT |
API
GET /api/txs — List recent transactions
GET /api/tx/:hash — Get transaction by hash
GET /api/address/:public_key/transactions — Get transactions for an address
Block Explorer — Addresses
Address Detail Page
URL: /address/:pubkey
Shows comprehensive information for any RougeChain address. Addresses are displayed in the compact rouge1... Bech32m format throughout the UI, derived from the SHA-256 hash of the raw ML-DSA-65 public key.
Balances
- XRGE balance
- All token balances (qETH, qUSDC, custom tokens)
- Staked amount (if any)
Transaction History
Paginated list of all transactions where this address is the sender or recipient, including:
- Transfers
- Swaps
- Staking operations
- Bridge operations
- NFT operations
NFTs Owned
List of all NFTs currently owned by this address, grouped by collection.
API
GET /api/balance/:public_key — XRGE balance
GET /api/balance/:public_key/:token_symbol — Token balance
GET /api/address/:public_key/transactions — Transaction history (paginated)
GET /api/nft/owner/:pubkey — NFTs owned
Contracts
The Contracts Explorer lets you browse all WASM smart contracts deployed on RougeChain.
Contracts List (/contracts)
The contracts page displays:
- Total Contracts — Number of deployed contracts
- Total WASM Size — Combined bytecode size
- Search — Filter by contract address or deployer
- Sort — By newest, oldest, or size
Each contract card shows its address, deployer, WASM size, and deploy block. Click to view details.
Contract Detail (/contract/:addr)
The contract detail page has three sections:
Contract Information
- Contract address (hex)
- Deployer identity
- Deploy block and transaction
- WASM bytecode size
State Viewer
Live key-value table showing all data in the contract's persistent storage. Updates after each contract call.
Execute Contract
Interactive form to call contract methods directly from the browser:
| Field | Description |
|---|---|
| Method | Function name (e.g. get_count, transfer) |
| Arguments | JSON object with method parameters |
| Caller | Caller identity (optional) |
| Gas Limit | Max fuel units (default: 100,000) |
Results display success/failure status, gas used, and return data.
Transaction Labels
Contract transactions appear throughout the explorer with distinct labels:
| Label | Description |
|---|---|
| Contract Deploy | WASM bytecode deployment |
| Contract Call | Method execution |
The transaction detail page shows a Contract Details card for these transactions with the contract address, method name, gas used, and WASM size.
API Reference
The RougeChain node exposes a REST API on the configured --api-port (default: 5101).
Important: All write operations (POST/PUT/DELETE) require v2 signed requests using ML-DSA-65 client-side signing. Legacy unsigned v1 write endpoints return
410 GONEin production. Private keys never leave your application.
Base URL
Local node:
http://127.0.0.1:5100/api
Public testnet:
https://testnet.rougechain.io/api
Public mainnet:
https://api.rougechain.io/api
Both public endpoints send Access-Control-Allow-Origin: *, so browser dApps and localhost dev servers can call them directly with no per-origin allowlisting. (The API has no cookie/session auth — every write is ML-DSA-65 signed — so a wildcard is safe, the same posture as public RPC providers.)
Address Format
RougeChain uses Bech32m addresses with the rouge1 prefix (e.g., rouge1q8f3x7k2m4n9p...). These are derived from the SHA-256 hash of the raw ML-DSA-65 public key.
All API endpoints expect raw hex public keys, not rouge1 addresses:
✅ /api/balance/d67d8da279755a...
❌ /api/balance/rouge1q8f3x7k2m4n9p...
Tip: Use
GET /api/resolve/:inputto convert betweenrouge1…addresses and hex public keys.
v2 Signed Request Format
All write endpoints use a standard signed-request envelope:
{
"payload": {
"...endpoint-specific fields...",
"from": "your-signing-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "ml-dsa65-signature-of-payload-hex",
"public_key": "your-signing-public-key-hex"
}
The payload is JSON-serialized with keys sorted alphabetically, then signed with your ML-DSA-65 private key. The server verifies the signature, checks the timestamp (must be within 60 seconds), and rejects replayed nonces.
Endpoints Overview
System
| Endpoint | Method | Description |
|---|---|---|
/api/health | GET | Node health check |
/api/stats | GET | Network statistics |
/api/ws | GET | WebSocket for real-time events |
/api/price/xrge | GET | Current XRGE price |
Blockchain
| Endpoint | Method | Description |
|---|---|---|
/api/blocks | GET | Get all blocks |
/api/blocks/summary | GET | Block summary for charts |
/api/block/:height | GET | Get block by height |
/api/txs | GET | Get transactions |
/api/tx/:hash | GET | Get transaction by hash |
/api/tx/:hash/receipt | GET | Get transaction receipt |
/api/events | GET | Get all events |
Wallet & Accounts
| Endpoint | Method | Description |
|---|---|---|
/api/balance/:publicKey | GET | Get XRGE balance |
/api/balance/:publicKey/:token | GET | Get token balance |
/api/account/:pubkey/nonce | GET | Get account nonce |
/api/resolve/:input | GET | Resolve address ↔ public key |
/api/address/:pubkey/transactions | GET | Get address transactions |
/api/v2/transfer | POST | Transfer tokens (signed) |
/api/v2/faucet | POST | Request testnet tokens (signed) |
Tokens
| Endpoint | Method | Description |
|---|---|---|
/api/tokens | GET | List all tokens |
/api/token/:symbol/metadata | GET | Get token metadata |
/api/token/:symbol/holders | GET | Get token holders |
/api/token/:symbol/transactions | GET | Get token transactions |
/api/burn-address | GET | Get official burn address |
/api/burned | GET | Get burned token stats |
/api/v2/token/create | POST | Create token (signed) |
/api/v2/token/metadata/update | POST | Update token metadata (signed) |
/api/v2/token/metadata/claim | POST | Claim metadata ownership (signed) |
Token Allowances (ERC-20 Style)
| Endpoint | Method | Description |
|---|---|---|
/api/v2/token/approve | POST | Approve spender allowance (signed) |
/api/v2/token/transfer-from | POST | Transfer using allowance (signed) |
/api/v2/token/freeze | POST | Freeze/unfreeze token transfers (signed) |
/api/token/allowance | GET | Check specific allowance |
/api/token/allowances | GET | List allowances by owner/spender |
/api/allowances/:pubkey | GET | List all allowances for a key |
/api/locks/:pubkey | GET | Get token locks |
Staking & Validators
| Endpoint | Method | Description |
|---|---|---|
/api/validators | GET | List validators |
/api/validators/stats | GET | Validator vote stats |
/api/selection | GET | Proposer selection |
/api/finality | GET | Finality status |
/api/votes | GET | Vote quorum info |
/api/v2/stake | POST | Stake tokens (signed) |
/api/v2/unstake | POST | Unstake tokens (signed) |
Token Staking Pools
| Endpoint | Method | Description |
|---|---|---|
/api/staking/pools | GET | List all staking pools |
/api/staking/pool/:pool_id | GET | Get staking pool details |
/api/staking/stakes/:pubkey | GET | Get stakes by owner |
/api/staking/pool/:pool_id/stakes | GET | Get stakes in a pool |
AMM/DEX
| Endpoint | Method | Description |
|---|---|---|
/api/pools | GET | List liquidity pools |
/api/pool/:pool_id | GET | Get pool details |
/api/pool/:pool_id/prices | GET | Get price history |
/api/pool/:pool_id/events | GET | Get pool events |
/api/pool/:pool_id/stats | GET | Get pool statistics |
/api/swap/quote | POST | Get swap quote |
/api/v2/pool/create | POST | Create liquidity pool (signed) |
/api/v2/pool/add-liquidity | POST | Add liquidity (signed) |
/api/v2/pool/remove-liquidity | POST | Remove liquidity (signed) |
/api/v2/swap/execute | POST | Execute swap (signed) |
NFTs
| Endpoint | Method | Description |
|---|---|---|
/api/nft/collections | GET | List collections |
/api/nft/collection/:id | GET | Get collection |
/api/nft/collection/:id/tokens | GET | Get collection tokens |
/api/nft/token/:coll/:id | GET | Get specific NFT |
/api/nft/owner/:pubkey | GET | Get NFTs by owner |
/api/v2/nft/collection/create | POST | Create collection (signed) |
/api/v2/nft/mint | POST | Mint NFT (signed) |
/api/v2/nft/batch-mint | POST | Batch mint (signed) |
/api/v2/nft/transfer | POST | Transfer NFT (signed) |
/api/v2/nft/burn | POST | Burn NFT (signed) |
/api/v2/nft/lock | POST | Lock/unlock NFT (signed) |
/api/v2/nft/freeze-collection | POST | Freeze collection (signed) |
Smart Contracts
| Endpoint | Method | Description |
|---|---|---|
/api/v2/contract/deploy | POST | Deploy WASM contract (signed) |
/api/v2/contract/call | POST | Call contract method (signed) |
/api/contract/:address | GET | Get contract metadata |
/api/contract/:address/state | GET | Get contract state |
/api/contract/:address/events | GET | Get contract events |
Bridge (ETH/USDC + XRGE)
| Endpoint | Method | Description |
|---|---|---|
/api/bridge/config | GET | ETH/USDC bridge config |
/api/bridge/claim | POST | Claim bridge deposit |
/api/bridge/withdraw | POST | Withdraw to EVM |
/api/bridge/withdrawals | GET | List pending withdrawals |
/api/bridge/withdrawals/:txId | DELETE | Fulfill withdrawal |
/api/bridge/xrge/config | GET | XRGE bridge config |
/api/bridge/xrge/claim | POST | Claim XRGE deposit |
/api/bridge/xrge/withdraw | POST | Withdraw XRGE to EVM |
/api/bridge/xrge/withdrawals | GET | List XRGE withdrawals |
/api/bridge/xrge/withdrawals/:txId | DELETE | Fulfill XRGE withdrawal |
Shielded Transactions
| Endpoint | Method | Description |
|---|---|---|
/api/v2/shielded/shield | POST | Shield tokens (signed) |
/api/v2/shielded/transfer | POST | Private transfer (signed) |
/api/v2/shielded/unshield | POST | Unshield tokens (signed) |
/api/shielded/stats | GET | Shielded pool statistics |
/api/shielded/nullifier/:hash | GET | Check nullifier |
Rollup (Phase 3)
| Endpoint | Method | Description |
|---|---|---|
/api/v2/rollup/status | GET | Rollup status |
/api/v2/rollup/batch/:id | GET | Get rollup batch |
/api/v2/rollup/submit | POST | Submit rollup transfer (signed) |
Governance
| Endpoint | Method | Description |
|---|---|---|
/api/governance/proposals | GET | List all proposals |
/api/governance/proposals/:token | GET | Proposals by token |
/api/governance/proposal/:id | GET | Get proposal details |
/api/governance/proposal/:id/votes | GET | Get proposal votes |
Social
| Endpoint | Method | Description |
|---|---|---|
/api/social/track/:trackId/stats | GET | Get track stats (plays, likes, comments) |
/api/social/track/:trackId/comments | GET | Get track comments |
/api/social/artist/:pubkey/stats | GET | Get artist stats (followers, following) |
/api/social/user/:pubkey/likes | GET | Get user's liked track/post IDs |
/api/social/user/:pubkey/following | GET | Get user's followed artists |
/api/social/user/:pubkey/posts | GET | Get user's posts |
/api/social/post/:postId | GET | Get a single post with stats |
/api/social/post/:postId/stats | GET | Get post stats (likes, reposts, replies) |
/api/social/post/:postId/replies | GET | Get replies to a post |
/api/social/timeline | GET | Global timeline (newest first) |
/api/v2/social/play | POST | Record a play (signed) |
/api/v2/social/like | POST | Toggle like (signed) |
/api/v2/social/comment | POST | Post a comment (signed) |
/api/v2/social/comment/delete | POST | Delete a comment (signed) |
/api/v2/social/follow | POST | Toggle follow (signed) |
/api/v2/social/post | POST | Create a post (signed) |
/api/v2/social/post/delete | POST | Delete a post (signed) |
/api/v2/social/repost | POST | Toggle repost (signed) |
/api/v2/social/feed | POST | Get following feed (signed) |
Messenger
| Endpoint | Method | Description |
|---|---|---|
/api/messenger/wallets | GET | List messenger wallets |
/api/v2/messenger/wallets/register | POST | Register wallet (signed) |
/api/v2/messenger/conversations | POST | Create conversation (signed) |
/api/v2/messenger/conversations/list | POST | List conversations (signed) |
/api/v2/messenger/conversations/delete | POST | Delete conversation (signed) |
/api/v2/messenger/messages | POST | Send message (signed) |
/api/v2/messenger/messages/list | POST | List messages (signed) |
/api/v2/messenger/messages/read | POST | Mark as read (signed) |
/api/v2/messenger/messages/delete | POST | Delete message (signed) |
| Endpoint | Method | Description |
|---|---|---|
/api/v2/names/register | POST | Register mail name (signed) |
/api/v2/names/release | POST | Release a name (signed) |
/api/names/resolve/:name | GET | Resolve name → wallet |
/api/names/reverse/:walletId | GET | Reverse lookup → name |
/api/v2/mail/send | POST | Send encrypted mail (signed) |
/api/v2/mail/folder | POST | Get inbox/sent/trash (signed) |
/api/v2/mail/message | POST | Get single mail item (signed) |
/api/v2/mail/read | POST | Mark as read (signed) |
/api/v2/mail/move | POST | Move to folder (signed) |
/api/v2/mail/delete | POST | Delete permanently (signed) |
Push Notifications
| Endpoint | Method | Description |
|---|---|---|
/api/push/register | POST | Register push token (PQC-signed) |
/api/push/unregister | POST | Unregister push token (PQC-signed) |
P2P
| Endpoint | Method | Description |
|---|---|---|
/api/peers | GET | List known peers |
/api/peers/register | POST | Register as peer |
/api/blocks/import | POST | Import block from peer |
/api/tx/broadcast | POST | Receive broadcasted tx |
Authentication
Some endpoints require an API key (if configured on the node):
curl -H "X-API-Key: your-api-key" https://testnet.rougechain.io/api/stats
Rate Limiting
Rate limiting is disabled by default (--rate-limit-per-minute 0). When enabled, the node supports tiered limits:
- Tier 1 (Validators): Proven via
X-Validator-Keyheader + signature - Tier 2 (Registered peers): Recognized by IP
- Tier 3 (Public): Separate limits for read and write endpoints
Health & Stats API
System endpoints for monitoring node status and network statistics.
Health Check
GET /api/health
Returns the node's current status.
Response
{
"status": "ok",
"chain_id": "rougechain-devnet-1",
"height": 12345
}
| Field | Type | Description |
|---|---|---|
status | string | "ok" if the node is healthy |
chain_id | string | The chain identifier |
height | number | Current block height |
Use Cases
- Monitoring node uptime
- Checking sync status (compare height with peers)
- Load balancer health checks
Network Statistics
GET /api/stats
Returns network-wide statistics.
Response
{
"connected_peers": 3,
"network_height": 12345,
"is_mining": true,
"node_id": "abc123...",
"total_fees_collected": 1234.5,
"fees_in_last_block": 0.6,
"chain_id": "rougechain-devnet-1",
"finalized_height": 12300,
"ws_clients": 12,
"node_name": "node-1",
"base_fee": 0.001,
"total_fees_burned": 5000.0
}
| Field | Type | Description |
|---|---|---|
connected_peers | number | Currently connected peers |
network_height | number | Current block height |
is_mining | boolean | Whether this node is mining |
node_id | string | This node's ID |
total_fees_collected | number | Total fees collected |
fees_in_last_block | number | Fees collected in the last block |
chain_id | string | Chain identifier |
finalized_height | number | Last finalized block height |
ws_clients | number | Active WebSocket clients |
node_name | string | null | Human-readable node name (optional) |
base_fee | number | Current EIP-1559 base fee |
total_fees_burned | number | Total fees (base fee) burned |
Burn Stats
GET /api/burned
Get burned token statistics.
Response
{
"burned": {
"XRGE": 5000.0,
"qETH": 1.5
},
"total_xrge_burned": 5000.0
}
| Field | Type | Description |
|---|---|---|
burned | object | Per-token burned totals, keyed by token symbol |
total_xrge_burned | number | Total XRGE burned |
Examples
Monitoring Script
#!/bin/bash
while true; do
HEIGHT=$(curl -s http://127.0.0.1:5100/api/health | jq '.height')
echo "$(date): Block height = $HEIGHT"
sleep 10
done
Compare with Testnet
LOCAL=$(curl -s http://127.0.0.1:5100/api/health | jq '.height')
TESTNET=$(curl -s https://testnet.rougechain.io/api/health | jq '.height')
echo "Local: $LOCAL, Testnet: $TESTNET, Behind: $((TESTNET - LOCAL))"
Blocks API
Endpoints for querying block data on RougeChain.
Get Blocks
GET /api/blocks?limit=50&from_height=0
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | - | Page number (1-based); enables paginated mode |
per_page | number | 10 | Blocks per page (used with page) |
limit | number | 50 | Maximum blocks to return (max 100, up to 1000 for sync) |
from_height | number | - | Start from this block height (used for P2P sync) |
Response
{
"blocks": [
{
"version": 1,
"header": {
"version": 1,
"chain_id": "rougechain-devnet-1",
"height": 42,
"time": 1706745600000,
"prev_hash": "abc123...",
"tx_hash": "def456...",
"proposer_pub_key": "ghi789..."
},
"txs": [...],
"proposer_sig": "...",
"hash": "xyz..."
}
],
"total_height": 12345,
"page": 1,
"total_pages": 1235
}
total_heightis always present.pageandtotal_pagesare only returned in paginated mode (whenpageis supplied).
Block Fields
| Field | Type | Description |
|---|---|---|
version | number | Block format version |
header.chain_id | string | Chain identifier |
header.height | number | Block number |
header.time | number | Timestamp (ms since epoch) |
header.prev_hash | string | Hash of previous block |
header.tx_hash | string | Merkle root of transactions |
header.proposer_pub_key | string | Validator who proposed this block |
txs | array | Transactions included in this block |
proposer_sig | string | ML-DSA-65 signature by the proposer |
hash | string | SHA-256 hash of this block |
Block Summary
GET /api/blocks/summary
Returns time-bucketed block/transaction counts, suitable for charts and dashboards.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
range | string | 24h | Time range: 1h, 24h, or 7d |
Response
{
"success": true,
"range": "24h",
"intervalMs": 3600000,
"startTime": 1706659200000,
"endTime": 1706745600000,
"points": [
{
"timestamp": 1706742000000,
"blocks": 42,
"transactions": 137
}
]
}
Import Block (P2P)
Used by peer nodes to propagate blocks.
POST /api/blocks/import
Content-Type: application/json
See Peers API for details.
Block Verification
Every block is verified by receiving nodes:
- Hash check — Recompute the block hash and compare
- Signature check — Verify ML-DSA-65 signature against the proposer's public key
- Height check — Must extend the current chain tip by exactly 1
- Previous hash — Must reference the current tip's hash
- Transaction validity — All transactions must have valid signatures and sufficient balances
Transactions API
Transfer Tokens (v2)
Send XRGE or custom tokens to another address using client-side ML-DSA-65 signing.
POST /api/v2/transfer
Content-Type: application/json
Request Body
{
"payload": {
"to": "recipient-public-key-hex",
"amount": 100.0,
"token": "XRGE",
"from": "sender-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "sender-public-key-hex"
}
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient's public key (hex) or rouge1 address |
amount | number | Yes | Amount to send |
token | string | No | Token symbol (default: "XRGE") |
Fee: The transaction fee is enforced by the server; any client-supplied
feein the payload is ignored.Security: Private keys never leave your application. The transaction is signed client-side using ML-DSA-65 and the server verifies the signature before processing.
Response
{
"success": true,
"txId": "abc123..."
}
Error Response
{
"success": false,
"error": "insufficient balance: have 50.0000 XRGE, need 100.1000 XRGE"
}
Get Transactions
Retrieve recent transactions.
GET /api/txs?limit=50&offset=0
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 200 | Max transactions to return (max 1000) |
offset | number | 0 | Pagination offset |
Response
Each item wraps the transaction with its block context. The wrapper fields are camelCase
(txId, blockHeight, blockHash, blockTime); the nested tx (a TxV1) is
snake_case.
{
"txs": [
{
"txId": "abc123...",
"blockHeight": 42,
"blockHash": "xyz...",
"blockTime": 1706745600000,
"tx": {
"version": 1,
"tx_type": "transfer",
"from_pub_key": "abc...",
"nonce": 1234567890,
"payload": {
"to_pub_key_hex": "def...",
"amount": 100
},
"fee": 0.1,
"sig": "ghi..."
}
}
],
"total": 150
}
Get Transaction by Hash
GET /api/tx/:hash
Response
txId, blockHeight, blockHash, and blockTime are top-level (not inside tx). The
nested tx (a TxV1) is snake_case, and receipt carries the execution receipt when
present.
{
"success": true,
"txId": "abc123...",
"blockHeight": 42,
"blockHash": "xyz...",
"blockTime": 1706745600000,
"tx": {
"version": 1,
"tx_type": "transfer",
"from_pub_key": "abc...",
"nonce": 1234567890,
"payload": {
"to_pub_key_hex": "def...",
"amount": 100
},
"fee": 0.1,
"sig": "ghi..."
},
"receipt": null
}
Get Transaction Receipt
GET /api/tx/:hash/receipt
Returns execution receipt for contract calls and other complex transactions.
Request Faucet (v2)
Get free testnet XRGE tokens.
POST /api/v2/faucet
Content-Type: application/json
Request Body
{
"payload": {
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "your-signature-hex",
"public_key": "your-public-key-hex"
}
Response
{
"success": true,
"amount": 1000,
"txId": "abc123..."
}
Rate Limit
The faucet has additional rate limiting:
- 1 request per address per hour
- Whitelisted addresses bypass rate limits
Transaction Types
| Type | Description |
|---|---|
transfer | Standard XRGE or token transfer |
faucet | Faucet distribution |
stake | Stake tokens to become validator |
unstake | Unstake tokens |
create_token | Create custom token |
burn | Burn tokens permanently |
shield | Shield tokens (make private) |
unshield | Unshield tokens (make public) |
contract_deploy | Deploy WASM smart contract |
contract_call | Call smart contract method |
Wallet API
Endpoints for balance queries, transfers, and token management.
Note: Wallets are created client-side using ML-DSA-65 + ML-KEM-768 key generation. Private keys never leave your application. See Create a Wallet for details.
Get Balance
GET /api/balance/:publicKey
Path Parameters
| Parameter | Type | Description |
|---|---|---|
publicKey | string | The wallet's ML-DSA-65 public key (hex) |
Response
{
"balance": 1500.5,
"publicKey": "abc123...",
"tokens": {
"XRGE": 1500.5,
"qETH": 0.5
}
}
Transfer Tokens (v2)
POST /api/v2/transfer
Content-Type: application/json
Request Body
{
"payload": {
"to": "recipient-public-key-hex",
"amount": 100,
"token": "XRGE",
"from": "sender-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "sender-public-key-hex"
}
The transaction is signed client-side using ML-DSA-65. The server verifies the signature before processing.
Response
{
"success": true,
"txId": "abc123..."
}
Request Faucet (v2)
POST /api/v2/faucet
Content-Type: application/json
Request Body
{
"payload": {
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "your-signature-hex",
"public_key": "your-public-key-hex"
}
Response
{
"success": true,
"amount": 1000,
"txId": "abc123..."
}
See Get Test Tokens for details on rate limits.
Burn Address
GET /api/burn-address
Response
{
"burnAddress": "XRGE_BURN_0x000000000000000000000000000000000000000000000000000000000000DEAD"
}
Send tokens to this address to permanently burn them. Burned amounts are tracked on-chain.
Address Resolution
Resolve between compact rouge1… bech32 addresses and full hex public keys.
GET /api/resolve/:input
Input can be either a rouge1… address or a hex public key. The endpoint auto-detects the format.
Response
{
"success": true,
"address": "rouge1q8f3x7k2m4...",
"publicKey": "a1b2c3d4e5f6...",
"balance": 1000.5
}
Account Nonce
Get the current and next sequential nonce for a wallet. Used for replay protection in v2 signed transactions.
GET /api/account/:publicKey/nonce
Response
{
"success": true,
"publicKey": "a1b2c3d4...",
"nonce": 5,
"next_nonce": 6
}
Staking API
Endpoints for validator staking operations. All write operations use v2 signed requests.
List Validators
GET /api/validators
Query Parameters
| Parameter | Type | Description |
|---|---|---|
publicKey | string | (Optional) Filter by specific validator |
Response
{
"validators": [
{
"publicKey": "abc123...",
"stake": 10000.0,
"status": "active",
"blocksProposed": 142
},
{
"publicKey": "def456...",
"stake": 5000.0,
"status": "active",
"blocksProposed": 71
}
],
"totalStaked": 15000.0
}
Validator Fields
| Field | Type | Description |
|---|---|---|
publicKey | string | Validator's ML-DSA-65 public key |
stake | number | Amount of XRGE staked |
status | string | active or unbonding |
blocksProposed | number | Total blocks produced |
Stake Tokens
POST /api/v2/stake
Content-Type: application/json
Request Body
{
"payload": {
"amount": 10000,
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "your-ml-dsa65-signature-hex",
"public_key": "your-public-key-hex"
}
Response
{
"success": true,
"txId": "abc123...",
"stake": 10000.0,
"status": "active"
}
Requirements
| Requirement | Value |
|---|---|
| Minimum stake | 10,000 XRGE |
| Fee | 0.1 XRGE |
Unstake Tokens
POST /api/v2/unstake
Content-Type: application/json
Request Body
{
"payload": {
"amount": 5000,
"from": "your-public-key-hex",
"timestamp": 1706745600000,
"nonce": "random-hex-string"
},
"signature": "your-ml-dsa65-signature-hex",
"public_key": "your-public-key-hex"
}
Response
{
"success": true,
"txId": "abc123...",
"remainingStake": 5000.0
}
Unbonding
After unstaking, tokens enter an unbonding period (500 blocks) before they become available in your balance.
Error Responses
| Error | Cause |
|---|---|
"insufficient balance" | Not enough XRGE to stake |
"below minimum stake" | Amount is less than 10,000 XRGE |
"validator not found" | Trying to unstake but not a validator |
"invalid signature" | ML-DSA-65 signature verification failed |
Peers API
Endpoints for P2P peer management and discovery.
List Peers
Get all known peers.
GET /api/peers
Response
{
"peers": [
"https://testnet.rougechain.io/api",
"https://node2.example.com/api"
],
"peer_details": [
{ "url": "https://testnet.rougechain.io/api", "node_name": "RougeChain Primary" },
{ "url": "https://node2.example.com/api", "node_name": "My Cool Node" }
],
"count": 2
}
Register Peer
Register this node with another peer (enables discovery).
POST /api/peers/register
Content-Type: application/json
Request Body
{
"peerUrl": "https://mynode.example.com",
"nodeName": "My Validator Node"
}
The nodeName field is optional. If provided, it will be displayed on the network globe at rougechain.io/blockchain.
Response
{
"success": true,
"message": "Peer registered"
}
If the peer is already known:
{
"success": true,
"message": "Peer already known"
}
Import Block (P2P)
Receive a block from a peer (used for block propagation).
POST /api/blocks/import
Content-Type: application/json
Request Body
The full block object:
{
"version": 1,
"header": {
"version": 1,
"chainId": "rougechain-devnet-1",
"height": 42,
"time": 1706745600000,
"prevHash": "abc123...",
"txHash": "def456...",
"proposerPubKey": "..."
},
"txs": [...],
"proposerSig": "...",
"hash": "..."
}
Response
{
"success": true
}
Error Response
{
"success": false,
"error": "Block height 42 doesn't extend tip height 40"
}
Broadcast Transaction (P2P)
Receive a transaction broadcast from a peer.
POST /api/tx/broadcast
Content-Type: application/json
Request Body
The full transaction object.
Response
{
"success": true
}
Peer Discovery Flow
1. Node A starts with --peers "https://seed.example.com"
2. Node A calls GET /api/peers on seed node
→ Receives list of other peers
3. Node A adds new peers to its list
4. If Node A has --public-url, it calls POST /api/peers/register
on all known peers to announce itself
5. Every 30 seconds, repeat steps 2-4
This creates a mesh network where all nodes eventually discover each other.
Messenger API
Endpoints for the end-to-end encrypted PQC messenger.
All messages are encrypted client-side using ML-KEM-768 key encapsulation and AES-GCM. The server only stores encrypted blobs — it cannot read message contents.
v2 API (March 2026): All write operations now use
/api/v2/endpoints that require ML-DSA-65 signed requests with timestamp validation and nonce-based anti-replay protection. Legacy unsigned endpoints return HTTP 410 (Gone) in production.
Signed Request Format
All v2 write endpoints accept a signed request body:
{
"payload": {
"action": "register_wallet",
"from": "ml-dsa65-public-key-hex",
"timestamp": 1710100000000,
"nonce": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"...": "operation-specific fields"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "ml-dsa65-public-key-hex"
}
The server verifies: (1) the ML-DSA-65 signature, (2) the timestamp is within a 5-minute window, (3) the from field matches the signing key, and (4) the nonce has not been used before.
Register Messenger Wallet
POST /api/v2/messenger/wallets/register
Content-Type: application/json
Register your wallet's encryption public key so others can send you encrypted messages and mail. This is required before receiving mail from other apps. Requires a signed request.
Request Body (signed)
{
"payload": {
"id": "wallet-uuid-or-public-key",
"displayName": "Alice",
"signingPublicKey": "ml-dsa65-signing-public-key-hex",
"encryptionPublicKey": "ml-kem768-encryption-public-key-hex",
"from": "ml-dsa65-signing-public-key-hex",
"timestamp": 1710100000000,
"nonce": "random-hex-nonce"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "ml-dsa65-signing-public-key-hex"
}
| Field | Type | Description |
|---|---|---|
id | string | Unique wallet identifier |
displayName | string | Human-readable name (unique, case-insensitive) |
signingPublicKey | string | ML-DSA-65 public key (hex) |
encryptionPublicKey | string | ML-KEM-768 public key (hex) — needed for E2E encryption |
Response
{
"success": true
}
List Messenger Wallets
GET /api/messenger/wallets
Returns all registered messenger wallets.
Response
{
"success": true,
"wallets": [
{
"id": "wallet-uuid",
"display_name": "Alice",
"signing_public_key": "abc123...",
"encryption_public_key": "def456..."
}
]
}
Send Message
POST /api/v2/messenger/messages
Content-Type: application/json
Requires a signed request. The sender is authenticated via the ML-DSA-65 signature.
Request Body (signed)
The payload includes the message fields plus from, timestamp, and nonce:
{
"payload": {
"conversationId": "conv-uuid",
"senderPublicKey": "sender-pub-hex",
"recipientPublicKey": "recipient-pub-hex",
"senderEncrypted": "base64-encrypted-for-sender",
"recipientEncrypted": "base64-encrypted-for-recipient",
"senderDisplayName": "Alice",
"recipientDisplayName": "Bob",
"selfDestruct": false,
"destructSeconds": 0,
"from": "sender-pub-hex",
"timestamp": 1710100000000,
"nonce": "random-hex-nonce"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "sender-pub-hex"
}
Messages are encrypted twice — once for the sender (so they can read their sent messages) and once for the recipient. The server stores both ciphertexts.
Self-Destruct Messages
Set selfDestruct: true and destructSeconds to a value. After the recipient reads the message, it is marked as read and hidden from the UI.
Response
{
"success": true,
"messageId": "msg-uuid"
}
Get Messages
POST /api/v2/messenger/messages/list
Content-Type: application/json
Requires a signed request. The server verifies the caller is a participant in the conversation.
Request Body (signed)
{
"payload": {
"conversationId": "conv-uuid",
"from": "your-pub-hex",
"timestamp": 1710100000000,
"nonce": "random-hex-nonce"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "your-pub-hex"
}
Response
{
"messages": [
{
"id": "msg-uuid",
"conversationId": "conv-uuid",
"senderPublicKey": "abc...",
"senderDisplayName": "Alice",
"encrypted": "base64-ciphertext",
"timestamp": 1706745600000,
"selfDestruct": false,
"mediaType": null
}
]
}
Mark Message as Read
POST /api/v2/messenger/messages/read
Content-Type: application/json
Used for self-destruct messages. Requires a signed request with messageId and conversationId in the payload.
Delete Message
POST /api/v2/messenger/messages/delete
Content-Type: application/json
Requires a signed request with messageId and conversationId in the payload. The caller must be a conversation participant.
Delete Conversation
POST /api/v2/messenger/conversations/delete
Content-Type: application/json
Requires a signed request with conversationId in the payload. The caller must be a conversation participant.
Get Conversations
POST /api/v2/messenger/conversations/list
Content-Type: application/json
Returns all conversations for a wallet. Requires a signed request.
Response
{
"conversations": [
{
"conversationId": "conv-uuid",
"participants": [
{
"publicKey": "abc...",
"displayName": "Alice"
},
{
"publicKey": "def...",
"displayName": "Bob"
}
],
"lastMessage": "2024-01-31T12:00:00Z",
"unreadCount": 2
}
]
}
Media Messages
The messenger supports image and video attachments. Media is encrypted and sent as base64 within the message payload.
| Limit | Value |
|---|---|
| Max upload size | 50 MB (before compression) |
| Compressed target | ~1.5 MB |
| Image format | Converted to WebP client-side |
| Video format | Converted to WebM (VP9) client-side |
The client automatically compresses large media before encryption and sending.
Encryption Flow
Sender Recipient
│ │
│ 1. Generate shared secret via ML-KEM-768 │
│ 2. Derive AES-256 key via HKDF │
│ 3. Encrypt message with AES-GCM │
│ 4. Encrypt for self (sender copy) │
│ 5. Encrypt for recipient │
│ │
│── POST /messages (both ciphertexts) ────►│
│ │
│ 6. Decapsulate shared secret
│ 7. Derive same AES key
│ 8. Decrypt message
Wallet Blocking
Users can block wallets client-side. Blocked wallets are filtered from conversations and contacts. The block list is stored in browser localStorage under pqc_blocked_wallets.
This is a client-side feature — the server is not involved. Blocked users can still send messages, but they won't appear in the blocking user's UI.
SDK Usage
The @rougechain/sdk provides a high-level API for messenger operations. All write operations now require a wallet parameter for ML-DSA-65 request signing:
import { RougeChain, Wallet } from "@rougechain/sdk";
const rc = new RougeChain("https://testnet.rougechain.io/api");
const wallet = Wallet.generate();
// Register wallet (signed request, required for receiving messages and mail)
await rc.messenger.registerWallet(wallet, {
id: wallet.publicKey,
displayName: "Alice",
signingPublicKey: wallet.publicKey,
encryptionPublicKey: encPubKey,
});
// Conversations (signed requests)
const convos = await rc.messenger.getConversations(wallet);
await rc.messenger.createConversation(wallet, [wallet.publicKey, recipientPubKey]);
// Messages (signed requests)
const msgs = await rc.messenger.getMessages(wallet, conversationId);
await rc.messenger.sendMessage(wallet, conversationId, encryptedContent, {
selfDestruct: true,
destructAfterSeconds: 30,
});
await rc.messenger.markRead(wallet, messageId);
await rc.messenger.deleteMessage(wallet, messageId);
TypeScript types: MessengerWallet, MessengerConversation, MessengerMessage
Mail API
Endpoints for the PQC-encrypted mail system. Mail uses ML-KEM-768 encryption with a Content Encryption Key (CEK) pattern for multi-recipient support, ML-DSA-65 unified signatures, and name registry with atomic registration.
v2 API (March 2026): All write operations now use
/api/v2/endpoints that require ML-DSA-65 signed requests with timestamp validation and nonce-based anti-replay protection. Legacy unsigned endpoints return HTTP 410 (Gone) in production.
Signed Request Format
All v2 write endpoints accept a signed request body:
{
"payload": {
"name": "alice",
"walletId": "wallet-uuid",
"from": "ml-dsa65-public-key-hex",
"timestamp": 1710100000000,
"nonce": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "ml-dsa65-public-key-hex"
}
The server verifies: (1) the ML-DSA-65 signature, (2) the timestamp is within a 5-minute window, (3) the from field matches the signing key, (4) the nonce has not been used before, and (5) the caller is authorized for the operation.
Name Registry
Important for third-party apps: Before a user can receive mail, two things must be registered on the node:
- Their wallet (via
/api/v2/messenger/wallets/register) — provides the encryption key - Their mail name (via
/api/v2/names/register) — maps a human-readable name to the wallet
Register a Name
POST /api/v2/names/register
Content-Type: application/json
Register a human-readable email address (e.g., alice@rouge.quant). Requires a signed request. Registration uses atomic compare-and-swap to prevent race conditions.
Request (signed):
{
"payload": {
"name": "alice",
"walletId": "wallet-uuid-or-public-key",
"from": "signing-public-key-hex",
"timestamp": 1710100000000,
"nonce": "random-hex-nonce"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "signing-public-key-hex"
}
Response:
{
"success": true,
"entry": {
"name": "alice",
"wallet_id": "wallet-uuid-or-public-key",
"registered_at": "2026-03-11T00:00:00Z"
}
}
Error:
{
"success": false,
"error": "Name already taken"
}
Resolve a Name
GET /api/names/resolve/:name
Returns the name entry and associated wallet (with encryption keys).
Response:
{
"success": true,
"entry": {
"name": "alice",
"wallet_id": "wallet-uuid",
"registered_at": "2026-03-11T00:00:00Z"
},
"wallet": {
"id": "wallet-uuid",
"display_name": "Alice",
"signing_public_key": "abc123...",
"encryption_public_key": "def456..."
}
}
Reverse Lookup (Wallet ID → Name)
GET /api/names/reverse/:walletId
Response:
{
"success": true,
"name": "alice"
}
Release a Name
POST /api/v2/names/release
Content-Type: application/json
Requires a signed request. The caller must own the name (verified via signing key).
{
"payload": {
"name": "alice",
"from": "signing-public-key-hex",
"timestamp": 1710100000000,
"nonce": "random-hex-nonce"
},
"signature": "ml-dsa65-signature-hex",
"public_key": "signing-public-key-hex"
}
Send Mail
POST /api/v2/mail/send
Content-Type: application/json
Requires a signed request. The sender is authenticated via ML-DSA-65 signature verification.
Request Body
{
"from": "alice",
"fromPublicKey": "sender-pub-hex",
"to": "bob",
"toPublicKey": "recipient-pub-hex",
"senderEncrypted": "base64-encrypted-for-sender",
"recipientEncrypted": "base64-encrypted-for-recipient",
"replyToId": null
}
The senderEncrypted and recipientEncrypted fields contain the encrypted subject and body. Both sender and recipient get their own copy, just like the messenger.
Set replyToId to the ID of the mail being replied to, enabling threading.
Response
{
"success": true,
"id": "mail-uuid"
}
Get Inbox
POST /api/v2/mail/folder
Content-Type: application/json
Requires a signed request with "folder": "inbox" in the payload. The caller's wallet is resolved from the signing key.
Response
{
"mail": [
{
"id": "mail-uuid",
"from": "alice",
"fromPublicKey": "abc...",
"to": "bob",
"toPublicKey": "def...",
"encrypted": "base64-ciphertext",
"timestamp": 1706745600000,
"read": false,
"replyToId": null
}
]
}
Get Sent Mail
POST /api/v2/mail/folder
Content-Type: application/json
Same as inbox but with "folder": "sent" in the payload. Requires a signed request.
Get Single Mail
POST /api/v2/mail/message
Content-Type: application/json
Requires a signed request with messageId in the payload.
Mark as Read
POST /api/v2/mail/read
Content-Type: application/json
Requires a signed request with messageId in the payload.
Move to Folder
POST /api/v2/mail/move
Content-Type: application/json
Requires a signed request with messageId and folder in the payload. Valid folders: inbox, sent, trash, starred, drafts.
Get Trash
POST /api/v2/mail/folder
Content-Type: application/json
Same as inbox/sent but with "folder": "trash" in the payload. Requires a signed request.
Delete Mail
POST /api/v2/mail/delete
Content-Type: application/json
Permanently deletes a mail item. Requires a signed request with messageId in the payload. The caller must be a participant (sender or recipient).
Email Domains
| Domain | Platform |
|---|---|
@rouge.quant | Website and browser extensions |
@qwalla.mail | QWalla mobile app (future) |
Threading
Mail threading is handled client-side by following the replyToId chain. When viewing a mail, the client:
- Fetches both inbox and sent mail
- Walks the
replyToIdchain to build the thread - Displays messages in chronological order
- Collapses older messages, expands the latest two
Encryption
Mail uses a Content Encryption Key (CEK) pattern for efficient multi-recipient support:
- Generate a random 256-bit AES key (the CEK)
- Encrypt subject, body, and attachment with the CEK via AES-256-GCM
- For each recipient (and the sender): encapsulate a shared secret via ML-KEM-768, derive a wrapping key via HKDF, and encrypt the CEK with AES-GCM
- Sign the concatenation of all encrypted parts (subject + body + attachment) with ML-DSA-65 (unified signature)
- Server stores the encrypted content with per-recipient wrapped keys
Attachments
Mail supports encrypted file attachments up to 2 MB. Attachments are encrypted client-side using the same ML-KEM-768 scheme as the message body.
Sending with Attachment
Include the attachmentEncrypted and hasAttachment fields in the send request:
{
"fromWalletId": "sender-wallet-id",
"toWalletIds": ["recipient-wallet-id"],
"subjectEncrypted": "ml-kem-encrypted-subject",
"bodyEncrypted": "ml-kem-encrypted-body",
"attachmentEncrypted": "ml-kem-encrypted-attachment-json",
"signature": "ml-dsa-65-signature",
"replyToId": null,
"hasAttachment": true
}
Attachment Payload
The attachmentEncrypted field contains an ML-KEM encrypted JSON string:
{
"name": "photo.jpg",
"type": "image/jpeg",
"data": "base64-encoded-file-data",
"size": 102400
}
| Field | Type | Description |
|---|---|---|
name | string | Original filename |
type | string | MIME type |
data | string | Base64-encoded file content |
size | number | File size in bytes |
Reading Attachments
When fetching mail (inbox/sent/trash), messages with attachments will include the attachment_encrypted field. The client must:
- Decrypt using the recipient's ML-KEM-768 private key
- Parse the JSON to extract the attachment metadata and data
- Display inline (images) or offer download (other types)
SDK Usage
The @rougechain/sdk provides a high-level API for name registry and mail operations. All write operations now require a wallet parameter for ML-DSA-65 request signing:
import { RougeChain, Wallet } from "@rougechain/sdk";
const rc = new RougeChain("https://testnet.rougechain.io/api");
const wallet = Wallet.generate();
// Register wallet + name (signed requests)
await rc.messenger.registerWallet(wallet, {
id: wallet.publicKey,
displayName: "Alice",
signingPublicKey: wallet.publicKey,
encryptionPublicKey: encPubKey,
});
await rc.mail.registerName(wallet, "alice", wallet.publicKey);
// Resolve a recipient before sending (public, no signing needed)
const recipient = await rc.mail.resolveName("bob");
// recipient.wallet.encryption_public_key → use for ML-KEM encryption
// Reverse lookup (public, no signing needed)
const name = await rc.mail.reverseLookup(wallet.publicKey); // "alice"
// Send, read, manage mail (all signed requests)
await rc.mail.send(wallet, { from, to, subject, body, encrypted_subject, encrypted_body });
const inbox = await rc.mail.getInbox(wallet);
await rc.mail.markRead(wallet, messageId);
await rc.mail.move(wallet, messageId, "trash");
await rc.mail.delete(wallet, messageId);
TypeScript types: NameEntry, ResolvedName, MailMessage, SendMailParams
Push Notifications API
Register and unregister Expo push tokens for real-time mobile notifications. Both endpoints require ML-DSA-65 signed payloads — only the wallet owner can register or unregister their token.
Register Push Token
POST /api/push/register
Content-Type: application/json
Request (SignedTransactionRequest)
{
"payload": {
"type": "push_register",
"from": "wallet-public-key",
"pushToken": "ExponentPushToken[xxx]",
"platform": "expo",
"timestamp": 1234567890123,
"nonce": "random-hex"
},
"signature": "ml-dsa-65-signature-hex",
"public_key": "wallet-public-key"
}
Response
{
"success": true
}
Unregister Push Token
POST /api/push/unregister
Content-Type: application/json
Request (SignedTransactionRequest)
{
"payload": {
"type": "push_unregister",
"from": "wallet-public-key",
"timestamp": 1234567890123,
"nonce": "random-hex"
},
"signature": "ml-dsa-65-signature-hex",
"public_key": "wallet-public-key"
}
Response
{
"success": true
}
Security
Push token registration is secured with the same SignedTransactionRequest pattern used by v2 API endpoints:
- The payload is signed client-side with the wallet's ML-DSA-65 private key
- The server verifies the signature against the
public_key - Timestamp must be within 5 minutes to prevent replay attacks
- Only the wallet owner can register a push token for their public key
This prevents spoofing — an attacker cannot register a rogue push token for someone else's wallet.
SDK Usage
import { RougeChain } from "@rougechain/sdk";
const rc = new RougeChain("https://testnet.rougechain.io/api");
// Register — pass the wallet, not raw keys (signing happens inside)
await rc.registerPushToken(wallet, "ExponentPushToken[xxx]"); // platform defaults to "expo"
// Unregister
await rc.unregisterPushToken(wallet);
API Reference — Bridge
ETH/USDC Bridge
Get Bridge Config
GET /api/bridge/config
Returns bridge status, custody address, chain ID, and supported tokens.
Response:
{
"enabled": true,
"custodyAddress": "0x...",
"chainId": 8453,
"supportedTokens": ["ETH"]
}
enabledisfalsewhen the custody address is unset.chainIdis8453on Base mainnet (84532on Base Sepolia).Only ETH (→ qETH) is claimable through this endpoint today. qUSDC bridging is planned but not yet enabled — do not treat USDC as claimable.
Claim Bridge Deposit
POST /api/bridge/claim
Claim wrapped qETH after depositing ETH on Base. (Usually automatic — see the auto-claim note below.) qUSDC is not mintable — USDC bridging is planned but not yet enabled.
Body:
{
"evmTxHash": "0x...",
"evmAddress": "0x...",
"evmSignature": "0x...",
"recipientRougechainPubkey": "abc123...",
"token": "ETH"
}
The token field is "ETH" (default); this mints qETH. "USDC" is not claimable yet.
The node verifies the on-chain deposit (the Transfer to custody, not a caller-supplied
amount), checks the EVM signature, requires the configured confirmation depth
(QV_BRIDGE_MIN_CONFIRMATIONS, default 6), and then mints qETH.
Auto-claim: with the deposit watcher enabled (default), the relayer detects
BridgeDepositETH/BridgeDepositERC20events on Base and claims them for you via/api/bridge/deposit/auto-claim— no manual claim needed. This endpoint remains available as a manual fallback. Claims are deduped, so an auto-claim and a manual claim of the same deposit cannot double-mint.
Bridge Withdraw
POST /api/bridge/withdraw
Burn wrapped tokens and create a pending withdrawal for the relayer.
Body (signed):
{
"fromPublicKey": "abc123...",
"amountUnits": 10000,
"evmAddress": "0x...",
"signature": "...",
"payload": { "type": "bridge_withdraw", "..." }
}
List Pending Withdrawals
GET /api/bridge/withdrawals
Returns pending ETH/USDC withdrawals waiting for the relayer. XRGE withdrawals are
excluded here — they are served by /api/bridge/xrge/withdrawals.
Response:
{
"withdrawals": [
{
"txId": "0x...",
"evmAddress": "0x...",
"amountUnits": 10000,
"createdAt": 1717000000000,
"ownerPubkey": "abc123...",
"tokenSymbol": "qETH",
"status": "pending",
"attempts": 0,
"lastError": null
}
]
}
| Field | Meaning |
|---|---|
ownerPubkey | RougeChain L1 key of the withdrawer — the refund recipient |
tokenSymbol | qETH / qUSDC / XRGE; authoritative type discriminator (replaces the legacy xrge: tx-id prefix) |
status | pending · failed (relayer retrying) · fulfilled · refunded |
attempts | Failed relayer release attempts so far |
lastError | Last release error, when status is failed |
Fulfill Withdrawal
DELETE /api/bridge/withdrawals/:txId
Mark a withdrawal as fulfilled (relayer calls this after releasing on Base). Requires
x-bridge-relayer-secret header or a PQC-signed operator body.
Report Withdrawal Failure
POST /api/bridge/withdrawals/:txId/failure
Relayer reports a failed release attempt. Increments attempts, records lastError,
and sets status: failed. Auth: relayer secret or operator signature.
Body: { "error": "release tx reverted: 0x..." }
Response: { "success": true, "attempts": 3, "shouldRefund": false, "threshold": 5 }
When attempts reaches the threshold (5), shouldRefund becomes true and the
daemon logs an alert.
Refund Withdrawal
POST /api/bridge/withdrawals/:txId/refund
Refund a withdrawal that could not be released: re-mints the burned amountUnits of
tokenSymbol back to ownerPubkey via a bridge_mint, then clears the pending entry.
Deduped with a refund:<txId> claim-store key so it can never refund twice. Auth:
relayer secret or operator signature.
Response: { "success": true, "txId": "<l1-mint-tx>", "amount": 10000, "token": "qETH", "recipient": "abc123..." }
Deposit Auto-claim
POST /api/bridge/deposit/auto-claim
Used by the relayer's deposit watcher to mint a deposit discovered on Base without a browser claim. Re-verifies the EVM tx on-chain and dedupes against manual claims (idempotent). Auth: relayer secret or operator signature.
Body:
{
"evmTxHash": "0x...",
"recipientRougechainPubkey": "abc123...",
"token": "ETH"
}
token is ETH / USDC / XRGE, taken from the on-chain deposit event.
Admin Reclaim
POST /api/bridge/admin/reclaim
Manually process a missed deposit. Same verified mint path as auto-claim, but
authenticated with adminKey (requires QV_ADMIN_KEY set on the daemon).
Body: { "evmTxHash": "0x...", "recipientRougechainPubkey": "abc123...", "token": "ETH", "adminKey": "..." }
XRGE Bridge
Get XRGE Bridge Config
GET /api/bridge/xrge/config
Response:
{
"enabled": true,
"vaultAddress": "0x...",
"tokenAddress": "0x147120faEC9277ec02d957584CFCD92B56A24317",
"chainId": 8453
}
Claim XRGE Deposit
POST /api/bridge/xrge/claim
Body:
{
"evmTxHash": "0x...",
"evmAddress": "0x...",
"evmSignature": "0x...",
"recipientRougechainPubkey": "abc123..."
}
evmSignature is required — sign the claim message with the wallet that sent the
XRGE. The mint amount and depositor are derived from the on-chain
Transfer(from → vault) log emitted by the XRGE token; any caller-supplied amount
is ignored. The deposit must reach the required confirmation depth
(QV_BRIDGE_MIN_CONFIRMATIONS, default 6) before it mints.
XRGE Withdraw
POST /api/bridge/xrge/withdraw
Body (signed):
{
"fromPublicKey": "abc123...",
"amount": 100,
"evmAddress": "0x...",
"signature": "...",
"payload": { "..." }
}
List XRGE Withdrawals
GET /api/bridge/xrge/withdrawals
Returns pending XRGE withdrawals (filtered by tokenSymbol == "XRGE"). Each item
carries the same status / attempts / ownerPubkey / tokenSymbol fields as
the ETH listing.
Fulfill XRGE Withdrawal
DELETE /api/bridge/xrge/withdrawals/:txId
Failure / Refund
XRGE withdrawals reuse the shared withdrawal endpoints by txId:
POST /api/bridge/withdrawals/:txId/failure and .../refund (see above). The relayer
reports failures and the refund re-mints XRGE to ownerPubkey.
API Reference — NFTs
All write operations use the v2 signed transaction API (client-side signing).
Read Endpoints
List Collections
GET /api/nft/collections
Get Collection
GET /api/nft/collection/:id
Get Collection Tokens
GET /api/nft/collection/:id/tokens
Get Token
GET /api/nft/token/:collection_id/:token_id
Get NFTs by Owner
GET /api/nft/owner/:pubkey
Write Endpoints (v2 Signed)
All write endpoints accept a signed transaction body:
{
"payload": { "type": "nft_mint", "..." },
"signature": "...",
"public_key": "..."
}
Create Collection
POST /api/v2/nft/collection/create
Payload fields: symbol, name, maxSupply, royaltyBps, royaltyRecipient, image, description
Fee: 50 XRGE
royaltyBps— royalty in basis points (500= 5%).royaltyRecipient— (optional) wallet that receives secondary-sale royalties. Defaults to the creator when omitted, and is fixed at creation. Must be a normal wallet address — ⚠️ never a contract address (funds sent to a contract are permanently lost).
Mint NFT
POST /api/v2/nft/mint
Payload fields: collectionId, name, metadataUri, attributes
Fee: 5 XRGE
Batch Mint
POST /api/v2/nft/batch-mint
Payload fields: collectionId, names, uris, batchAttributes
Fee: 5 XRGE per NFT
Transfer NFT
POST /api/v2/nft/transfer
Payload fields: collectionId, tokenId, to, salePrice
Fee: 1 XRGE
When salePrice > 0 and the collection has a royalty, the sender also pays salePrice × royaltyBps / 10000 XRGE to the collection's royaltyRecipient (on top of the fee). salePrice is self-declared — a transfer with no salePrice pays no royalty.
Burn NFT
POST /api/v2/nft/burn
Payload fields: collectionId, tokenId
Fee: 0.1 XRGE
Lock/Unlock NFT
POST /api/v2/nft/lock
Payload fields: collectionId, tokenId, locked
Fee: 0.1 XRGE
Freeze Collection
POST /api/v2/nft/freeze-collection
Payload fields: collectionId, frozen
Fee: 0.1 XRGE
API Reference — DEX / AMM
Read Endpoints
List Pools
GET /api/pools
Returns all liquidity pools with reserves, LP supply, and token info.
Response:
{
"success": true,
"pools": [
{
"pool_id": "XRGE-MTK",
"token_a": "MTK",
"token_b": "XRGE",
"reserve_a": 50000,
"reserve_b": 100000,
"total_lp_supply": 70710,
"fee_rate": 0.003
}
]
}
Get Pool
GET /api/pool/:pool_id
Pool ID format: TOKEN_A-TOKEN_B (alphabetically sorted).
Response:
{
"success": true,
"pool": {
"pool_id": "XRGE-MTK",
"token_a": "MTK",
"token_b": "XRGE",
"reserve_a": 50000,
"reserve_b": 100000,
"total_lp_supply": 70710,
"fee_rate": 0.003
}
}
Get Pool Events
GET /api/pool/:pool_id/events
Returns swap, add_liquidity, and remove_liquidity events for a pool.
Response:
{
"success": true,
"events": [
{
"id": "evt_abc123",
"pool_id": "XRGE-MTK",
"event_type": "Swap",
"user_pub_key": "abc123...",
"timestamp": 1710000000,
"block_height": 12345,
"tx_hash": "tx_abc...",
"token_in": "XRGE",
"token_out": "MTK",
"amount_in": 1000,
"amount_out": 490,
"reserve_a_after": 50490,
"reserve_b_after": 99000
}
]
}
Get Pool Price History
GET /api/pool/:pool_id/prices
Returns up to 500 price snapshots in chronological order. A snapshot is recorded after every swap, pool creation, and liquidity change.
Response:
{
"success": true,
"prices": [
{
"pool_id": "XRGE-MTK",
"timestamp": 1710000000,
"block_height": 12345,
"reserve_a": 50000,
"reserve_b": 100000,
"price_a_in_b": 2.0,
"price_b_in_a": 0.5
}
]
}
| Field | Description |
|---|---|
price_a_in_b | How many token_b for 1 token_a |
price_b_in_a | How many token_a for 1 token_b |
Get Pool Stats
GET /api/pool/:pool_id/stats
Response:
{
"success": true,
"stats": {
"pool_id": "XRGE-MTK",
"total_swaps": 142,
"total_volume_a": 500000,
"total_volume_b": 1000000,
"swap_count_24h": 12,
"volume_24h_a": 25000,
"volume_24h_b": 50000
}
}
Get Swap Quote
POST /api/swap/quote
Body:
{
"token_in": "XRGE",
"token_out": "qETH",
"amount_in": 1000
}
Response:
{
"success": true,
"amount_out": 95,
"price_impact": 0.5,
"path": ["XRGE", "qETH"],
"pools": ["XRGE-qETH"]
}
Get All Events
GET /api/events
Returns all DEX events across all pools.
Write Endpoints (v2 Signed)
Create Pool
POST /api/v2/pool/create
Payload fields: token_a, token_b, amount_a, amount_b
Fee: 10 XRGE
Add Liquidity
POST /api/v2/pool/add-liquidity
Payload fields: pool_id, amount_a, amount_b
Fee: 1 XRGE
Remove Liquidity
POST /api/v2/pool/remove-liquidity
Payload fields: pool_id, lp_amount
Fee: 1 XRGE
Execute Swap
POST /api/v2/swap/execute
Payload fields: token_in, token_out, amount_in, min_amount_out
Fee: 0.3% of input + 1 XRGE
SDK Usage
import { RougeChain, Wallet } from '@rougechain/sdk';
import type { PriceSnapshot, PoolEvent, PoolStats } from '@rougechain/sdk';
const rc = new RougeChain('https://testnet.rougechain.io/api');
// Read
const pools = await rc.dex.getPools();
const pool = await rc.dex.getPool('XRGE-MTK');
const prices: PriceSnapshot[] = await rc.dex.getPriceHistory('XRGE-MTK');
const stats: PoolStats = await rc.dex.getPoolStats('XRGE-MTK');
const events: PoolEvent[] = await rc.dex.getPoolEvents('XRGE-MTK');
// Quote & swap
const quote = await rc.dex.quote({ poolId: 'XRGE-MTK', tokenIn: 'XRGE', tokenOut: 'MTK', amountIn: 100 });
await rc.dex.swap(wallet, { tokenIn: 'XRGE', tokenOut: 'MTK', amountIn: 100, minAmountOut: 95 });
Smart Contracts API
Deploy Contract
Deploy a WASM smart contract to RougeChain.
POST /api/v2/contract/deploy
| Field | Type | Required | Description |
|---|---|---|---|
wasm | string | ✅ | Base64-encoded WASM bytecode |
deployer | string | ✅ | Deployer's public key (hex) |
nonce | number | ❌ | Nonce for deterministic address (default: 0) |
Response:
{
"success": true,
"address": "a1b2c3d4e5f6...",
"wasmSize": 12345
}
Contract deploy fee:
wasmSize × 0.000001XRGE
Call Contract
Execute a method on a deployed contract (mutating — creates an on-chain tx).
POST /api/v2/contract/call
| Field | Type | Required | Description |
|---|---|---|---|
contractAddr | string | ✅ | Contract address (hex) |
method | string | ✅ | Method name to call |
caller | string | ❌ | Caller's public key |
args | object | ❌ | JSON arguments |
gasLimit | number | ❌ | Max fuel (default: 10,000,000) |
Response:
{
"success": true,
"returnData": { ... },
"gasUsed": 1500,
"events": [],
"error": null
}
Contract call fee:
gasUsed × 0.000001XRGE
Get Contract Metadata
GET /api/contract/:addr
Returns contract metadata: address, deployer, code hash, creation timestamp, WASM size.
{
"success": true,
"contract": {
"address": "86fe93e2...",
"deployer": "test-deployer",
"code_hash": "a1b2c3...",
"wasm_size": 711,
"created_at": 1774401569985
}
}
Read Contract Storage
Full State Dump
GET /api/contract/:addr/state
Returns all key-value pairs in the contract's persistent storage.
{
"success": true,
"state": {
"count": "42",
"owner": "alice"
},
"count": 2
}
Single Key Lookup
GET /api/contract/:addr/state?key=<hex_key>
Reads a single key from storage.
{
"success": true,
"key": "636f756e74",
"value": "3432",
"valueUtf8": "42"
}
Get Contract Events
GET /api/contract/:addr/events?limit=50
Returns indexed events emitted by the contract.
{
"success": true,
"events": [
{
"contract_addr": "86fe93e2...",
"topic": "transfer",
"data": "{\"from\":\"alice\",\"to\":\"bob\",\"amount\":100}",
"block_height": 620,
"tx_hash": "c3d4e5f6..."
}
],
"count": 1
}
List All Contracts
GET /api/contracts
Returns all deployed contracts with their metadata.
{
"success": true,
"contracts": [
{
"address": "86fe93e2...",
"deployer": "test-deployer",
"code_hash": "a1b2c3...",
"wasm_size": 711,
"created_at": 1774401569985
}
],
"count": 1
}
Architecture
An overview of RougeChain's system architecture.
High-Level Architecture
┌───────────────────────────────────────────────────────────┐
│ Clients │
│ │
│ ┌──────────┐ ┌──────────────────┐ ┌────────────────┐ │
│ │ Website │ │ Browser Extension│ │ @rougechain/sdk│ │
│ │ React │ │ Chrome / Firefox │ │ npm package │ │
│ └────┬─────┘ └────────┬─────────┘ └───────┬────────┘ │
│ │ │ │ │
│ │ Client-side ML-DSA-65 signing │ │
│ │ Client-side ML-KEM-768 encryption │ │
│ └────────────┬────┴─────────────────────┘ │
└────────────────────┼──────────────────────────────────────┘
│ HTTPS REST API
▼
┌───────────────────────────────────────────────────────────┐
│ Core Node (Rust) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ REST API │ │Blockchain│ │Validator │ │Messenger │ │
│ │ (Actix) │ │ Engine │ │ / PoS │ │ Server │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴──────────────┴──────────────┴──────────────┴────┐ │
│ │ Storage Layer │ │
│ │ chain.jsonl │ validators-db (RocksDB) │ messenger-db│ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────┬──────────────────────────────────────┘
│ P2P (HTTP)
▼
┌───────────────────────────────────────────────────────────┐
│ Peer Nodes │
│ Block sync │ TX broadcast │ Peer discovery │
└───────────────────────────────────────────────────────────┘
Components
Core Node (Rust)
The backend is a single Rust binary (quantum-vault-daemon) that includes:
| Module | Responsibility |
|---|---|
| REST API | HTTP endpoints via Actix-web |
| Blockchain Engine | Block production, transaction processing, state management |
| Validator / PoS | Stake tracking, proposer selection, rewards |
| Messenger Server | Stores encrypted messages and wallet registrations |
| Mail Server | Stores encrypted mail, name registry |
| P2P Layer | Peer discovery, block/tx propagation |
| AMM/DEX | Liquidity pools, swap execution, price calculation |
| Bridge | qETH bridge from Base Sepolia |
Frontend (React + TypeScript)
The website at rougechain.io is a single-page application built with:
| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| TypeScript | Type safety |
| Vite | Build tool |
| Tailwind CSS | Styling |
| shadcn/ui | Component library |
@noble/post-quantum | PQC cryptography (ML-DSA-65, ML-KEM-768) |
The frontend is a PWA (Progressive Web App) and can be installed on mobile and desktop.
Browser Extensions
Two Chrome extensions provide wallet functionality:
| Extension | Description |
|---|---|
| RougeChain Wallet | Primary browser extension |
| rougechain-wallet | Secondary extension |
Both inject a window.rougechain provider (similar to MetaMask's window.ethereum) for dApp integration.
SDK (@rougechain/sdk)
The npm package @rougechain/sdk provides a programmatic interface for interacting with RougeChain from Node.js or browser applications.
Cryptography Stack
┌─────────────────────────────────────────┐
│ Application Layer │
│ Transactions │ Messages │ Mail │ Auth │
└─────────────┬───────────────────────────┘
│
┌─────────────▼───────────────────────────┐
│ Cryptographic Primitives │
│ │
│ ML-DSA-65 (FIPS 204) │
│ └─ Signing: txs, blocks, stakes │
│ │
│ ML-KEM-768 (FIPS 203) │
│ └─ Key encapsulation: messenger, mail │
│ │
│ AES-256-GCM │
│ └─ Symmetric encryption of content │
│ │
│ HKDF (SHA-256) │
│ └─ Key derivation from shared secrets │
│ │
│ SHA-256 │
│ └─ Block hashes, tx hashes, Merkle │
└─────────────────────────────────────────┘
Data Flow
Transaction Flow
1. User creates transaction in browser
2. Transaction payload is constructed
3. ML-DSA-65 signs the payload client-side
4. Signed transaction is sent to node via REST API
5. Node verifies signature
6. Transaction enters mempool
7. Validator includes it in next block
8. Block is signed and propagated to peers
Message Flow
1. Sender looks up recipient's ML-KEM-768 public key
2. ML-KEM-768 encapsulation generates shared secret
3. HKDF derives AES-256 key from shared secret
4. Message is encrypted with AES-GCM (for both sender and recipient)
5. Encrypted blobs are sent to server
6. Recipient fetches encrypted blob
7. ML-KEM-768 decapsulation recovers shared secret
8. Message is decrypted client-side
Mail Flow
1. User registers a name (e.g., alice@rouge.quant) via Name Registry
2. Sender composes email, encrypts subject + body with PQC
3. Encrypted mail is stored on the server
4. Recipient fetches and decrypts client-side
5. Thread history is reconstructed via replyToId chain
Storage
Node Storage
| Store | Format | Content |
|---|---|---|
chain.jsonl | Append-only JSON lines | Block data |
tip.json | JSON | Current chain tip reference |
validators-db/ | RocksDB | Validator stakes and state |
messenger-db/ | RocksDB | Encrypted messages and wallets |
Client Storage
| Store | Location | Content |
|---|---|---|
| Wallet keys | localStorage | Encrypted ML-DSA-65 and ML-KEM-768 keys |
| Block list | localStorage | Blocked wallet addresses |
| Mail settings | localStorage | Email signature preferences |
| Display name | localStorage | User's messenger display name |
Security Model
| Principle | Implementation |
|---|---|
| Keys never leave client | All signing/encryption happens in-browser |
| Server is untrusted | Server only stores encrypted data |
| Quantum-resistant | NIST-approved PQC algorithms throughout |
| BIP-39 mnemonics | Wallets derive from a 24-word BIP-39 mnemonic (256-bit entropy); the mnemonic is the primary backup. Keys are also encrypted at rest with AES-256-GCM (PBKDF2, 600k iterations) |
| Signed v2 writes | /api/v2 writes require an ML-DSA-65 signature over a canonical payload; legacy v1 write endpoints return 410 Gone in production |
| Dual encryption | Messages encrypted for both sender and recipient |
v2 Signed Write Requirements
Every write through /api/v2 is authenticated by signature, not by a session:
- The request is a signed envelope
{ payload, signature, public_key }. - The signature is an ML-DSA-65 signature over the canonical
payloadbytes. payload.timestampmust fall within a ±5-minute window of server time.payload.frommust equal the signing public key (public_key).- A signature replay guard (process-global
SEEN_SIGNATURES, keyed bysha256(signature), 5-minute window) rejects any signature already seen in-window. - Legacy v1 write endpoints return 410 Gone in production.
Post-Quantum Cryptography
RougeChain uses NIST-approved post-quantum cryptographic algorithms to protect against both classical and quantum computer attacks.
Why Post-Quantum?
Quantum computers threaten current cryptography:
| Algorithm | Quantum Threat |
|---|---|
| RSA | Broken by Shor's algorithm |
| ECDSA | Broken by Shor's algorithm |
| SHA-256 | Weakened (Grover's algorithm) |
| ML-DSA | Secure |
| ML-KEM | Secure |
Algorithms Used
ML-DSA-65 (Digital Signatures)
Formerly: CRYSTALS-Dilithium
Standard: FIPS 204
Security Level: NIST Level 3 (192-bit classical equivalent)
Used for:
- Transaction signatures
- Block proposal signatures
- Validator attestations
Key sizes:
| Component | Size |
|---|---|
| Public key | ~1,952 bytes |
| Private key | ~4,032 bytes |
| Signature | ~3,309 bytes |
ML-KEM-768 (Key Encapsulation)
Formerly: CRYSTALS-Kyber
Standard: FIPS 203
Security Level: NIST Level 3
Used for:
- Messenger encryption
- PQC Mail encryption
- Future: Encrypted transactions
Key sizes:
| Component | Size |
|---|---|
| Public key | ~1,184 bytes |
| Private key | ~2,400 bytes |
| Ciphertext | ~1,088 bytes |
SHA-256 (Hashing)
Used for:
- Block hashes
- Transaction hashes
- Merkle trees
While Grover's algorithm reduces SHA-256 security to ~128-bit equivalent against quantum computers, this is still considered secure.
Implementation
RougeChain uses the following libraries:
| Component | Library |
|---|---|
| Backend (Rust) | fips204 crate (ml_dsa_65) |
| Frontend (JS) | @noble/post-quantum |
All cryptographic operations happen locally - private keys never leave your device.
Key Generation
#![allow(unused)] fn main() { // Rust example — fips204 ml_dsa_65 (FIPS 204) use fips204::ml_dsa_65; use fips204::traits::{Signer, Verifier}; // Generate an ML-DSA-65 keypair let (pk, sk) = ml_dsa_65::try_keygen().unwrap(); // Sign a message (second arg is an optional context string) let signature = sk.try_sign(message, &[]).unwrap(); // Verify let valid = pk.verify(message, &signature, &[]); }
// TypeScript example
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa';
const { publicKey, secretKey } = ml_dsa65.keygen();
const signature = ml_dsa65.sign(message, secretKey);
const valid = ml_dsa65.verify(signature, message, publicKey);
Security Considerations
- Key storage - Private keys are stored in
localStoragewhen no vault password is set (for PWA persistence), or encrypted with AES-256-GCM (PBKDF2, 600K iterations) when the user configures a vault passphrase. Active session keys are held insessionStorage. - Entropy - Keys use cryptographically secure random number generators
- Side channels - Library implementations are designed to be constant-time
- Hybrid approach - Consider adding classical signatures for defense-in-depth
zk-STARKs (Zero-Knowledge Proofs)
RougeChain includes a zk-STARK proof system for privacy-preserving transaction verification. STARKs are quantum-resistant by design — they rely only on hash functions, not elliptic curves.
How It Works
The STARK module can prove that a balance transfer is valid (value is conserved, sender has sufficient funds) without revealing the actual balances or transfer amount. The verifier only sees the final balances.
| Property | Value |
|---|---|
| Library | winterfell (Meta) |
| Hash function | Blake3-256 |
| Quantum resistance | ✅ Hash-based (no EC) |
| Proof type | Balance transfer (value conservation) |
Usage
#![allow(unused)] fn main() { use quantum_vault_crypto::stark::{ prove_balance_transfer, verify_balance_transfer, BalanceTransferInputs, }; use winterfell::math::{fields::f128::BaseElement, FieldElement}; // Prover (knows private balances) let proof = prove_balance_transfer(1000, 500, 250).unwrap(); // Verifier (only sees final balances) let public_inputs = BalanceTransferInputs { total_value: BaseElement::from(1500u64), final_sender_balance: BaseElement::from(750u64), final_receiver_balance: BaseElement::from(750u64), }; verify_balance_transfer(proof, public_inputs).unwrap(); }
zk-STARK Rollup Batch Proofs (Phase 3)
The rollup system batches multiple transfers into a single STARK proof, dramatically reducing on-chain verification costs.
Rollup AIR (5-Column Trace)
sender_before | sender_after | receiver_after | amount | running_hash
Constraints:
- Value conservation:
sender_after = sender_before - amount - Running hash accumulation:
hash[i+1] = hash[i] + sender_before[i] * amount[i] - Cross-check redundancy
Boundary Assertions: running_hash transitions from pre_state_root to post_state_root
State Root
SHA-256 Merkle tree from sorted account balances with domain separation:
- Leaf:
SHA-256("ROUGECHAIN_STATE_V1" || address || balance_le_bytes) - Node:
SHA-256("ROUGECHAIN_NODE_V1" || left || right)
Rollup API
# Check rollup status
curl https://testnet.rougechain.io/api/v2/rollup/status
# Submit transfer to rollup batch
curl -X POST https://testnet.rougechain.io/api/v2/rollup/submit \
-H "Content-Type: application/json" \
-d '{"sender":"pubkey1","receiver":"pubkey2","amount":100,"fee":1}'
# Get completed batch result
curl https://testnet.rougechain.io/api/v2/rollup/batch/1
Bridge Verification (STARK Bridge)
Deposits from Base are cryptographically verified before minting:
- EVM Receipt Verification — tx status, recipient, sender, confirmations
- SHA-256 Commitment Nullifiers —
SHA-256("ROUGECHAIN_BRIDGE_V1" || tx_hash || address || amount)prevents double-claims - BridgeDeposit Event Verification — for XRGE vault deposits (log parsing)
Future Roadmap
- zk-STARK proof system (Phase 1: balance transfer AIR)
- zk-STARK Phase 2: shielded transactions on-chain
- zk-STARK Phase 3: ZK-rollup layer
- STARK bridge deposit verification
- Fully trustless STARK bridge (Base light client)
- SLH-DSA (SPHINCS+) as alternative signature scheme
- Hybrid classical+PQC mode
- Hardware wallet support
- Threshold signatures for multi-sig
-
WASM-compiled STARK prover for browser (
core/wasm-prover/, served asstark-prover.wasm)
PQC Messaging & Mail
RougeChain includes two built-in communication systems — both fully end-to-end encrypted with post-quantum cryptography.
Overview
| Feature | Messenger | |
|---|---|---|
| Purpose | Real-time chat | Async email |
| Encryption | ML-KEM-768 + AES-GCM | ML-KEM-768 + AES-GCM |
| Addresses | Wallet public keys | @rouge.quant / @qwalla.mail names |
| Media | Images, videos (auto-compressed) | Text + attachments (up to 2 MB) |
| Self-destruct | ✅ Configurable timer | ❌ |
| Folders | — | Inbox, Sent, Trash |
| Threading | Conversations | Reply chains |
| Server sees | Encrypted blobs only | Encrypted blobs only |
Why Post-Quantum?
Classical encrypted messengers (Signal, WhatsApp) use algorithms like X25519 and Ed25519. A sufficiently powerful quantum computer could break these with Shor's algorithm.
RougeChain's messaging uses ML-KEM-768 (CRYSTALS-Kyber, FIPS 203) for key encapsulation — resistant to both classical and quantum attacks. Messages encrypted today remain secure even if quantum computers arrive in the future.
How It Works
Messenger Encryption
Alice Bob
│ │
│ 1. Look up Bob's ML-KEM-768 public key │
│ 2. Encapsulate → shared secret │
│ 3. HKDF → AES-256 key │
│ 4. AES-GCM encrypt (for Bob) │
│ 5. AES-GCM encrypt (for self) │
│ │
│── Signed request to /api/v2/... ────────►│
│ │
│ 6. Decapsulate → same shared secret
│ 7. HKDF → same AES-256 key
│ 8. AES-GCM decrypt
Key principle: The server stores two encrypted blobs per message — one for the sender, one for the recipient. It never has the keys to decrypt either.
Mail Encryption (CEK Pattern)
Mail uses a Content Encryption Key (CEK) pattern for efficient multi-recipient support:
- Generate a random 256-bit AES key (the CEK)
- Encrypt all mail content (subject, body, attachment) once with the CEK via AES-256-GCM
- For each recipient (and the sender): KEM-wrap the CEK using their ML-KEM-768 public key
- Sign the concatenation of all encrypted parts with ML-DSA-65 (unified signature)
This design encrypts content only once regardless of recipient count.
Messenger
The real-time messenger is built into the RougeChain web app and browser extension.
Features
- E2E encrypted conversations between any two wallets
- Media sharing — images (auto-converted to WebP) and videos (VP9 WebM), compressed client-side
- Self-destruct messages — set a timer; message disappears after being read
- Display names — register a name that shows in conversations
- Contact blocking — client-side block list stored in localStorage
- Conversation management — create, view, and delete conversations
Getting Started
- Go to Messenger in the sidebar
- Your wallet is automatically registered
- Enter a recipient's public key to start a conversation
- Messages are encrypted before leaving your browser
API Endpoints
See Messenger API Reference for full endpoint documentation.
PQC Mail
Mail adds traditional email features on top of the same PQC encryption layer.
@rouge.quant Addresses
Instead of sharing long public keys, register a human-readable name:
| Action | Result |
|---|---|
| Register "alice" | You get alice@rouge.quant |
| Send to "bob" | System looks up Bob's public key automatically |
Names are unique and first-come-first-served.
Mail Features
- Subject lines — encrypted along with the body
- Folders — Inbox, Sent, Trash with move/delete operations
- Threading — reply chains built client-side via
replyToId - Mark as read — track read status per message
Email Domains
| Domain | Platform |
|---|---|
@rouge.quant | Website and browser extension |
@qwalla.mail | QWalla mobile app |
Both domains resolve against the same on-chain name registry — the domain is a client-side display choice only.
Getting Started
- Go to Mail in the sidebar
- Click Register Name and choose your
@rouge.quantaddress - Send encrypted mail to any registered name
- All encryption/decryption happens in your browser
API Endpoints
See Mail API Reference for full endpoint documentation.
Authenticated Requests
All mail, messenger, and name registry write operations require ML-DSA-65 signed requests via /api/v2/ endpoints. Each request includes:
from— Sender's ML-DSA-65 public keytimestamp— Millisecond-precision timestamp (valid within a 5-minute window)nonce— 16 bytes of random hex (prevents replay attacks)signature— ML-DSA-65 signature over the canonical JSON payload
The server verifies the signature, validates the timestamp, confirms the sender owns the wallet, and rejects duplicate nonces. Legacy unsigned endpoints return HTTP 410 (Gone) in production.
Trust-on-First-Use (TOFU)
The messenger tracks public key fingerprints (SHA-256 hash) for contacts:
- On first interaction, the contact's key fingerprint is stored locally
- On subsequent interactions, the fingerprint is compared — a "Key Changed" warning appears if it differs
- The shortened fingerprint is shown in the chat header for manual verification
Security Properties
| Property | Details |
|---|---|
| Quantum-resistant | ML-KEM-768 key encapsulation (FIPS 203) |
| Forward secrecy | Each message uses a fresh encapsulation |
| Zero-knowledge server | Server stores only ciphertext — cannot read messages |
| Client-side crypto | All encryption/decryption in the browser via WebAssembly |
| Dual ciphertext | Sender and recipient each get their own encrypted copy |
| Signed requests | All API calls authenticated with ML-DSA-65 signatures |
| Anti-replay | Nonce + timestamp prevents request replay attacks |
| TOFU | Key fingerprint tracking with change detection |
| Unified signatures | Mail signed over all encrypted parts (subject + body + attachment) |
| CEK multi-recipient | Efficient per-recipient key wrapping without re-encryption |
| Atomic name registry | Compare-and-swap prevents race conditions on name claims |
| Persistent or vaulted keys | Private keys in localStorage (no password) or AES-256-GCM encrypted blob (with vault passphrase); active session keys in sessionStorage |
Notifications & Unread Badges
Browser Extension
The browser extension tracks unread counts for both Chat and Mail tabs:
| Feature | Details |
|---|---|
| Tab badges | Chat and Mail tabs display a numeric badge when unread items exist |
| Tooltips | Hovering a badged tab shows "3 unread messages" or "2 unread emails" |
| Extension icon badge | The combined unread total (chat + mail) is shown on the browser toolbar icon via chrome.action.setBadgeText |
| System notifications | Native OS notifications for new messages, new mail, received/sent tokens, contract events, staking, and balance changes |
| Badge clearing | Viewing a conversation or inbox marks items as read server-side and updates the badge immediately |
Notifications are powered by two channels:
- WebSocket — Real-time transaction and balance events via
wss://testnet.rougechain.io/api/ws - Polling — Unread messenger and mail counts checked every 15 seconds via signed
/api/v2/endpoints
QWalla Mobile App
QWalla provides the same unread badge experience:
| Feature | Details |
|---|---|
| Tab badges | Expo Router tabBarBadge on Chat and Mail tabs |
| Initial poll | On app launch, actual unread counts are fetched from the server so badges are accurate from the start |
| Real-time updates | WebSocket events increment the badge for new messages and mail |
| Push notifications | Expo push notifications for messages, mail, and transfers (requires registerPushToken) |
| Badge clearing | Navigating to the Chat or Mail tab clears the respective unread count |
Deriving Unread Counts
The server does not expose a dedicated "unread total" endpoint. Clients derive counts from existing data:
- Chat: Sum
unread_countfrom each conversation returned byPOST /api/v2/messenger/conversations/list - Mail: Count inbox items where the label's
is_readfield isfalsefromPOST /api/v2/mail/folder
SDK Usage
All write operations now require a wallet parameter for ML-DSA-65 request signing:
import { RougeChain, Wallet } from '@rougechain/sdk';
const rc = new RougeChain('https://testnet.rougechain.io/api');
const wallet = Wallet.generate();
// Step 1: Register wallet (signed request)
await rc.messenger.registerWallet(wallet, {
id: wallet.publicKey,
displayName: 'Alice',
signingPublicKey: wallet.publicKey,
encryptionPublicKey: encPubKey,
});
// Step 2: Register a mail name (signed request)
await rc.mail.registerName(wallet, 'alice', wallet.publicKey);
// Resolve a recipient's name → wallet + encryption key (public, no signing needed)
const bob = await rc.mail.resolveName('bob');
// bob.wallet.encryption_public_key → use for ML-KEM encryption
// Reverse lookup (public, no signing needed)
const name = await rc.mail.reverseLookup(wallet.publicKey); // "alice"
For the full messenger and mail APIs, see the SDK documentation.
Social Layer
RougeChain includes a built-in social layer for posts, likes, reposts, follows, comments, and play tracking. Social data is stored server-side in sled with ML-DSA-65 signed writes — tips settle on-chain via rc.transfer().
Features
| Feature | Description |
|---|---|
| Posts | Standalone text posts (max 4000 chars) with threaded replies via replyToId |
| Timeline | Global timeline (newest first) and personalized following feed |
| Likes | Toggle likes on posts or tracks — reuses the same endpoint |
| Reposts | Toggle reposts on any post |
| Comments | Track-level comments with pagination |
| Follows | Follow/unfollow any user; follower and following counts |
| Play counts | Record plays on tracks (debounced per session) |
| Tips | Send XRGE tips to creators via rc.transfer() — settles on-chain |
API Endpoints
Read (unsigned GET):
GET /api/social/timeline— Global timelineGET /api/social/post/:postId— Single post with statsGET /api/social/post/:postId/stats— Post engagement stats (likes, reposts, replies)GET /api/social/post/:postId/replies— Threaded repliesGET /api/social/user/:pubkey/posts— User's postsGET /api/social/track/:trackId/stats— Track stats (plays, likes, comments)GET /api/social/track/:trackId/comments— Track commentsGET /api/social/artist/:pubkey/stats— Artist stats (followers, following)GET /api/social/user/:pubkey/likes— User's liked IDsGET /api/social/user/:pubkey/following— User's followed artists
Write (v2 signed POST):
POST /api/v2/social/post— Create a postPOST /api/v2/social/post/delete— Delete your postPOST /api/v2/social/like— Toggle likePOST /api/v2/social/repost— Toggle repostPOST /api/v2/social/comment— Post a commentPOST /api/v2/social/comment/delete— Delete your commentPOST /api/v2/social/follow— Toggle followPOST /api/v2/social/play— Record a playPOST /api/v2/social/feed— Get following feed (authenticated)
SDK Usage
import { RougeChain, Wallet } from '@rougechain/sdk';
const rc = new RougeChain('https://testnet.rougechain.io/api');
const wallet = Wallet.generate();
// Create a post
const { post } = await rc.social.createPost(wallet, "Hello RougeChain!");
// Reply
await rc.social.createPost(wallet, "Great post!", post.id);
// Like, repost, follow
await rc.social.toggleLike(wallet, post.id);
await rc.social.toggleRepost(wallet, post.id);
await rc.social.toggleFollow(wallet, artistPubKey);
// Read timeline
const timeline = await rc.social.getGlobalTimeline();
const feed = await rc.social.getFollowingFeed(wallet);
CLI Usage
# Post
rougechain post "Hello from the CLI!"
# Reply
rougechain post "Nice post!" --reply-to <post-id>
# Timeline
rougechain timeline --limit 20
# Your feed (posts from people you follow)
rougechain feed
# Like / repost
rougechain like <post-or-track-id>
rougechain repost <post-id>
# Get a post
rougechain get-post <post-id>
WASM Smart Contracts
RougeChain includes a built-in WASM smart contract engine powered by wasmi — the same pure-Rust WASM interpreter used by Parity/Substrate.
v2 update: contracts can now custody and move XRGE.
host_transfer/host_get_balanceoperate in quanta (1 XRGE = 10^9 quanta), transfers are single-hop (a contract moves only its own balance), and moves are enforced conserving and overdraft-free. See Contract XRGE Custody for the rules and examples.
Overview
Contracts are written in Rust (or any language that compiles to WASM), compiled to .wasm, and deployed on-chain. Execution is fuel-metered in a sandbox with host functions for chain interaction.
Architecture
Your Contract (Rust) → cargo build --target wasm32
→ .wasm bytecode
→ Deploy via API
→ Execute in wasmi sandbox
→ Host functions bridge to chain state
Host Functions
Contracts can call these host functions to interact with the chain:
| Function | Description |
|---|---|
host_log(ptr, len) | Debug logging |
host_get_caller(buf, len) | Get caller's public key |
host_get_self_addr(buf, len) | Get contract's own address |
host_get_block_height() | Current block height |
host_get_block_time() | Current block timestamp |
host_get_balance(addr, len) | Check XRGE balance |
host_transfer(to, len, amount) | Transfer XRGE from contract |
host_storage_read(key, klen, val, vlen) | Read persistent storage |
host_storage_write(key, klen, val, vlen) | Write persistent storage |
host_storage_delete(key, klen) | Delete from storage |
host_emit_event(topic, tlen, data, dlen) | Emit indexed event |
host_sha256(data, dlen, out) | Compute SHA-256 hash |
host_set_return(data, dlen) | Set return value |
host_call_contract(addr, alen, method, mlen, args, argslen, gas) | Cross-contract call (returns call_id) |
host_get_call_result(call_id, buf, len) | Read sub-call result |
host_pqc_verify(pk, pklen, msg, msglen, sig, siglen) | ML-DSA-65 signature verify |
host_pqc_pubkey_to_address(pk, pklen, out, outlen) | Derive rouge1... address |
host_pqc_hash_pubkey(pk, pklen, out) | SHA-256 of public key |
Gas Metering & Fees
Every WASM instruction costs 1 fuel unit. The default limit is 10,000,000 fuel per call (≈10M instructions). If a contract runs out of fuel, execution halts and all state changes are reverted.
Fee Schedule
| Operation | Fee Formula |
|---|---|
| Contract Deploy | wasm_size_bytes × 0.000001 XRGE |
| Contract Call | gas_used × 0.000001 XRGE |
Fees are automatically calculated and included in the on-chain transaction. They appear in the transaction detail view on the explorer.
API
Deploy a Contract
POST /api/v2/contract/deploy
{
"wasm": "<base64-encoded WASM bytecode>",
"deployer": "<public key hex>",
"nonce": 0
}
Call a Contract Method
POST /api/v2/contract/call
{
"contractAddr": "<contract address>",
"method": "my_method",
"caller": "<public key>",
"args": { "key": "value" },
"gasLimit": 10000000
}
Query Contract State
GET /api/contract/{addr} # metadata
GET /api/contract/{addr}/state # full state dump (all keys)
GET /api/contract/{addr}/state?key=x # single key lookup
GET /api/contract/{addr}/events # event log
GET /api/contracts # list all contracts
ERC-20 Token Standard
RougeChain includes a reference ERC-20 token contract at contracts/erc20_template/. This implements the standard fungible token interface:
| Method | Args | Description |
|---|---|---|
init | {name, symbol, decimals, total_supply, owner} | Initialize token, mint supply to owner |
name / symbol / decimals / total_supply | {} | Token metadata queries |
balance_of | {account} | Get account balance |
transfer | {to, amount} | Transfer tokens (caller → to) |
approve | {spender, amount} | Set allowance |
allowance | {owner, spender} | Get allowance |
transfer_from | {from, to, amount} | Transfer using allowance |
Storage Layout
meta:name/meta:symbol/meta:decimals/meta:total_supply— Token metadatabal:{account}— Account balancesallow:{owner}:{spender}— Allowances
Build & Deploy
cd contracts/erc20_template
cargo build --release --target wasm32-unknown-unknown
# Deploy the .wasm from target/wasm32-unknown-unknown/release/
Explorer Integration
Deployed contracts are visible in the RougeChain explorer:
- Contracts Explorer (
/contracts) — List all deployed contracts with search/sort - Contract Detail (
/contract/{addr}) — Contract info, live state viewer, interactive call UI - Transaction Detail — Contract txs show: contract address, method, gas used, WASM size
SDK
Contract helpers live on the rc.shielded sub-client. The constructor takes the API
base URL as a string (note the trailing /api):
import { RougeChain } from '@rougechain/sdk';
const rc = new RougeChain('https://api.rougechain.io/api');
// Deploy
const deploy = await rc.shielded.deployContract({
wasm: base64WasmBytes,
deployer: wallet.publicKey,
});
// Call
const result = await rc.shielded.callContract({
contractAddr: deploy.address,
method: 'increment',
caller: wallet.publicKey,
});
// Query
const meta = await rc.shielded.getContract(deploy.address);
const events = await rc.shielded.getContractEvents(deploy.address);
const allState = await rc.shielded.getContractState(deploy.address); // full dump
const single = await rc.shielded.getContractState(deploy.address, '636f756e74'); // single key
MCP Server (AI Agents)
The RougeChain MCP server exposes smart contract operations as tools for AI agents:
| Tool | Description |
|---|---|
list_contracts | List all deployed contracts |
get_contract | Get contract metadata |
get_contract_state | Read state (single key or full dump) |
get_contract_events | Get contract event log |
deploy_contract | Deploy WASM bytecode |
call_contract | Execute a contract method |
Security
WASM smart contracts maintain RougeChain's post-quantum security guarantees:
- All contract interactions are ML-DSA-65 signed transactions
- WASM execution is pure computation — no classical crypto involved
- Contract addresses are derived deterministically via SHA-256
- Execution is sandboxed with no host OS access
Cross-Contract Calls
Contracts can call other contracts using host functions. Calls are queued during execution and processed recursively by the runtime after the primary call completes.
Host Functions
| Function | Returns | Description |
|---|---|---|
host_call_contract(addr, alen, method, mlen, args, argslen, gas) | call_id (i32) | Queue a call to another contract |
host_get_call_result(call_id, buf, len) | bytes written | Read the result of a sub-call |
Behavior
- Max depth: 8 nested calls (prevents infinite recursion)
- State merging: storage writes and events from sub-calls are merged atomically
- XRGE moves are single-hop (v2): a contract moves only its own balance —
host_transfercalls made by a sub-contract are not applied on-chain. See Contract XRGE Custody - Gas: sub-calls consume gas from the parent's remaining budget
- Failure: if a sub-call fails, it returns
-2fromhost_get_call_result; the parent can handle it gracefully
Example (Rust)
#![allow(unused)] fn main() { extern "C" { fn host_call_contract( addr: *const u8, alen: u32, method: *const u8, mlen: u32, args: *const u8, argslen: u32, gas: u64, ) -> i32; fn host_get_call_result(call_id: i32, buf: *mut u8, len: u32) -> i32; } #[no_mangle] pub extern "C" fn call_other() { let addr = b"contract_abc123..."; let method = b"get_value"; let args = b"{}"; let call_id = unsafe { host_call_contract( addr.as_ptr(), addr.len() as u32, method.as_ptr(), method.len() as u32, args.as_ptr(), args.len() as u32, 1_000_000, ) }; // Result is available after host processes the call queue let mut buf = [0u8; 1024]; let n = unsafe { host_get_call_result(call_id, buf.as_mut_ptr(), buf.len() as u32) }; // n > 0: success, n bytes of return data // n == -2: sub-call failed } }
PQC Precompiles
Native post-quantum cryptographic operations available as host functions — no need to implement ML-DSA in WASM.
| Function | Returns | Description |
|---|---|---|
host_pqc_verify(pk, pklen, msg, msglen, sig, siglen) | 1 valid, 0 invalid, -1 error | ML-DSA-65 signature verification |
host_pqc_pubkey_to_address(pk, pklen, out, outlen) | bytes written | Derive rouge1... bech32m address from raw pubkey |
host_pqc_hash_pubkey(pk, pklen, out) | 32 on success | SHA-256 hash of public key |
Key Sizes
- Public Key: 1,952 bytes (ML-DSA-65)
- Signature: 3,309 bytes (ML-DSA-65)
- Address: ~63 bytes (
rouge1...bech32m string) - Pubkey Hash: 32 bytes (SHA-256)
Use Cases
- On-chain auth: Verify a user's PQC signature inside a contract
- Address derivation: Convert a pubkey to a
rouge1...address for permission checks - Identity checks: Compare pubkey hashes for compact storage
EIP-1559 Dynamic Fees
RougeChain uses an EIP-1559-like fee model with base fee adjustment and fee burning:
How It Works
- Base fee adjusts ±12.5% per block based on block fullness (target: 10 txs/block)
- Floor: minimum base fee of 0.001 XRGE
- Fee burning: base fee portion is burned (deflationary)
- Priority fee (tip): 20% to block proposer, 70% to validators (stake-weighted), 10% to treasury
API
GET /api/fee-info
{
"baseFee": 0.0377,
"suggestedPriorityFee": 0.0038,
"suggestedTotalFee": 0.0415,
"totalBurned": 68.16,
"targetTxsPerBlock": 10,
"maxChangePercent": 12.5,
"blockHeight": 618
}
Contract XRGE Custody (RougeChain v2)
Live on mainnet since v2 (block 18). WASM contracts can now hold and move XRGE.
Before v2, a contract could receive XRGE but never spend it — funds sent to a contract address were locked forever. As of the v2 fork, contracts are first-class holders of XRGE: they can custody a balance and pay it out from within their own execution.
The unit: quanta
XRGE is counted internally in quanta, its smallest indivisible unit:
1 XRGE = 1,000,000,000 quanta (10^9)
This is the wei ↔ ETH model. Contracts do integer math in quanta; wallets and
UIs render the decimal XRGE. There are no floating-point amounts anywhere in a
contract — everything is exact integers, so a payout can never drift or round
ambiguously. A 1-XRGE three-way split is 333_333_333 quanta to each recipient
with a 1-quanta remainder — exact, conserved to the atom.
Host functions
host_get_balance(addr_ptr, addr_len) -> i64 // returns the address's balance in QUANTA
host_transfer(to_ptr, to_len, amount_i64) -> i32 // moves `amount` QUANTA from THIS contract to `to`
// returns 0 on success, 1 on refusal
amountis in quanta. A negative amount is rejected.- A contract can only transfer its own balance —
host_transferalways debits the executing contract's address. - If the contract's balance can't cover
amount, the transfer returns1and nothing moves (no partial transfer, no delta recorded).
The two rules the chain enforces
Every set of moves a contract makes in a call is checked before any balance changes, all-or-nothing:
- Conservation — the moves must net to exactly zero. A contract can never mint or burn XRGE, only move it.
- No overdraft — no account may go below zero. A contract can only move quanta it actually holds.
Break either and the whole call's balance effects are rejected.
v1 limitation: single-hop
XRGE custody is single-hop in this release: a contract moves its own XRGE. Cross-contract XRGE moves (contract A calls contract B, and B pays out) are not applied — cross-calls still work for compute/storage, but XRGE does not move across a hop. Design payout logic so each contract moves only its own balance.
Determinism & deployment
- Deploy transactions now carry the contract bytecode on-chain; every node installs it on import and re-executes identically. No separate code distribution step.
- Call arguments are recorded on the transaction, so on-chain re-execution matches what the caller signed.
- A call a node cannot execute deterministically (missing code, VM error) is rejected — the network fails closed rather than silently diverging. A contract that simply reverts or runs out of gas is normal: no moves, the block proceeds.
Example: a royalty splitter
(module
(import "env" "host_transfer" (func $tr (param i32 i32 i64) (result i32)))
(memory (export "memory") 1)
(data (i32.const 0) "alice")
(data (i32.const 16) "bob")
(data (i32.const 32) "carol")
;; send 1 XRGE (1e9 quanta) to each of three recipients
(func (export "split") (result i32)
(drop (call $tr (i32.const 0) (i32.const 5) (i64.const 1000000000)))
(drop (call $tr (i32.const 16) (i32.const 3) (i64.const 1000000000)))
(call $tr (i32.const 32) (i32.const 5) (i64.const 1000000000))))
Fund the contract, call split, and 1 XRGE lands in each of three wallets —
conserved exactly, agreed by every node.
What this changes for NFT royalties (qRougee)
Previously the NFT royaltyRecipient had to be a wallet — routing royalties
to a contract address would lock them. With v2 custody that constraint relaxes:
a splitter contract can receive royalties and fan them to collaborators on-chain
(single-hop). Off-chain payout wallets are no longer the only safe option.
Multi-Signature Wallets
RougeChain supports M-of-N multi-signature wallets with PQC-safe ML-DSA-65 aggregate verification.
Overview
A multi-sig wallet requires M out of N co-signers to approve a transaction before it executes. This enables:
- Shared treasury management — DAOs, teams, and organizations
- Secure cold storage — 2-of-3 schemes (e.g. hardware + phone + backup)
- Escrow — Buyer + seller + arbitrator
Transaction Types
Create a Wallet
{
"tx_type": "multisig_create",
"payload": {
"multisig_signers": ["pubkey_hex_1", "pubkey_hex_2", "pubkey_hex_3"],
"multisig_threshold": 2,
"multisig_label": "Team Treasury"
}
}
- Minimum 2 signers
- Threshold must be ≥ 1 and ≤ number of signers
- Wallet ID is auto-generated if not provided
Submit a Proposal
{
"tx_type": "multisig_submit",
"payload": {
"multisig_wallet_id": "ms-abc123...",
"multisig_proposal_tx_type": "transfer",
"multisig_proposal_payload": {
"to_pub_key_hex": "recipient_pubkey_hex",
"amount": 1000
},
"multisig_proposal_fee": 0.1
}
}
- Only signers of the wallet can submit proposals
- The submitter's signature counts as the first approval
Approve a Proposal
{
"tx_type": "multisig_approve",
"payload": {
"multisig_proposal_id": "mp-def456..."
}
}
- Only signers who haven't already approved can approve
- When threshold is reached, the proposal auto-executes
- For
transferproposals: funds move from the wallet creator's balance
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/multisig/wallets | List all multi-sig wallets |
| GET | /api/multisig/wallet/:id | Get wallet details |
| GET | /api/multisig/wallet/:id/proposals | List proposals for a wallet |
| GET | /api/multisig/wallets/:pubkey | Find wallets where pubkey is a signer |
Response Format
{
"success": true,
"wallet": {
"wallet_id": "ms-abc123...",
"creator": "pubkey_hex",
"signers": ["pubkey1", "pubkey2", "pubkey3"],
"threshold": 2,
"created_at_height": 500,
"label": "Team Treasury"
}
}
SDK
import { RougeChain } from '@rougechain/sdk';
// The constructor takes the API base URL as a string (note the trailing /api)
const rc = new RougeChain('https://api.rougechain.io/api');
// Query multi-sig wallets and proposals (public reads)
const wallets = await rc.get('/multisig/wallets');
const myWallets = await rc.get(`/multisig/wallets/${myPubKey}`);
const wallet_ = await rc.get(`/multisig/wallet/${walletId}`);
const proposals = await rc.get(`/multisig/wallet/${walletId}/proposals`);
Writes are not yet exposed. The
multisig_create/multisig_submit/multisig_approvetransaction types above document the on-chain payload structure, but the node currently serves only the multi-sig read routes — there is no public submit endpoint and no SDK method (rc.sendTransactiondoes not exist). Multi-sig proposal submission is tracked for a future release. See the SDK reference.
MCP Server (AI Agent Integration)
RougeChain is the first blockchain with native MCP (Model Context Protocol) integration. AI agents like Claude, ChatGPT, and custom agents can interact with the blockchain using standardized MCP tools.
What is MCP?
The Model Context Protocol is an open standard for AI agents to interact with external services. RougeChain's MCP server exposes 61 blockchain tools — read the chain and, with a wallet configured, sign and submit real transactions. Every write is signed locally with ML-DSA-65 via @rougechain/sdk; private keys never leave the server process.
Two modes
| Mode | How | What the agent can do |
|---|---|---|
| Read-only (default) | no wallet env | All query tools. Safe to expose anywhere. |
| Read + write | set a wallet env (below) | Everything above plus signed transactions from that wallet. |
Write tools are only registered when a wallet is configured — with no wallet, the transaction tools don't even appear.
Setup
No install needed — the server is published on npm as @rougechain/mcp-server and runs via npx.
Claude Desktop — read-only
Add to ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"rougechain": {
"command": "npx",
"args": ["-y", "@rougechain/mcp-server"],
"env": {
"ROUGECHAIN_URL": "https://api.rougechain.io"
}
}
}
}
Claude Desktop — read + write
Add a wallet to unlock the signing tools:
{
"mcpServers": {
"rougechain": {
"command": "npx",
"args": ["-y", "@rougechain/mcp-server"],
"env": {
"ROUGECHAIN_URL": "https://api.rougechain.io",
"ROUGECHAIN_MNEMONIC": "your twenty four word seed phrase here ..."
}
}
}
}
⚠️ The mnemonic controls real funds. Use a dedicated low-balance agent wallet — generate one with the
generate_wallettool — and keep it out of shared configs. Alternatively supply raw hex keys viaROUGECHAIN_PRIVATE_KEY+ROUGECHAIN_PUBLIC_KEY.
Host note:
ROUGECHAIN_URLmust be the API host (api.rougechain.io), notrougechain.io, which serves the web app for every/api/*path.
Available Tools (61)
Wallet (always available)
| Tool | Description |
|---|---|
generate_wallet | Create a fresh ML-DSA-65 wallet (mnemonic + address); not persisted |
wallet_info | Show the configured signer, address, live balance, and whether writes are enabled |
Read tools (always available)
| Category | Tools |
|---|---|
| Chain | get_chain_stats, get_block, get_latest_blocks |
| Wallet | get_balance, get_transaction |
| Tokens | list_tokens, get_token, get_token_holders |
| DeFi | list_pools, get_swap_quote |
| NFTs | list_nft_collections, get_nft_collection |
| Validators | list_validators |
| Contracts | list_contracts, get_contract, get_contract_state, get_contract_events, deploy_contract, call_contract |
| Social | get_global_timeline, get_post, get_user_posts, get_post_replies, get_track_stats, get_artist_stats |
| Mail & Messaging | resolve_name, reverse_lookup_name, list_messenger_wallets |
| Governance & Fees | list_proposals, get_fee_info |
✍️ Write tools (write mode only — signed with ML-DSA-65)
| Category | Tools |
|---|---|
| Value | send_transaction, burn_tokens, stake, unstake, request_faucet (testnet) |
| Tokens | create_token, mint_tokens, update_token_metadata, claim_token_metadata |
| DeFi | swap, create_pool, add_liquidity, remove_liquidity |
| NFTs | nft_create_collection, nft_mint, nft_batch_mint, nft_transfer, nft_burn, nft_lock, nft_freeze_collection |
| Names | register_name, release_name |
| Social | create_post, delete_post, repost, follow, like_track, comment_on_track |
| Bridge | bridge_withdraw |
Architecture
AI Agent (Claude / GPT / Custom)
↕ stdio (MCP protocol)
RougeChain MCP Server
↕ HTTPS
RougeChain Node API
↕ PQC-signed transactions
RougeChain L1 (ML-DSA + ML-KEM)
All operations maintain post-quantum security guarantees.
Resources
The MCP server also exposes a rougechain://info resource with static context about RougeChain's technology stack, features, and API endpoints.
Browser Extensions
RougeChain provides browser extensions that serve as quantum-safe wallets, similar to how MetaMask works for Ethereum — but using post-quantum cryptography.
Available Extensions
| Extension | Description | Store |
|---|---|---|
| RougeChain Wallet | Primary browser extension | Chrome Web Store |
Features
- Wallet — View balances, send/receive XRGE, claim faucet, custom token support
- Encrypted Messenger — E2E encrypted chat using ML-KEM-768 + ML-DSA-65
- PQC Mail — Encrypted email with
@rouge.quantaddresses - Vault Lock — AES-256-GCM encryption with PBKDF2 key derivation and auto-lock timer
- Cross-browser — Chrome, Edge, Brave, Opera, Arc, Firefox (Manifest V3)
Installation
From Chrome Web Store
- Visit the extension page on the Chrome Web Store
- Click Add to Chrome
- The extension icon appears in your toolbar
From Source
cd browser-extension
npm install
npm run build
- Open
chrome://extensions(oredge://extensions,brave://extensions) - Enable Developer mode
- Click Load unpacked
- Select the
browser-extension/distfolder
Firefox
cd browser-extension
npm install
npm run build
- Open
about:debugging#/runtime/this-firefox - Click Load Temporary Add-on
- Select
browser-extension/dist/manifest.json
DApp Integration
The extension (and the Qwalla mobile app's dApp browser) inject a window.rougechain
provider object, similar to MetaMask's window.ethereum — but the API is
RougeChain-specific, not the EVM/ethers API. In particular there is no
getAddress() and no getNetwork(); you obtain the user's public key by calling
connect().
⚠️ Common mistake: calling
window.rougechain.getAddress()throwse.getAddress is not a function. That method does not exist — RougeChain is not EVM-compatible. Useconnect()(below) and read.publicKey.
Detecting the extension and connecting
// 1. Detect
if (!window.rougechain?.isRougeChain) {
throw new Error("RougeChain wallet not found — install the extension or open in Qwalla.");
}
// 2. Connect (prompts the user, returns their public key)
const { publicKey, displayName, encryptionPublicKey } = await window.rougechain.connect();
console.log("Connected:", publicKey);
The provider may not be injected the instant your script runs. Either check for it
after the rougechain#initialized event, or poll briefly:
function getProvider(timeoutMs = 3000) {
return new Promise((resolve, reject) => {
if (window.rougechain?.isRougeChain) return resolve(window.rougechain);
const onReady = () => resolve(window.rougechain);
window.addEventListener("rougechain#initialized", onReady, { once: true });
setTimeout(() => reject(new Error("RougeChain wallet not detected")), timeoutMs);
});
}
Deriving a rouge1… address
connect() returns the public key (hex). If you need the short Bech32m
rouge1… address, derive it with the SDK:
import { pubkeyToAddress } from "@rougechain/sdk";
const { publicKey } = await window.rougechain.connect();
const address = await pubkeyToAddress(publicKey); // rouge1...
Provider API
| Method | Signature | Description |
|---|---|---|
isRougeChain | boolean | Property — true on the authentic provider. Use it to detect the wallet. |
connect() | → { publicKey, displayName?, encryptionPublicKey? } | Prompt the user to connect. Returns their public key. This replaces getAddress(). |
getBalance() | → { balance, tokens } | Get the connected wallet's XRGE balance and token balances. |
signTransaction(payload) | → { signature, signedPayload } | Sign a transaction payload with ML-DSA-65 (key never leaves the wallet). |
sendTransaction(payload) | → { txId } | Sign and broadcast a transaction. |
on(event, cb) | void | Subscribe to events (e.g. account/connection changes). |
removeListener(event, cb) | void | Remove an event listener. |
Authenticity: the genuine provider also sets a non-enumerable
Symbol.for("rougechain:authentic") to true, so a dApp can guard against a page
that pre-defines a fake window.rougechain:
const authentic = window.rougechain?.[Symbol.for("rougechain:authentic")] === true;
Security
| Feature | Implementation |
|---|---|
| Vault encryption | AES-256-GCM with a PBKDF2-derived key (600k iterations); a password (min 8 chars) is mandatory at wallet creation |
| Auto-lock | Configurable timer via background service worker |
| Key storage | Encrypted blob in chrome.storage.local; the decrypted key is held only in chrome.storage.session (memory) and never written to disk. Legacy plaintext wallets are force-migrated to encrypted storage. |
| Signing | ML-DSA-65 (FIPS 204) — quantum-resistant |
| Encryption | ML-KEM-768 (FIPS 203) — quantum-resistant |
Permissions
The extensions request minimal permissions:
| Permission | Purpose |
|---|---|
storage | Store encrypted wallet data |
alarms | Auto-lock timer |
notifications | Transaction alerts |
| Host permissions | Connect to RougeChain nodes (rougechain.io, testnet.rougechain.io, localhost) |
Why Not MetaMask?
MetaMask and all EVM wallets use secp256k1 / ECDSA cryptography. RougeChain uses ML-DSA-65 / ML-KEM-768 (post-quantum). The key formats, signature schemes, and transaction structures are fundamentally incompatible. RougeChain's extensions are purpose-built for quantum-safe operations.
See PQC Cryptography for details on the algorithms used.
SDK (@rougechain/sdk)
The official JavaScript/TypeScript SDK for building on RougeChain.
Installation
npm install @rougechain/sdk
Quick Start
import { RougeChain, Wallet } from '@rougechain/sdk';
const rc = new RougeChain('https://testnet.rougechain.io/api');
// Generate a new post-quantum wallet (24-word BIP-39 mnemonic + keypair)
const wallet = Wallet.generate();
console.log('Public key:', wallet.publicKey);
console.log('Recovery phrase:', wallet.mnemonic); // save this to restore the wallet
// Request testnet tokens
await rc.faucet(wallet);
// Check balance
const balance = await rc.getBalance(wallet.publicKey);
console.log('Balance:', balance);
// Transfer tokens
await rc.transfer(wallet, { to: recipientPubKey, amount: 100 });
Wallet
All signing happens client-side. Private keys never leave your application.
import { Wallet } from '@rougechain/sdk';
// Generate a new wallet WITH a 24-word BIP-39 recovery phrase (default, recommended)
const wallet = Wallet.generate();
console.log(wallet.mnemonic); // "word1 word2 … word24"
// 12-word phrase instead of 24
const wallet12 = Wallet.generate(128);
// Generate from pure entropy — no mnemonic, cannot be seed-restored
const random = Wallet.generateRandom();
// Restore from a BIP-39 recovery phrase (optionally with a 25th-word passphrase)
const fromSeed = Wallet.fromMnemonic('word1 word2 … word24');
// Restore from saved hex keys
const restored = Wallet.fromKeys(publicKey, privateKey);
// Derive the short Bech32m address (rouge1…) from the public key
const address = await wallet.address();
// Export for storage — includes `mnemonic` when the wallet has one
const keys = wallet.toJSON(); // { publicKey, privateKey, mnemonic? }
// Verify keypair integrity
const valid = wallet.verify(); // true
The mnemonic model is what the MCP server uses via
ROUGECHAIN_MNEMONIC. Store the phrase securely — anyone with it controls the wallet.
Client
import { RougeChain } from '@rougechain/sdk';
// Basic connection
const rc = new RougeChain('https://testnet.rougechain.io/api');
// With API key
const rc = new RougeChain('https://testnet.rougechain.io/api', {
apiKey: 'your-api-key',
});
Available Methods
Queries
await rc.getHealth();
await rc.getStats();
await rc.getBlocks({ limit: 10 });
await rc.getBalance(publicKey);
await rc.getValidators();
await rc.getTokens();
await rc.getBurnedTokens();
Transactions
await rc.transfer(wallet, { to: recipient, amount: 100 });
await rc.transfer(wallet, { to: recipient, amount: 50, token: 'MYTOKEN' });
await rc.createToken(wallet, { name: 'My Token', symbol: 'MTK', totalSupply: 1_000_000, image: 'https://example.com/logo.png' });
await rc.burn(wallet, 500, 1, 'XRGE');
await rc.faucet(wallet);
Staking
await rc.stake(wallet, { amount: 1000 });
await rc.unstake(wallet, { amount: 500 });
DEX (rc.dex)
// Pool queries
await rc.dex.getPools();
await rc.dex.getPool('XRGE-MTK');
await rc.dex.getPriceHistory('XRGE-MTK'); // PriceSnapshot[] — for building charts
await rc.dex.getPoolStats('XRGE-MTK'); // volume, swap counts (total + 24h)
await rc.dex.getPoolEvents('XRGE-MTK'); // swap/add/remove event history
// Quote & swap
await rc.dex.quote({ poolId: 'XRGE-MTK', tokenIn: 'XRGE', tokenOut: 'MTK', amountIn: 100 });
await rc.dex.swap(wallet, { tokenIn: 'XRGE', tokenOut: 'MTK', amountIn: 100, minAmountOut: 95 });
// Liquidity
await rc.dex.createPool(wallet, { tokenA: 'XRGE', tokenB: 'MTK', amountA: 10000, amountB: 5000 });
await rc.dex.addLiquidity(wallet, { poolId: 'XRGE-MTK', amountA: 1000, amountB: 500 });
await rc.dex.removeLiquidity(wallet, { poolId: 'XRGE-MTK', lpAmount: 100 });
NFTs (rc.nft)
await rc.nft.createCollection(wallet, { symbol: 'ART', name: 'My Art', royaltyBps: 500, maxSupply: 10000 });
// Route royalties to a specific wallet (defaults to the creator). Never use a contract address — funds would be lost.
await rc.nft.createCollection(wallet, { symbol: 'ART2', name: 'Split Art', royaltyBps: 1000, royaltyRecipient: payoutWalletPubKey });
await rc.nft.mint(wallet, { collectionId: 'abc123', name: 'Piece #1', metadataUri: '...' });
await rc.nft.batchMint(wallet, { collectionId: 'abc123', names: ['#1', '#2'], uris: ['...', '...'] });
await rc.nft.transfer(wallet, { collectionId: 'abc123', tokenId: 1, to: buyerPubKey, salePrice: 100 });
await rc.nft.getCollections();
await rc.nft.getByOwner(wallet.publicKey);
Bridge (rc.bridge)
await rc.bridge.getConfig();
await rc.bridge.withdraw(wallet, { amount: 500000, evmAddress: '0x...' });
await rc.bridge.claim({ evmTxHash: '0x...', evmAddress: '0x...', evmSignature: '0x...', recipientPubkey: wallet.publicKey });
await rc.bridge.getWithdrawals();
Mail Name Registry (rc.mail)
Register human-readable names and resolve recipients before sending encrypted mail. Third-party apps must call these for cross-app mail to work.
All write operations require a wallet parameter for ML-DSA-65 signed requests via /api/v2/ endpoints with anti-replay nonce protection.
// Register wallet on the node first (signed request)
await rc.messenger.registerWallet(wallet, {
id: wallet.publicKey,
displayName: "Alice",
signingPublicKey: wallet.publicKey,
encryptionPublicKey: encPubKey,
});
// Register a mail name (signed request)
await rc.mail.registerName(wallet, "alice", wallet.publicKey);
// Resolve a name → wallet info (public, no signing needed)
const resolved = await rc.mail.resolveName("bob");
// { entry: { name, wallet_id }, wallet: { id, encryption_public_key, ... } }
// Reverse lookup: wallet ID → name (public, no signing needed)
const name = await rc.mail.reverseLookup(wallet.publicKey); // "alice"
// Release a name (signed request)
await rc.mail.releaseName(wallet, "alice");
// Send mail (signed request, multi-recipient CEK encryption)
await rc.mail.send(wallet, { from, to, subject, body, encrypted_subject, encrypted_body });
// Read inbox / sent (signed requests)
const inbox = await rc.mail.getInbox(wallet);
const sent = await rc.mail.getSent(wallet);
// Manage mail (signed requests)
await rc.mail.markRead(wallet, messageId);
await rc.mail.move(wallet, messageId, "trash");
await rc.mail.delete(wallet, messageId);
Messenger (rc.messenger)
All operations use ML-DSA-65 signed requests with nonce-based anti-replay protection.
await rc.messenger.getWallets();
await rc.messenger.registerWallet(wallet, { id, displayName, signingPublicKey, encryptionPublicKey });
await rc.messenger.getConversations(wallet);
await rc.messenger.createConversation(wallet, [pubKeyA, pubKeyB]);
await rc.messenger.getMessages(wallet, conversationId);
await rc.messenger.sendMessage(wallet, conversationId, encryptedContent, { selfDestruct: true, destructAfterSeconds: 30 });
await rc.messenger.deleteMessage(wallet, messageId, conversationId);
await rc.messenger.deleteConversation(wallet, conversationId);
Social (rc.social)
On-chain posts, replies, reposts, follows, likes, and track comments. Writes are ML-DSA-65 signed; reads are public.
// Reads
await rc.social.getGlobalTimeline(50, 0); // newest-first feed
await rc.social.getPost(postId, viewerPubKey); // single post + stats
await rc.social.getUserPosts(pubKey);
await rc.social.getPostReplies(postId);
await rc.social.getTrackStats(trackId, viewerPubKey);
await rc.social.getArtistStats(artistPubKey, viewerPubKey);
// Writes (signed)
await rc.social.createPost(wallet, 'gm from RougeChain');
await rc.social.createPost(wallet, 'nice!', parentPostId); // reply
await rc.social.deletePost(wallet, postId);
await rc.social.toggleRepost(wallet, postId);
await rc.social.toggleFollow(wallet, artistPubKey);
await rc.social.toggleLike(wallet, trackId);
await rc.social.postComment(wallet, trackId, 'great track');
Push Notifications
// Register for push notifications (PQC-signed) — pass the wallet, not raw keys
await rc.registerPushToken(wallet, 'ExponentPushToken[xxx]'); // platform defaults to "expo"
await rc.registerPushToken(wallet, 'ExponentPushToken[xxx]', 'expo');
// Unregister
await rc.unregisterPushToken(wallet);
Address Resolution
// Resolve rouge1… address to public key, or vice versa
const result = await rc.resolveAddress('rouge1q8f3x...');
// → { address, publicKey, balance }
Account Nonce
const nonce = await rc.getNonce(publicKey);
// → { nonce, next_nonce }
Token Allowances (ERC-20 style approve / transferFrom)
There are no rc.approveAllowance / rc.transferFrom convenience methods. Build the
signed transaction with the exported signer helpers, then submit it with
rc.submitTx(endpoint, signedTx):
import {
createSignedTokenApproval,
createSignedTokenTransferFrom,
} from '@rougechain/sdk';
// Approve a spender to move up to `amount` of your MTK
await rc.submitTx(
'/v2/token/approve',
createSignedTokenApproval(wallet, spenderPubKey, 'MTK', 1000),
);
// Spend an existing allowance on the owner's behalf
await rc.submitTx(
'/v2/token/transfer-from',
createSignedTokenTransferFrom(wallet, ownerPubKey, recipientPubKey, 'MTK', 1000),
);
// Read current allowances (public)
await rc.get('/token/allowances');
await rc.get('/token/allowance');
Freeze/unfreeze a token you created is a creator-only signed request to
/api/v2/token/freeze(payload{ tokenSymbol, frozen }). There is no dedicated SDK helper — sign it with the genericsignRequest(wallet, payload)and post it.
Multi-Sig Wallets
Multi-sig queries are available today:
const wallets = await rc.get('/multisig/wallets');
const myWallets = await rc.get(`/multisig/wallets/${myPubKey}`);
const wallet_ = await rc.get(`/multisig/wallet/${walletId}`);
const proposals = await rc.get(`/multisig/wallet/${walletId}/proposals`);
Note: creating a multi-sig wallet and submitting/approving proposals (
multisig_create/multisig_submit/multisig_approve) are on-chain transaction types, but they are not yet exposed through a stable SDK method or public submit endpoint — the node currently serves the multi-sig read routes above only. Do not callrc.sendTransaction(...); that method does not exist. Multi-sig write support is tracked for a future release.
Smart Contracts (rc.shielded)
Contract helpers are accessed via the rc.shielded sub-client:
// Deploy WASM contract
const deploy = await rc.shielded.deployContract({
wasm: base64WasmBytes,
deployer: wallet.publicKey,
});
// Call contract method
const result = await rc.shielded.callContract({
contractAddr: deploy.address,
method: 'increment',
caller: wallet.publicKey,
args: { key: 'value' },
gasLimit: 10_000_000,
});
// Query contract
const meta = await rc.shielded.getContract(deploy.address);
const state = await rc.shielded.getContractState(deploy.address);
const events = await rc.shielded.getContractEvents(deploy.address);
EIP-1559 Fee Info
const feeInfo = await rc.getFeeInfo();
// → { base_fee, total_fee_suggestion, total_fees_burned, block_height }
Environment Support
| Environment | Requirements |
|---|---|
| Browser | Any bundler (Vite, webpack) |
| Node.js 18+ | Works out of the box |
| Node.js < 18 | Provide node-fetch polyfill |
| React Native | Install react-native-get-random-values |
TypeScript
Full type declarations are included:
import type {
Block, Transaction, Validator, LiquidityPool, NftCollection,
NameEntry, ResolvedName, MailMessage, SendMailParams,
PriceSnapshot, PoolStats, SwapQuote,
} from '@rougechain/sdk';
Security
- All signatures use ML-DSA-65 (FIPS 204), resistant to quantum attacks
- Private keys never leave your application
- The SDK does not store keys — persistence is your responsibility
- All mail, messenger, and name registry operations use ML-DSA-65 signed requests with timestamp validation and nonce-based anti-replay protection
- Mail uses a CEK pattern for efficient multi-recipient encryption via ML-KEM-768
- TOFU key fingerprint tracking for contact key-change detection
Source
The SDK source code is in the sdk/ directory of the quantum-vault repository.
Troubleshooting
Common issues and how to fix them.
Node Issues
Node won't start
"Address already in use"
Another process is using the port. Find it and stop it, or use a different port:
# Linux/macOS
lsof -i :5100
kill -9 <PID>
# Windows (PowerShell)
Get-Process -Id (Get-NetTCPConnection -LocalPort 5100).OwningProcess
Stop-Process -Id <PID>
# Or just use a different port
./quantum-vault-daemon --api-port 5102
"cargo not found"
Rust isn't in your PATH. Fix with:
source ~/.cargo/env
# Or restart your terminal
"OpenSSL not found" (build error)
Install OpenSSL development headers:
# Ubuntu/Debian
sudo apt install libssl-dev pkg-config
# Fedora/RHEL
sudo dnf install openssl-devel
# macOS
brew install openssl
# Windows — install Visual Studio Build Tools with C++ workload
Node won't sync
| Check | How |
|---|---|
| Node is running | curl http://127.0.0.1:5100/api/health |
| Peers are correct | Make sure --peers includes /api (e.g. mainnet --peers "https://api.rougechain.io/api") |
| Genesis + chain-id match the network | Mainnet needs --genesis daemon/genesis-mainnet.json --chain-id rougechain-mainnet-1. A wrong/missing genesis or chain-id makes a fresh node fail to join — its /api/health chain_id must read rougechain-mainnet-1 |
| Firewall isn't blocking | Ensure your --api-port / P2P port reachability is correct |
| Network is reachable | mainnet curl https://api.rougechain.io/api/health (or testnet https://testnet.rougechain.io/api/health) |
Peers value of 0
Your node isn't connected to anyone. Check:
- You passed
--peerswith the correct URL (including/api) - Your internet connection is working
- The testnet node is online:
curl https://testnet.rougechain.io/api/stats
Height stuck at 0
If your node's chain height isn't advancing:
- Ensure
--peersis set (solo nodes without peers don't receive blocks) - If you're mining solo (
--minewithout--peers), blocks are only produced locally - Check logs for sync errors
Blocks not propagating
If you're mining but other nodes don't see your blocks:
- Set
--public-url— Without this, your node is invisible to the network. Other nodes can't sync from you. - Check your firewall — Your API port must be reachable from the internet
- Verify with peers API:
curl https://testnet.rougechain.io/api/peers # Your node's URL should appear in the list
Transaction Issues
"Insufficient balance"
Transaction amount + fee must be less than your balance. The fee is 0.1 XRGE per transfer.
Required: amount + 0.1 XRGE
Use the faucet to get more tokens:
- Website: Go to the Wallet page and click "Request Faucet"
- API:
POST /api/v2/faucet
"Transaction rejected"
Common causes:
| Cause | Fix |
|---|---|
| Wrong signature | Ensure you're signing with the correct private key |
| Duplicate transaction | Wait a moment and retry — the previous tx may still be processing |
| Node not synced | Check GET /api/health — your node's height should match the network |
| Stale nonce | Refresh your wallet state and retry |
"Failed to fetch" in the web app
- Check you're connected to the right network (Testnet vs local Devnet)
- If using local devnet, make sure the daemon is running
- Check browser console (
F12) for the actual error - Verify CORS: a self-hosted node only allows a built-in set of origins by default — set
QV_CORS_ORIGINSor run with--dev(see Configuration → Browser access). The publictestnet/api.rougechain.ioendpoints already sendAccess-Control-Allow-Origin: *, so if you're hitting those, CORS is not the cause.
Wallet Issues
Wallet not loading
- Clear browser cache and
localStorage - Check the browser console for errors (
F12→ Console) - Try a different browser
- If using the extension, check it's enabled and not suspended
Lost private key
There is no recovery mechanism. Private keys are stored locally in your browser. If you clear browser data, the keys are gone.
Best practice: Always export and safely store your keys after creating a wallet.
Extension not connecting
- Check the extension is enabled in your browser
- Ensure you're on a supported page (the extension injects on pages that need it)
- Try disabling and re-enabling the extension
- Check if another wallet extension is conflicting
Staking Issues
Can't stake — "insufficient balance"
You need at least 10,000 XRGE plus the transaction fee — the 10,000 XRGE minimum is enforced on every stake call. Use the faucet if needed.
Staked but not producing blocks
- Most common cause: your node's key is not your staked key. Your node signs
blocks with the keypair in
<data-dir>/node-keys.json, and peers reject blocks whose proposer isn't a staked validator. If you staked from one key but the node runs with a different (auto-generated) key, your blocks are silently rejected. The node'snode-keys.jsonkey and your staked key must be the same key — see Becoming a Validator. - Ensure the
--mineflag is set on your node - Your node must be synced (height matches the network)
- Validator selection is stake-weighted — with minimum stake, you'll produce blocks less frequently
- Check your validator status:
GET /api/validators— your node's public key should appear with active stake
Unstaked but balance not returned
After unstaking, the tokens are returned to your wallet address. Check your balance:
curl "https://testnet.rougechain.io/api/balance/YOUR_PUBLIC_KEY"
Bridge Issues
Bridge deposit not credited
- Confirm the EVM transaction was confirmed on Base Sepolia
- Wait up to 2 minutes — the bridge relayer polls periodically
- Check the bridge config:
GET /api/bridge/config - Verify the custody address matches your bridge target
Withdrawal pending
Withdrawals require the bridge relayer to process them. Check status:
curl "https://testnet.rougechain.io/api/bridge/withdrawals"
DEX Issues
Swap failed — "slippage exceeded"
The price moved between your quote and execution. Increase your slippage tolerance or retry.
Pool creation failed
- Both tokens must exist on the network
- You need sufficient balance of both tokens
- Pool creation fee is 10 XRGE
Platform-Specific
Windows: Build fails
- Install Visual Studio Build Tools with the C++ desktop workload
- Install Rust — make sure
cargois in your PATH - Restart your terminal after installation
Windows: Permission denied
Run PowerShell as Administrator, or check that your antivirus isn't blocking the daemon.
macOS: "developer cannot be verified"
xattr -d com.apple.quarantine ./target/release/quantum-vault-daemon
Still Stuck?
- Check the GitHub Issues
- Visit the built-in node dashboard at
http://localhost:5100for live diagnostics - Review daemon logs (stderr output) for detailed error messages
Changelog
All notable changes to RougeChain.
Security Hardening — 2026-08-19
Security Hardening
- On-chain bridge verification — The bridge now verifies the actual on-chain Base deposit instead of trusting caller-supplied messages. An XRGE claim requires an EVM signature and derives the credited amount from the on-chain
Transferlog. The qETH EntryPoint/balance-delta claim path was removed in favor of a direct-to-custody, recipient-bound ECDSA claim only - Signature replay guard — Signed transactions are now protected by a process-global replay guard keyed by
sha256(signature)with a 5-minute window, closing the in-window replay hole left by the timestamp check alone - Bridge confirmation depth & limits — Default bridge confirmation depth raised to 6; added a withdraw kill-switch and a per-transaction withdrawal cap
- Token freeze authorization — Token freeze now cryptographically verifies the request signature and requires
signer == creator - Wallet keys encrypted at rest — Wallet keys are encrypted with AES-256-GCM (PBKDF2, 600k iterations) behind a mandatory password (min 8 chars); the decrypted key is kept only in memory and never written to disk, and legacy plaintext wallets are force-migrated
Testnet v0.2.4 — March 2026
Added
- Social layer — On-chain social features with plays, likes, comments, follows, and tips. Data is stored server-side in sled with ML-DSA-65 signed writes; tips settle on-chain via
rc.transfer() - Standalone posts — Create, delete, and fetch posts (max 4000 chars) with threaded replies via
replyToId. Global timeline and personalized following feed endpoints - Reposts — Toggle repost on any post; repost counts aggregated per post with viewer state
- Post stats — Aggregate endpoint returns likes, reposts, reply count, and viewer's liked/reposted state for any post
- Following feed — Authenticated endpoint returns posts from users the viewer follows, sorted newest-first
- qRougee social integration — TrackDetail shows play counts, like button, tip modal, and comments. TrackCard badges show plays/likes. ArtistProfile shows followers and follow button. Library includes a "Liked" tab. Home page sorts discovery by popularity
- SDK v1.0.0 —
rc.socialnamespace with 19 methods:createPost,deletePost,toggleRepost,getPost,getPostStats,getPostReplies,getUserPosts,getGlobalTimeline,getFollowingFeed, plus existing play/like/comment/follow methods - WASM STARK prover — Browser-side STARK proof generation via
core/wasm-prover/compiled to WebAssembly. Unshield and shielded transfer operations now generate real winterfell STARK proofs client-side without relying on a trusted server - Groq-powered Quantum Bot — Messenger AI bot proxied through the node using Groq's
llama-3.1-8b-instantmodel with a comprehensive RougeChain knowledge base - Mail & messenger unread badges — Browser extension and QWalla app show unread count badges on both Chat and Mail tabs with hover tooltips (e.g. "3 unread emails"). The browser extension icon badge displays the combined unread total
- Native browser notifications — Browser extension fires system notifications for new messages, new mail, received/sent tokens, contract deployments, staking events, and balance changes via WebSocket and periodic polling
- Mail reply pre-fill — Clicking "Reply" in the browser extension auto-populates the recipient, subject (with "Re:"), and quoted original message body
- Mail attachments (extension) — Browser extension mail compose and read views now support file attachments with upload, preview, and download, matching the website's feature set
- Initial unread count polling (QWalla) — App fetches actual unread chat and mail counts from the server on launch so tab badges are accurate immediately, not just after a real-time event arrives
Fixed
- Custom token transfers —
v2_transferhandler now correctly setstoken_symbolonTxPayload(previously only settoken_name), which the balance engine actually reads. Without this, all custom token transfers silently moved XRGE instead of the selected token - Browser extension token send — Added token picker dropdown to the send form so users can send any held token, not just XRGE. Upgraded from legacy v1
/tx/submit(which sent the private key to the server) to v2 signed endpoint with client-side ML-DSA-65 signing - Browser extension import token — Added MetaMask-style "Import Custom Token" flow: enter a symbol, validates on-chain, persists to tracked list. Replaced the "All Tokens on Chain" dump with a curated held + imported view
- Qwalla custom token balance validation — Send screen now fetches and validates the correct token balance when sending a custom token, instead of always checking XRGE balance
- Website send dialog pre-selection — Clicking "Send" from a token's detail view now pre-selects that token instead of always defaulting to XRGE
- Swap pre-fill from Trade button — "Trade on this Pool" now passes
tokenIn/tokenOutURL params to the swap page, correctly pre-filling both sides of the swap - PWA wallet persistence — Wallet private keys now persist in
localStoragewhen no vault password is set, preventing wallet loss on PWA/tab restart. Password-protected vaults continue to use encrypted storage only - Browser extension key regeneration — Removed aggressive version-based key regeneration that was replacing existing valid keys and causing loss of on-chain identity (faucet funds)
- Quantum Bot registration — Bot wallets use unique per-browser IDs and register as non-discoverable to prevent display name conflicts
- Browser extension messenger — All 8 messenger endpoints migrated from legacy unsigned v1 to ML-DSA-65 signed v2 endpoints, fixing "Registration failed" errors in production
- Browser extension mail decryption — Ported v2 multi-recipient CEK encryption/decryption to the extension, fixing "[Unable to decrypt]" on mail sent from the website
- QWalla mail encryption — Added v2 CEK encryption/decryption for cross-client mail compatibility with the website and browser extension
- QWalla message signing — Messages now include ML-DSA-65 content signatures; fixed
ml_dsa65.signargument order that caused "secretKey expected Uint8Array of length 4032" errors - Message signature display — Three-state indicator: green check (verified), red X (failed), grey shield (no signature) — prevents false negatives on unsigned legacy messages
- Shielded note badge — Browser extension wallet tab now shows shielded notes for the current wallet only (per-wallet
getActiveNotes) instead of the global chain stat - Unread badge persistence — Badge clears correctly after viewing messages;
lastKnownUnreadpersisted tochrome.storage.localto survive service worker restarts - QWalla
@qwalla.maildomain — Mail addresses now display as@qwalla.mailthroughout the app instead of@rouge.quant - QWalla mail name resolution — Mail list and detail views resolve and display registered names instead of raw wallet IDs
Changed
- Validator economics — Base fee burn reduced from 100% to 50%; remaining 50% flows into the tip pool for validator rewards. A 0.1 XRGE/block minimum tip floor is guaranteed from the staking reserves allocation
- Minimum stake — Enforced at 10,000 XRGE (previously unenforced). Staking requests below this threshold are rejected
- Entropy prefetch — ANU QRNG entropy is now fetched in a background thread and cached, eliminating per-block blocking HTTP calls that could stall block production for up to 20 seconds
- Service worker cache bumped to
rougechain-v2to invalidate stale assets on existing PWA installs - Session-only private keys policy updated:
localStorageused for unprotected wallets,sessionStorage-only when vault passphrase is configured
Testnet v0.2.3 — March 2026
Security Hardening
- Signed API requests — All 16 mail, messenger, and name registry endpoints now require ML-DSA-65 signed requests via
/api/v2/routes. Legacy unsigned endpoints return HTTP 410 (Gone) - Anti-replay nonces — Each signed request includes a cryptographically random nonce; duplicates within the timestamp window are rejected server-side
- Multi-recipient CEK encryption — Mail content encrypted once with a random AES-256 CEK, KEM-wrapped individually per recipient via ML-KEM-768
- Unified mail signatures — Single ML-DSA-65 signature over concatenation of all encrypted parts (subject + body + attachment) prevents partial content substitution
- TOFU key verification — Public key fingerprints (SHA-256) tracked on first use with key-change warnings displayed in messenger UI
- Atomic name registration — Name registry uses sled compare-and-swap (CAS) to prevent TOCTOU race conditions during name claims
- Sled messenger storage — Messenger data migrated from JSON file to sled embedded database with per-record atomic operations and automatic migration from legacy format
- Server-side input validation — Length limits enforced on all fields (display names: 50 chars, message content: 2 MB, mail subject: 10 KB, mail body: 512 KB, attachments: 3 MB, max 50 recipients)
- Session-only private keys — Web app stores private keys in
sessionStorage(cleared on tab close) instead oflocalStorage; encrypted wallet blob persists inlocalStorage(superseded in v0.2.4: localStorage used when no vault password is set for PWA persistence) - Legacy decryption removal — Pre-v2 mail and messenger decryption fallbacks removed to reduce attack surface
Changed
- Messenger, mail, and name registry SDK methods now require a
walletparameter for request signing WHITEPAPER.mdupdated to v1.7 with full security hardening documentation
Testnet v0.2.2 — March 2026
Added
- SDK v0.8.4 — Name registry methods:
rc.mail.registerName(),rc.mail.resolveName(),rc.mail.reverseLookup(),rc.mail.releaseName() - SDK types —
NameEntry,ResolvedNameexported for TypeScript consumers - Browser Extension — BIP-39 seed phrase support: generate, view, and import 24-word mnemonic phrases
Fixed
- API docs corrected: name registry endpoints now show actual routes (
/names/resolve/:name,/names/reverse/:walletId) instead of non-existent query-param URLs - Blockchain explorer chain validation no longer fails on descending block order from API
- Tamper detection demo works correctly with real blocks from the API
- SDK
SwapQuoteParamsnow includes requiredtokenOutfield
Testnet v0.2.1 — March 2026
Added
- Mail Attachments — Encrypted file attachments (up to 2 MB) via ML-KEM-768
- Push Notifications — PQC-signed Expo push token registration (
/api/push/register) - Address Resolution — Convert between
rouge1…and hex via/api/resolve/:input - Account Nonce API —
GET /api/account/:pubkey/noncefor replay protection - SDK v0.8.2 —
registerPushToken(),unregisterPushToken(),resolveAddress(),getNonce()
Fixed
- Auto-migration of stale timestamp-based nonces to sequential nonces on node startup
Testnet v0.2.0 — March 2026
Added
- PQC Mail — Encrypted email with
@rouge.quantaddresses, threading, and folder management - RC-721 NFTs — Collections, batch minting, royalties, transferring, burning, and freezing
- AMM/DEX — Uniswap V2-style liquidity pools, swaps, and price charts
- XRGE Bridge — Bidirectional bridge for XRGE between RougeChain and Base (ERC-20)
- Shielded Transactions — Private transfers using STARK proofs with on-chain nullifiers
- Token Staking Pools — Stake custom tokens with configurable reward rates
- Governance — On-chain proposal creation and weighted voting
- Token Locking — Time-locked and vesting token locks
- Token Allowances — Approve and spend-from delegation
- ZK Rollup (Phase 3) — Batch transaction accumulation with proof submission
- Tiered Rate Limiting — Validators and peers get separate rate limit tiers
- Network Globe — 3D visualization of connected nodes on the blockchain page
- SDK —
@rougechain/sdknpm package for building dApps - Docker Support — One-command node deployment with
docker run - Node Dashboard — Built-in web dashboard at
http://localhost:5100when running a node - Name Registry — Register human-readable names for wallets (
alice@rouge.quant) - Browser Extensions — Chrome/Firefox wallet extensions with vault lock
Changed
- Default block time reduced from 1000ms to 400ms
--peersURL now requires/apisuffix (e.g.,https://testnet.rougechain.io/api)- v1 endpoints that accept private keys are disabled by default (use
--devto enable) - Secure v2 API with client-side signing is the default for all write operations
Security
- All signatures use ML-DSA-65 (FIPS 204)
- All key encapsulation uses ML-KEM-768 (FIPS 203)
- Client-side signing — private keys never leave your browser
- Validator-proven rate limiting with PQC signature verification
Testnet v0.1.0 — February 2026
Added
- Core blockchain — Proof of Stake L1 with PQC cryptography
- Wallet — Generate ML-DSA-65 keypairs, send/receive XRGE
- Faucet — Request Testnet tokens
- Validator staking — Stake XRGE to become a block proposer
- Encrypted Messenger — E2E encrypted messaging with ML-KEM-768 and AES-GCM
- Self-destruct messages — Messages that auto-delete after being read
- Token creation — Create custom tokens with metadata
- Token burning — Official burn address with on-chain tracking
- P2P networking — Peer discovery, block propagation, and automatic sync
- ETH Bridge (qETH) — Bridge ETH from Base Sepolia with 6-decimal precision
- Block Explorer — Browse blocks, transactions, and addresses
- gRPC API — Chain, wallet, validator, and messenger services
- REST API — Full HTTP API for all blockchain operations