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.0.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
| Network | API Endpoint |
|---|---|
| Testnet | https://testnet.rougechain.io/api |
| Devnet (local) | http://127.0.0.1:5100/api |
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. Fees go to the block proposer. |
| 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). The base fee portion is burned, and only the priority tip goes to validators. 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.70+)
Quickest Path
- Visit rougechain.io
- Click "Wallet" in the sidebar
- Your wallet is automatically created and stored locally
- 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
Your wallet is created automatically when you first visit. 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
Keys are stored locally in your browser.
Step 3: Get Test Tokens
- Click Wallet in the sidebar
- Click Request from Faucet
- Receive 1,000 XRGE instantly
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
Your RougeChain wallet is automatically created when you first visit the app. 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 (qRougee) | 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
| Platform | Storage |
|---|---|
| Web (rougechain.io) | localStorage → encrypted via vault password |
| Browser Extension (qRougee) | chrome.storage.local → AES-256-GCM vault |
| Mobile (Qwalla) | expo-secure-store → device keychain |
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 1,000 XRGE instantly
Using the API
curl -X POST https://testnet.rougechain.io/api/faucet \
-H "Content-Type: application/json" \
-d '{"publicKey": "your-public-key-here"}'
Response:
{
"success": true,
"amount": 1000,
"txId": "abc123..."
}
Rate Limits
| Condition | Limit |
|---|---|
| Per address | 1 request / hour |
| Per IP | 10 requests / hour |
| Whitelisted | Unlimited |
Whitelisting
For development or testing, addresses can be whitelisted:
# Start node with whitelist
./quantum-vault-daemon --mine \
--faucet-whitelist "pubkey1,pubkey2,pubkey3"
# Or via environment variable
export QV_FAUCET_WHITELIST="pubkey1,pubkey2"
./quantum-vault-daemon --mine
Troubleshooting
"Rate limited"
Wait an hour 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 participate in the network, validate transactions, and earn rewards.
The node software is open source under the Apache 2.0 license.
Node Types
| Type | Description | Requires |
|---|---|---|
| Full Node | Syncs and validates all blocks | --peers |
| Mining Node | Produces new blocks | --mine |
| Public Node | Accepts external connections | --host 0.0.0.0 |
Quick Start
# Clone the public repo
git clone https://github.com/cyberdreadx/rougechain-node
cd rougechain-node/core
# Build the daemon
cargo build --release -p quantum-vault-daemon
# Run a syncing node
./target/release/quantum-vault-daemon \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
# Run a mining node
./target/release/quantum-vault-daemon \
--mine \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api"
Verify It's Working
curl http://127.0.0.1:5100/api/health
Expected response:
{
"status": "ok",
"chain_id": "rougechain-devnet-1",
"height": 123
}
Next Steps
- Installation - Detailed setup instructions
- Configuration - All CLI options
- Mining - Block production
Installation
System Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2+ vCPU |
| RAM | 512 MB | 1–2 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 \
--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
cd rougechain-node
git pull
cd core
cargo build --release -p quantum-vault-daemon
# Restart the node
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, 512 MB 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 |
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 1,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 1,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://testnet.rougechain.io,https://node2.example.com,https://node3.example.com"
Or via environment variable:
export QV_PEERS="https://testnet.rougechain.io,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 Configuration
If running behind a firewall, ensure the API port is accessible:
| Port | Protocol | Purpose |
|---|---|---|
| 5100 (default) | TCP/HTTP | REST API and P2P |
# Linux (ufw)
sudo ufw allow 5100/tcp
# Linux (firewalld)
sudo firewall-cmd --add-port=5100/tcp --permanent
sudo firewall-cmd --reload
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://testnet.rougechain.io,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 |
| Unbonding period | ~7 days |
| Slashing | Not implemented (testnet) |
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, 50% to block proposer |
| Priority fee (tip) | 100% to block proposer |
| Minimum tip | 0.1 XRGE per block |
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 produce blocks and earn transaction fees on RougeChain. This guide walks you through the full process.
Prerequisites
| Requirement | Details |
|---|---|
| XRGE balance | At least 10,000 XRGE (+ fees) |
| Wallet | A RougeChain wallet with signing keys |
| Node (optional) | Running a node earns you more blocks |
Step 1: Get XRGE
If you're on testnet, use the faucet:
- Visit rougechain.io
- Go to Wallet and click Request from Faucet
- Repeat until you have at least 10,000 XRGE
Step 2: Stake Tokens
Via Web UI
- Navigate to the Validators page
- Click Stake
- Enter your stake amount (minimum 10,000 XRGE)
- Confirm the transaction
- Your wallet signs the stake transaction with ML-DSA-65
Via v2 API (Client-Side Signing)
curl -X POST https://testnet.rougechain.io/api/v2/stake \
-H "Content-Type: application/json" \
-d '{
"publicKey": "your-public-key-hex",
"amount": 1000,
"nonce": 1706745600000,
"signature": "your-ml-dsa65-signature-hex"
}'
Step 3: Verify Your Validator Status
curl "https://testnet.rougechain.io/api/validators"
Look for your public key in the response:
{
"validators": [
{
"publicKey": "your-public-key",
"stake": 1000.0,
"status": "active",
"blocksProposed": 0
}
]
}
Step 4: Run a Mining Node (Recommended)
While staking alone makes you a validator, running a node ensures you're online to produce blocks when selected:
./quantum-vault-daemon \
--mine \
--api-port 5100 \
--peers "https://testnet.rougechain.io/api" \
--public-url "https://mynode.example.com"
Increasing Your Stake
You can add more XRGE to increase your block proposal probability:
# Stake additional 500 XRGE
curl -X POST https://testnet.rougechain.io/api/v2/stake \
-H "Content-Type: application/json" \
-d '{
"publicKey": "your-public-key-hex",
"amount": 500,
"nonce": 1706745600001,
"signature": "your-signature-hex"
}'
Your total stake accumulates.
Validator Selection Algorithm
Proposer selection uses three factors:
- Stake weight — Higher stake gives proportionally higher probability
- Quantum entropy — Sourced from ANU QRNG (quantum vacuum fluctuations); falls back to local CSPRNG if unavailable
- Block context — Previous block hash and height are mixed into the seed for deterministic verifiability
This means even validators with the minimum stake will produce blocks, just less frequently. The entropy source ("quantum" or "local") is visible on the validator dashboard.
Leaving the Validator Set
See Unstaking — after unstaking below the minimum, you're removed from the active validator set.
Staking Rewards
Validators earn rewards for producing blocks on RougeChain.
Reward Sources
| Source | Description |
|---|---|
| Transaction fees | All fees from transactions in your block |
| 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
- All transaction fees in that block go to the proposer
- 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 |
Validators earn the flat fees (transfer, token creation, pool creation, stake/unstake). Swap fees go to liquidity providers.
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% |
| Blocks per day | ~86,400 (1s block time) |
| Your blocks per day | ~8,640 |
| Avg fee per block | 0.5 XRGE |
| Daily earnings | ~4,320 XRGE |
These are approximate — actual returns vary with network conditions.
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 Sepolia (EVM) and the RougeChain L1 network. The bridge uses a lock-and-mint / burn-and-release model with a dedicated smart contract.
Supported Assets
| EVM Asset | RougeChain Asset | Decimals | Direction |
|---|---|---|---|
| ETH | qETH | 6 (L1 units) | Both ways |
| USDC | qUSDC | 6 | Both ways |
| XRGE | XRGE | 18 (EVM) / whole units (L1) | Both ways |
How It Works
Deposit (EVM → RougeChain)
- User deposits ETH, USDC, or XRGE into the RougeBridge smart contract on Base Sepolia
- User calls the claim endpoint on RougeChain with the EVM transaction hash
- The node verifies the deposit on-chain and mints the wrapped token (qETH, qUSDC, or XRGE) on L1
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
- The bridge relayer polls for pending withdrawals and releases the corresponding asset from the contract on EVM
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
- EVM signature verification — ETH claims require an EVM
personal_signto prove deposit ownership
Architecture
Base Sepolia (EVM) RougeChain L1
┌──────────────────┐ ┌──────────────────┐
│ RougeBridge.sol │ │ Node Daemon │
│ - depositETH() │──relayer──│ - /bridge/claim │
│ - depositERC20()│ │ - /bridge/withdraw│
│ - releaseETH() │◀─relayer──│ - withdraw store │
│ - releaseERC20()│ │ │
│ BridgeVault.sol │ │ │
│ - deposit() │──relayer──│ - /bridge/xrge/* │
│ - release() │◀─relayer──│ │
└──────────────────┘ └──────────────────┘
ETH Bridge (qETH)
Bridge ETH from Base Sepolia 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 Sepolia and send ETH to the custody/bridge contract address shown on the Bridge page.
Step 2: Claim qETH
- Go to the Bridge page and select the Bridge In tab
- Paste the EVM transaction hash
- Sign the claim message with your EVM wallet (proves you made the deposit)
- Click Claim
The node verifies:
- The transaction exists on Base Sepolia
- It was sent to the correct custody address
- The EVM signature matches the sender
- The transaction has sufficient confirmations
- It hasn't been claimed before
On success, qETH is minted to your RougeChain wallet.
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 Sepolia 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
A 0.1 XRGE fee is charged for the withdrawal transaction.
Fees
| Operation | Fee |
|---|---|
| Deposit (ETH → qETH) | Gas on Base Sepolia only |
| Withdraw (qETH → ETH) | 0.1 XRGE |
USDC Bridge (qUSDC)
Bridge USDC from Base Sepolia to RougeChain as qUSDC, and back.
Overview
qUSDC is a wrapped representation of USDC on RougeChain. It maintains a 1:1 peg with USDC locked in the RougeBridge contract on Base Sepolia.
USDC Contract on Base Sepolia: 0x036CbD53842c5426634e7929541eC2318f3dCF7e
Deposit (USDC → qUSDC)
- Approve the RougeBridge contract to spend your USDC
- Call
depositERC20(usdc_address, amount, rougechainPubkey)on the contract - Go to the Bridge page, select USDC as the token, switch to Bridge In
- Paste the EVM transaction hash and claim
The node parses the ERC-20 Transfer event from the transaction receipt to determine the amount. USDC uses 6 decimals, and qUSDC uses the same 6-decimal precision.
Withdraw (qUSDC → USDC)
- Go to Bridge page, select USDC, switch to Bridge Out
- Enter amount and destination EVM address
- Click Bridge Out qUSDC
The relayer calls releaseERC20() on the RougeBridge contract to send USDC back to your EVM address.
Fees
| Operation | Fee |
|---|---|
| Deposit | Gas on Base Sepolia |
| Withdraw | 0.1 XRGE |
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 - Call the
/api/bridge/xrge/claimendpoint with the transaction hash - The node verifies the receipt and credits XRGE on L1
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
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 Sepolia 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 contract is deployed on Base Sepolia (chain ID 84532). For mainnet, deploy with a Gnosis Safe multisig as the owner.
Bridge Relayer
The bridge relayer is an off-chain process that monitors pending withdrawals on RougeChain and executes the corresponding releases on Base Sepolia.
How It Works
- Polls the RougeChain node for pending ETH and XRGE withdrawals
- For each pending withdrawal, sends the corresponding asset on Base Sepolia
- Marks the withdrawal as fulfilled on the node
Running the Relayer
# Required environment variables
export CORE_API_URL="http://localhost:5101"
export BRIDGE_CUSTODY_PRIVATE_KEY="0x..." # EVM private key for the bridge wallet
export BRIDGE_RELAYER_SECRET="your-secret" # Shared secret for API authentication
export BASE_SEPOLIA_RPC="https://sepolia.base.org"
# Optional
export XRGE_BRIDGE_VAULT="0x..." # BridgeVault contract address
export ROUGE_BRIDGE_ADDRESS="0x..." # RougeBridge contract address
export USDC_ADDRESS="0x036CbD53842c5426634e7929541eC2318f3dCF7e"
export POLL_INTERVAL_MS="5000"
# Run
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
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 | 100 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 100 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
curl -X POST https://testnet.rougechain.io/api/v2/token/create \
-H "Content-Type: application/json" \
-d '{
"publicKey": "your-public-key-hex",
"payload": {
"name": "My Token",
"symbol": "MTK",
"totalSupply": 1000000,
"decimals": 8,
"image": "https://example.com/logo.png"
},
"nonce": 1706745600000,
"signature": "your-ml-dsa65-signature-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 '{
"publicKey": "your-public-key-hex",
"payload": {
"tokenA": "XRGE",
"tokenB": "MTK",
"amountA": 1000,
"amountB": 10000
},
"nonce": 1706745600001,
"signature": "your-signature-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%)
- Image — Collection cover image URL
- Description — Collection description
- Click Create
The collection ID is generated from creator_pubkey:SYMBOL.
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 |
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
http://127.0.0.1:5100/api
For the public testnet:
https://testnet.rougechain.io/api
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
{
"blockHeight": 12345,
"totalTransactions": 98765,
"totalWallets": 432,
"totalValidators": 15,
"totalStaked": 150000.0,
"totalBurned": 5000.0,
"totalPools": 8,
"chainId": "rougechain-devnet-1"
}
| Field | Type | Description |
|---|---|---|
blockHeight | number | Current block height |
totalTransactions | number | Total transactions processed |
totalWallets | number | Unique wallets on the network |
totalValidators | number | Active validators |
totalStaked | number | Total XRGE staked |
totalBurned | number | Total XRGE burned |
totalPools | number | Number of AMM liquidity pools |
chainId | string | Chain identifier |
Burn Stats
GET /api/burned
Get burned token statistics.
Response
{
"totalBurned": 5000.0,
"burnAddress": "XRGE_BURN_0x000000000000000000000000000000000000000000000000000000000000DEAD"
}
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 |
|---|---|---|---|
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) |
offset | number | 0 | Pagination offset |
Response
{
"blocks": [
{
"version": 1,
"header": {
"version": 1,
"chainId": "rougechain-devnet-1",
"height": 42,
"time": 1706745600000,
"prevHash": "abc123...",
"txHash": "def456...",
"proposerPubKey": "ghi789..."
},
"txs": [...],
"proposerSig": "...",
"hash": "xyz..."
}
],
"total": 12345
}
Block Fields
| Field | Type | Description |
|---|---|---|
version | number | Block format version |
header.height | number | Block number |
header.time | number | Timestamp (ms since epoch) |
header.prevHash | string | Hash of previous block |
header.txHash | string | Merkle root of transactions |
header.proposerPubKey | string | Validator who proposed this block |
txs | array | Transactions included in this block |
proposerSig | string | ML-DSA-65 signature by the proposer |
hash | string | SHA-256 hash of this block |
Block Summary
GET /api/blocks/summary
Returns a lightweight summary of recent blocks, suitable for charts and dashboards.
Response
{
"blocks": [
{
"height": 42,
"time": 1706745600000,
"txCount": 5,
"proposer": "abc123..."
}
]
}
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": {
"toPubKeyHex": "recipient-public-key-hex",
"amount": 100.0,
"fee": 0.1,
"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 |
|---|---|---|---|
toPubKeyHex | string | Yes | Recipient's public key (hex) or rouge1 address |
amount | number | Yes | Amount to send |
fee | number | No | Transaction fee (default: 0.1) |
token | string | No | Token symbol (default: "XRGE") |
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 | 50 | Max transactions to return |
offset | number | 0 | Pagination offset |
Response
{
"txs": [
{
"version": 1,
"txType": "transfer",
"fromPubKey": "abc...",
"nonce": 1234567890,
"payload": {
"toPubKeyHex": "def...",
"amount": 100
},
"fee": 0.1,
"sig": "ghi...",
"blockHeight": 42,
"blockTime": 1706745600000
}
],
"total": 150
}
Get Transaction by Hash
GET /api/tx/:hash
Response
{
"success": true,
"tx": {
"version": 1,
"txType": "transfer",
"fromPubKey": "abc...",
"nonce": 1234567890,
"payload": {
"toPubKeyHex": "def...",
"amount": 100
},
"fee": 0.1,
"sig": "ghi...",
"blockHeight": 42,
"blockTime": 1706745600000
}
}
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": {
"toPubKeyHex": "recipient-public-key-hex",
"amount": 100,
"fee": 0.1,
"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 (~7 days on testnet) 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
await rc.registerPushToken(
publicKey,
privateKey,
"ExponentPushToken[xxx]"
);
// Unregister
await rc.unregisterPushToken(publicKey, privateKey);
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": 84532,
"supportedTokens": ["ETH", "USDC"]
}
Claim Bridge Deposit
POST /api/bridge/claim
Claim wrapped tokens (qETH or qUSDC) after depositing on Base Sepolia.
Body:
{
"evmTxHash": "0x...",
"evmAddress": "0x...",
"evmSignature": "0x...",
"recipientRougechainPubkey": "abc123...",
"token": "ETH"
}
The token field can be "ETH" (default) or "USDC". The node verifies the EVM transaction, checks the signature, and mints the corresponding wrapped token.
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 all pending ETH/USDC withdrawals waiting for the relayer.
Fulfill Withdrawal
DELETE /api/bridge/withdrawals/:txId
Mark a withdrawal as fulfilled. Requires x-bridge-relayer-secret header or a PQC-signed body.
XRGE Bridge
Get XRGE Bridge Config
GET /api/bridge/xrge/config
Response:
{
"enabled": true,
"vaultAddress": "0x...",
"tokenAddress": "0x147120faEC9277ec02d957584CFCD92B56A24317",
"chainId": 84532
}
Claim XRGE Deposit
POST /api/bridge/xrge/claim
Body:
{
"evmTxHash": "0x...",
"evmAddress": "0x...",
"amount": "1000000000000000000",
"recipientRougechainPubkey": "abc123..."
}
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
Fulfill XRGE Withdrawal
DELETE /api/bridge/xrge/withdrawals/:txId
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, image, description
Fee: 50 XRGE
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
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: 100 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,
"txHash": "4520936071b9..."
}
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": [],
"txHash": "b226a36688f0...",
"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 |
| No seed phrases | Keys are stored directly (backup via file export) |
| Dual encryption | Messages encrypted for both sender and recipient |
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) | pqcrypto crate |
| Frontend (JS) | @noble/post-quantum |
All cryptographic operations happen locally - private keys never leave your device.
Key Generation
#![allow(unused)] fn main() { // Rust example use pqcrypto_dilithium::dilithium3::*; let (pk, sk) = keypair(); let signature = sign(message, &sk); let valid = verify(message, &signature, &pk); }
// 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.
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
import { RougeChain } from '@rougechain/sdk';
const rc = new RougeChain({ baseUrl: 'https://rougechain.io' });
// Deploy
const deploy = await rc.deployContract({
wasm: base64WasmBytes,
deployer: wallet.publicKey,
});
// Call
const result = await rc.callContract({
contractAddr: deploy.address,
method: 'increment',
caller: wallet.publicKey,
});
// Query
const meta = await rc.getContract(deploy.address);
const events = await rc.getContractEvents(deploy.address);
const allState = await rc.getContractState(deploy.address); // full dump
const single = await rc.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, events, and balance deltas from sub-calls are merged atomically
- 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
}
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';
const rc = new RougeChain({ baseUrl: 'https://rougechain.io' });
// Create a 2-of-3 wallet
await rc.sendTransaction({
txType: 'multisig_create',
payload: {
multisig_signers: [keyA, keyB, keyC],
multisig_threshold: 2,
multisig_label: 'Team Treasury',
},
});
// Submit a transfer proposal
await rc.sendTransaction({
txType: 'multisig_submit',
payload: {
multisig_wallet_id: 'ms-abc123...',
multisig_proposal_tx_type: 'transfer',
multisig_proposal_payload: { to_pub_key_hex: recipient, amount: 1000 },
},
});
// Co-signer approves
await rc.sendTransaction({
txType: 'multisig_approve',
payload: { multisig_proposal_id: 'mp-def456...' },
});
// Query wallets
const wallets = await rc.get('/api/multisig/wallets');
const myWallets = await rc.get(`/api/multisig/wallets/${myPubKey}`);
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 29 blockchain tools that any MCP-compatible agent can use.
Setup
cd mcp-server
npm install
npm run build
Claude Desktop
Add to ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"rougechain": {
"command": "node",
"args": ["/path/to/mcp-server/dist/index.js"],
"env": {
"ROUGECHAIN_URL": "https://rougechain.io"
}
}
}
}
Available Tools (29)
| 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 |
| Other | list_proposals, get_fee_info |
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 injects a window.rougechain provider object, similar to MetaMask's window.ethereum. DApps can use this to interact with the user's wallet.
Detecting the Extension
if (window.rougechain) {
console.log('RougeChain wallet detected');
const address = await window.rougechain.getAddress();
}
Provider API
| Method | Description |
|---|---|
getAddress() | Get the user's public key |
signTransaction(tx) | Sign a transaction with ML-DSA-65 |
getBalance() | Get the wallet's balance |
getNetwork() | Get the current network (testnet/devnet) |
Security
| Feature | Implementation |
|---|---|
| Vault encryption | AES-256-GCM with PBKDF2-derived key |
| Auto-lock | Configurable timer via background service worker |
| Key storage | chrome.storage.local (encrypted) |
| 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
const wallet = Wallet.generate();
console.log('Public key:', wallet.publicKey);
// 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 new keypair
const wallet = Wallet.generate();
// Restore from saved keys
const restored = Wallet.fromKeys(publicKey, privateKey);
// Export for storage
const keys = wallet.toJSON(); // { publicKey, privateKey }
// Verify keypair integrity
const valid = wallet.verify(); // true
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 });
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);
Push Notifications
// Register for push notifications (PQC-signed)
await rc.registerPushToken(publicKey, privateKey, 'ExponentPushToken[xxx]');
// Unregister
await rc.unregisterPushToken(publicKey, privateKey);
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
await rc.approveAllowance(wallet, { spender, token, amount });
await rc.transferFrom(wallet, { owner, to, token, amount });
await rc.freezeToken(wallet, { token, frozen: true });
Multi-Sig Wallets
// Create a 2-of-3 multi-sig wallet
await rc.sendTransaction(wallet, {
txType: 'multisig_create',
payload: {
multisig_signers: [keyA, keyB, keyC],
multisig_threshold: 2,
multisig_label: 'Team Treasury',
},
});
// Submit a transfer proposal
await rc.sendTransaction(wallet, {
txType: 'multisig_submit',
payload: {
multisig_wallet_id: 'ms-abc123...',
multisig_proposal_tx_type: 'transfer',
multisig_proposal_payload: { to_pub_key_hex: recipient, amount: 1000 },
},
});
// Co-signer approves
await rc.sendTransaction(coSignerWallet, {
txType: 'multisig_approve',
payload: { multisig_proposal_id: 'mp-def456...' },
});
// Query wallets + proposals
const wallets = await rc.get('/multisig/wallets');
const myWallets = await rc.get(`/multisig/wallets/${myPubKey}`);
const proposals = await rc.get(`/multisig/wallet/${walletId}/proposals`);
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: --peers "https://testnet.rougechain.io/api" |
| Firewall isn't blocking | Ensure port 5100 (or your --api-port) is open |
| Testnet is reachable | curl 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: the node only allows specific origins by default
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 1,000 XRGE plus the transaction fee. Use the faucet multiple times if needed.
Staked but not producing blocks
- Ensure
--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
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.
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