Backend

Backend

The CAREL backend is an off-chain execution layer built with Rust and Axum. It acts as a thin coordinator between user requests, external data sources, and on-chain contracts. Business-critical state lives on-chain; the backend computes, then writes canonical results to chain.

Why a Backend Exists

Certain protocol operations cannot run on-chain:

OperationWhy off-chain
Point calculationRequires USD prices, AI level, cross-epoch tx history from DB
Wash trading detectionTime-window query across tx history — no on-chain equivalent
Merkle tree generationMust iterate all users; posted root to SnapshotDistributor
Limit order monitoringKeeper pattern — monitors price, triggers on-chain execution
Social verificationRequires external API calls (Twitter, Telegram, Discord)
ZK proof generationGaraga Honk proof generation is a multi-second CPU job
Bridge routingAggregates quotes from LayerSwap, Garden Finance, Atomiq

Everything with consensus requirements lives in Cairo smart contracts. The backend computes, then writes the canonical result on-chain.


Architecture

Client / App


Axum API Layer
    ├── Auth + JWT
    ├── Swap / Stake / Limit Order
    ├── Privacy / Hide Mode
    ├── Points / Rewards / NFT
    ├── Bridge Routing
    └── AI Intent Parsing

         ├── PostgreSQL  (mutable off-chain ledger)
         ├── Redis       (price cache)
         └── Relayer Signer ──▶ Starknet (canonical state)

Background Workers
    ├── Block Indexer     — continuous
    ├── Price Updater     — 30s
    ├── Point Calculator  — 10–60s
    ├── Snapshot Manager  — epoch boundary
    ├── Limit Order Exec  — 10s
    └── Header Pusher     — WebSocket, continuous

Data flow rule: PostgreSQL is the mutable off-chain ledger. Smart contracts hold the immutable finalized state. Sync direction is always DB → chain, never chain → DB (except via the read-only indexer).


Service Layer

ServiceResponsibilityWhy off-chain
point_calculatorCalculate trading / staking / battle / bridge points per txNeeds USD prices, AI level, NFT state, wash-trade detection
merkle_generatorBuild Poseidon Merkle tree of epoch rewardsMust read all users; O(n) construction not feasible on-chain
snapshot_managerFinalize epochs, submit root + total to chainOrchestration between DB and multiple contracts
limit_order_executorMonitor active orders, trigger execution when price matchesKeeper pattern; price comparison needs off-chain price feed
social_verifierVerify Twitter/Telegram/Discord tasks, award pointsExternal OAuth / API calls
nft_discountRead NFT discount rate on-chain, consume usage on-chainOn-chain reads + writes — backend acts as session coordinator
privacy_verifierRoute ZK verifier selection, validate proof formatVerifier routing config + pre-validation before on-chain submission
gas_optimizerEstimate gas costs by tx typeAdvisory — no consensus needed
price_guardSanitize price inputs, fallback pricesDefense against oracle manipulation in off-chain computation
route_optimizerSelect best bridge route across providersAggregation across external provider APIs
liquidity_aggregatorAggregate DEX swap quotesRequires off-chain DEX API calls

On-Chain Sync Points

The backend calls these contracts to commit off-chain computation results:

TriggerContractMethodWhen
Points calculatedPointStoragesubmit_points(epoch, user, total)After each transaction is processed
Social task completedPointStoragesubmit_points(epoch, user, total)After DB upsert
Epoch finalizedPointStoragefinalize_epoch(epoch, total_points)End of epoch
Epoch finalizedSnapshotDistributorsubmit_merkle_root(epoch, root)After Merkle tree generated
Limit order expiredLimitOrderBookexpire_limit_order(order_id)When keeper detects expiry
Limit order filledLimitOrderBookexecute_limit_order(order_id, amount)When price condition met
Privacy actionShieldedPool V4submit_private_swap / stake / limitFor hide mode flows

Design invariant: submit_points always writes the absolute total (not a delta). This ensures that even if the backend retries or runs concurrently, on-chain state converges to the DB’s authoritative value without double-counting.


API Domains

GroupEndpoints
Identityauth, wallet, profile, admin
Tradingswap, stake, limit_order, market
Bridgebridge, garden
Privacyprivacy, onchain_privacy
Rewardsrewards, leaderboard, referral, nft, analytics
Datatransactions, charts, deposit, faucet
AIai, ai_plan
Social / Gamesocial, battleship, notifications
Infrawebhooks, health

Background Workers

WorkerIntervalFunction
Block indexerContinuousIndex Starknet events into DB
Price updater30sCache latest prices in Redis
Point calculator10–60sProcess pending txs, sync points to chain
Snapshot managerEpoch boundaryFinalize epoch, submit Merkle root
Limit order executor10sCheck prices, execute or expire orders
Header pusherContinuousWebSocket price/order push

Garaga ZK Proof Pipeline

For Hide Mode, the backend orchestrates the full ZK proof lifecycle:

User submits hide request


auto_prover receives tx_context + user_address

        ├── (testnet) load precomputed payload or static artifacts
        └── (mainnet) invoke GARAGA_PROVE_CMD


        Honk proof generated (Barretenberg / Noir circuit)


        garaga-rs assembles calldata for Starknet verifier


        nullifier / root / commitment bound to public inputs


        Relayer submits tx → ShieldedPool V4 verifies on-chain

The Redis-based prover queue limits concurrent proving jobs (default: 2) to prevent resource exhaustion.


Signer Semantics

KeyPurpose
BACKEND_PRIVATE_KEYStarknet relayer — signs all on-chain txs submitted by backend
BACKEND_ACCOUNT_ADDRESSStarknet account contract for the relayer key
BACKEND_PUBLIC_KEYCorresponding public key for verification

These are Starknet keys, unrelated to LLM provider API keys.


Internal Audit Status

A full file-by-file internal audit of all backend-rust/src/ files was completed on 2026-05-06.

Confirmed bugs — all fixed:

SeverityFinding
CriticalExpired limit orders permanently locked user from_token — keeper never called on-chain refund
CriticalReferral double-counting — bonus synced via submit_points AND separately claimable via ReferralSystem
MediumRace condition — social_verifier used add_points (delta) while point_calculator used submit_points (absolute overwrite)

Open pre-mainnet items (selected high priority):

ItemFile
Local Honk proof pre-validationservices/privacy_verifier.rs
merkle_generator.generate_proof returns unverified proof silentlyservices/merkle_generator.rs
Epoch finalization possible without Merkle rootservices/snapshot_manager.rs
Chain-indexed transactions have no USD value → 0 pointsindexer/block_processor.rs
Bridge worker uses inconsistent points minting pathbridge_worker.rs

Full audit with root cause analysis, 15 technical-debt items, and prioritized checklist: backend-rust/AUDIT_BACKEND_RUST.md


Running the Backend

cd backend-rust
cp .env.testnet.example .env
# fill in RPC URLs, contract addresses, keys
sqlx migrate run
cargo run

Tests:

cargo test
# 208 passed, 0 failed (snapshot: 2026-03-05)

Smoke test:

bash scripts/smoke_test_api.sh

For environment variable reference: backend-rust/.env.testnet.example