Production-grade Soroban smart contracts for a risk-free, on-chain trading classroom. PeerX gives users a fully simulated environment to learn market mechanics — AMM swaps, liquidity provision, limit & stop-loss orders, staking, referrals, and portfolio analytics — without exposing real capital.
- Overview
- Why PeerX
- Core Capabilities
- Architecture
- Repository Layout
- Tech Stack
- Getting Started
- Usage Examples
- Security Posture
- Observability
- Testing & Verification
- Migrations & Versioning
- Operational Runbook
- Performance & Benchmarking
- Experimental Modules
- Contributing
- License
- Contact
PeerX Contracts is the on-chain core of PeerX — a Soroban-based educational trading platform that mirrors real-world decentralized exchange mechanics in a fully sandboxed, zero-capital environment.
Every action executes against deterministic contract state with the same primitives users encounter on production AMMs: constant-product pricing, slippage checks, slippage-aware routing across multiple pools, liquidity-provider (LP) positions, fee tiers, and order books. Because the assets are simulated, learners can experiment with adversarial scenarios (front-running, oracle drift, liquidity crises) that would be unsafe and uneconomic against real funds.
| Problem | PeerX's approach |
|---|---|
| New traders learn by losing real money | Risk-free simulator — failures cost only reputation |
| Most "demo" platforms ignore DEX mechanics | Faithful AMM, LP math, fee curves, oracle slippage |
| Educational tools lack defensive engineering | Hardened contracts with KYC gates, risk limits, circuit breakers, formal invariants |
| Hard to teach advanced order types | Native limit and stop-loss orders on-chain |
- Constant-product AMM swaps (
XLM⇄ simulated issued assets). - Multi-pool registry with admin-managed pool creation and LP shares.
- Multi-hop routing — atomic best-route discovery across registered pools.
- Slippage protection — configurable basis-point ceiling per swap.
- Non-panicking
safe_swap— records failed orders as a counter rather than reverting.
- Limit orders that fill when the oracle price crosses a threshold.
- Stop-loss orders that trigger on adverse price movement.
- Order expiry, cancellation, and per-user lookup.
- Add / remove liquidity with proper LP-token minting using the Babylonian integer-sqrt initialization.
- LP positions are first-class: per-user deposits, share-of-pool, and proportional withdrawals.
- Multi-asset balances with non-negative saturation arithmetic.
- Tier system (Basic → Silver → Gold → Platinum) drives fee discounts and rate-limit budgets.
- Portfolio analytics — PnL, win rate, Sharpe-style ratios, top-traders leaderboard.
- Instance-storage query cache with TTL, hit/miss instrumentation, and admin-controlled invalidation.
- KYC state machine with operators,
Pending/Verified/Rejectedtransitions, and an audit trail. - Governance override path for terminal state changes with a configurable timelock.
- Every sensitive entry point is statically guarded (see
scripts/check_kyc_guards.sh).
- Circuit breaker that auto-pauses when swap volume crosses threshold.
- Concentration limits — no single user can dominate the pool.
- Position limits — per-asset exposure caps.
- Rate limiter — tier-aware per-hour budgets for swaps and LP operations.
- Referral system — register referrer relationships, earn commissions distributed from swap fees paid by the referred user.
- Badges & achievements — on-chain, programmatic, batched emission.
- Pause / resume trading (admin-gated).
- Freeze / unfreeze individual accounts.
- State snapshots for forensic and recovery workflows.
- Governance parameters with proposed-update lifecycle (experimental governance modules suggest design patterns, but stake-weighted voting is not implemented).
- Multi-sig compatible — admin authority is a single transferable
Address. Operations teams are expected to secure it behind a multi-sig wallet off-chain.
- Staking bonus manager with 30 / 60 / 90 / 365-day locks, periodic distributions, and 10% early-unstake penalty.
- Transparent distribution history for auditing.
(feature-gated behind experimental)
- NFT platform — minting, marketplace, fractional ownership, lending.
- ZKP / private transactions — commitments, range proofs, witness manager, balance proofs.
- Dynamic fee adjustment — congestion-aware, history-tracked, with emergency override.
- Predictive analytics — confidence-weighted signals.
- Cross-chain bridge hooks.
┌─────────────────────────────────────────┐
│ CounterContract │
│ (single WASM artifact, all entry pts) │
└──────────────┬──────────────────────────┘
│
┌────────────┬───────────┼────────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
Trading Portfolio Risk Governance Identity
(AMM, LP, (balances, (circuit (params, (KYC,
routing) analytics) breaker, pause, freeze, tiers)
limits) snapshot)
│ │ │ │ │
└────────────┴─────┬─────┴────────────┴─────────────┘
▼
Instance / Persistent Storage on Soroban
(with TTL-scoped query cache)
External modules expose a stable, narrow function surface; internal logic is split into focused files (trading.rs, portfolio.rs, kyc.rs, orders.rs, rate_limit.rs, risk_management/*, governance/*, …) so features can be reasoned about in isolation.
PeerX-Contracts/
├── Cargo.toml # Workspace root (counter, soroban-ping)
├── soroban.toml # Soroban network profiles (testnet/mainnet/local)
├── README.md # ← you are here
├── SECURITY.md # Auditing, invariants, trust model, SLA summary
├── CONTRIBUTING.md # Contribution rules + security disclosure SLA acknowledgment
├── docs/
│ └── SLA.md # Security response SLA: <24h ack, <14d/<30d patches, coordinated disclosure
├── .github/
│ ├── ISSUE_TEMPLATE/ # Issue templates (security reports acknowledge the SLA)
│ ├── workflows/
│ │ ├── ci.yml # Single canonical CI (quality + build + test)
│ │ └── README.md # Workflow rationale
├── scripts/
│ ├── benchmark_cache.py # Portfolio query cache benchmark runner
│ ├── check_kyc_guards.sh # Static audit: every sensitive fn has a KYC guard
│ ├── regression_detection.py
│ ├── verify_formal.sh
│ └── build_optimized.sh
├── benches/ # Release-mode contract benchmarks
├── market-data-streaming/ # Standalone Rust crate (off-chain HFT-grade feed)
└── peerx-contracts/
├── counter/ # Main contract crate (CounterContract)
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Contract entry points
│ ├── trading.rs # AMM swap & multi-hop routing
│ ├── portfolio.rs # Balances, tiers, analytics, caching
│ ├── orders.rs # Limit & stop-loss orders
│ ├── rate_limit.rs # Tier-aware rate limiter
│ ├── kyc.rs # KYC state machine
│ ├── referral_system.rs # Referrals & commissions
│ ├── staking_bonus.rs # Locked staking & distributions
│ ├── emergency.rs # Pause / freeze / snapshot
│ ├── migration.rs # Versioned upgrades
│ ├── events.rs # Structured events, routed through observability::log
│ ├── observability.rs # LogLevel enum, admin-settable log threshold
│ └── risk_management/ # Circuit breaker, concentration, limits
├── soroban-ping/ # Hello-world contract, smoke test
├── trading_test_runner/ # Off-chain trading test driver
├── waitlist/ # Standalone waitlist contract
├── referral/ # Standalone referral contract
├── bounty/ # Standalone bounty contract
└── credit-waitlist/ # Standalone credit-waitlist contract
| Layer | Technology |
|---|---|
| Smart contracts | Soroban (soroban-sdk = "21.7.1") |
| Language | Rust (no_std for WASM targets) |
| Build profile | release: opt-level = "z", lto = "fat", panic = "abort" — small, deterministic WASM |
| Off-chain market data | Standalone Rust crate (market-data-streaming/) — WebSocket / REST feeds, order-book analytics, throughput-limiter protections |
| Tooling | Soroban CLI, cargo, rustfmt, clippy |
| CI | GitHub Actions — quality → build → test |
- Rust toolchain (stable, ≥ 1.74) — install via rustup.
- Soroban CLI — setup guide.
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown.
git clone https://github.com/<REPO_URL>.git
cd peerx-contracts
# Type-check the workspace
cargo check --workspace
# Build the WASM artifact (release)
# Binary name comes from peerx-contracts/counter/Cargo.toml [package].name = "counter"
cargo build --release --target wasm32-unknown-unknown \
--manifest-path peerx-contracts/counter/Cargo.toml
# Run the full test suite
cargo test --workspace --libsoroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/counter.wasm \
--network testnet \
--source <YOUR_STELLAR_SECRET_KEY>
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
--source <YOUR_STELLAR_SECRET_KEY> \
-- initializeAll calls shown are thin wrappers around the contract — the same calls are invoked from the JS SDK or CLI.
soroban contract invoke --id <CONTRACT_ID> --network testnet \
-- mint --token XLM --to <USER_ADDRESS> --amount 1000000soroban contract invoke --id <CONTRACT_ID> --network testnet \
--source <USER_SECRET> \
-- swap --from XLM --to USDCSIM --amount 1000 --user <USER_ADDRESS>soroban contract invoke --id <CONTRACT_ID> --network testnet \
--source <USER_SECRET> \
-- add_liquidity --xlm_amount 5000 --usdc_amount 5000 --user <USER_ADDRESS>soroban contract invoke --id <CONTRACT_ID> --network testnet \
-- source <USER_SECRET> \
-- place_limit_order \
--token_in XLM --token_out USDCSIM \
--amount_in 100 --limit_price 150 \
--user <USER_ADDRESS># User submits
soroban contract invoke --id <CONTRACT_ID> --network testnet \
--source <USER_SECRET> -- kyc_submit --user <USER_ADDRESS>
# Operator approves after review
soroban contract invoke --id <CONTRACT_ID> --network testnet \
--source <OPERATOR_SECRET> \
-- kyc_update_status --operator <OPERATOR_ADDRESS> \
--user <USER_ADDRESS> --new_status Verifiedsoroban contract invoke --id <CONTRACT_ID> --network testnet \
-- get_portfolio --user <USER_ADDRESS>We treat the contract suite as auditable production code even though assets are simulated.
- Authorization — every privileged function goes through
require_adminorrequire_authenticated_verified_user. CI statically enforces this (scripts/check_kyc_guards.sh). - Arithmetic — all balance and fee math uses
saturating_*operations; divisions guard zero denominators. - Reentrancy — Soroban's deterministic execution model prevents reentrancy by design; the codebase also never makes cross-contract calls mid-mutation.
- Risk controls — circuit breaker, concentration and position limits are evaluated on every swap.
- Invariants — asset conservation, AMM constant-product non-increase, fee-bounds, and LP-token conservation are enforced and exposed via formal property tests (
tests/formal_verification_tests.rs). - Rate limits — tier-aware throttles for both swaps and LP operations, with garbage-collectable per-user state.
- Emergency — admin pause, per-account freeze, on-chain state snapshots for forensic recovery.
- Fuzzing —
cargo test fuzz_ -- --nocaptureexercises edge cases on every entry point.
See SECURITY.md for the full vulnerability checklist, invariant specifications, and authorization matrix. Security disclosures are handled under a documented Security Response SLA: acknowledgment < 24 h, patches < 14 days (High) / < 30 days (Medium), and coordinated disclosure after the patch ships.
Every structured event the contract emits (src/events.rs, plus the ad-hoc
diagnostics previously gated behind #[cfg(feature = "logging")] in
lib.rs) is routed through a single choke point in
src/observability.rs:
observability::log(env, level, topics, data). It publishes exactly what
env.events().publish(topics, data) would have published — same event
names, same payloads — but only if level clears the currently configured
threshold.
pub enum LogLevel { Debug, Info, Warn, Error }Ordered from most to least verbose. A configured threshold of Warn means
only Warn and Error events are published; Debug and Info are
silently dropped before the event is ever written.
Until an admin overrides it, the threshold falls back to a compiled default selected by cargo feature:
| Build | Feature | Default level |
|---|---|---|
| Dev | (none) | Debug |
| Testnet | testnet |
Info |
| Mainnet | mainnet |
Warn |
# Mainnet build — quiet by default, no Debug/Info noise
cargo build --release --target wasm32-unknown-unknown \
--manifest-path peerx-contracts/counter/Cargo.toml --features mainnetThe effective threshold can be changed at runtime — no redeploy required — and is stored in persistent storage, so it survives across calls and contract upgrades:
soroban contract invoke --id <CONTRACT_ID> --network mainnet \
--source <ADMIN_SECRET> \
-- set_log_level --caller <ADMIN_ADDRESS> --level Error
soroban contract invoke --id <CONTRACT_ID> --network mainnet \
-- get_log_levelset_log_level requires caller.require_auth() plus require_admin — the
same admin gate every other privileged entry point uses. Setting Error
silences every event except Error-level ones (see
observability_tests.rs::error_level_silences_everything_except_error_events
for the event-count assertion this guarantees).
New helpers in events.rs should call observability::log instead of
env.events().publish directly, and pick the lowest level that's still
useful in production:
Debug— high-frequency, per-operation detail (e.g. raw swap amounts).Info— normal business events (swaps, LP changes, tier changes).Warn— degraded conditions worth an operator's attention (admin pause/resume, failed orders, congestion changes).Error— extreme conditions (emergency overrides) that must never be silenced.
cargo test --workspace --lib --verbose # Library suites
cargo test --manifest-path peerx-contracts/counter/Cargo.toml \
--lib kyc_tests -- --nocapture # KYC focused
cargo test --manifest-path peerx-contracts/counter/Cargo.toml \
error_code_tests -- --nocapture # Error-code regression
cargo test --manifest-path peerx-contracts/counter/Cargo.toml \
--test formal_verification_tests formal_verification \
-- --nocapture --test-threads=1 # Property-based invariantsCI is a single workflow — .github/workflows/ci.yml — running three jobs in series: quality (fmt + check + KYC-guard audit) → build (full workspace build) → test (the four commands above).
PeerX supports versioned, in-place upgrades with a deterministic migration pipeline.
CONTRACT_VERSIONis declared inlib.rsand persisted in contract storage.initialize()is called on fresh deployments to record the active version.migrate()performs version-to-version data migration; example:migrate_from_v1_to_v2augmentsPortfoliowithmigration_time.
Upgrade checklist
- Bump
CONTRACT_VERSION. - Implement
migrate_from_vN_to_vN+1. - Add tests under
migration_tests.rssimulating the transition. - Verify backward compatibility of every persisted struct.
- Run
migrate()after deploying the new WASM.
For incident response:
| Step | Action | Contract call |
|---|---|---|
| 1 | Investigate | read events, state, snapshots |
| 2 | Halt trading | emergency_pause(admin) |
| 3 | Freeze suspect accounts | freeze_user(admin, user) |
| 4 | Capture forensic state | snapshot_state() |
| 5 | Fix root cause off-chain | deploy new WASM |
| 6 | Migrate & resume | migrate() → emergency_unpause(admin) |
The circuit breaker will auto-pause when swap volume crosses the configured threshold — a safety net, not a substitute for monitoring.
python3 scripts/benchmark_cache.pyMeasures cold-vs-warm latency for get_portfolio and get_top_traders, plus cache hit ratio, against the release-mode ignored test benchmark_cache_latency_and_hit_ratio.
For raw contract benchmarks:
cargo bench --manifest-path benches/Cargo.tomlThe following sit behind the experimental feature flag and are not mainnet-ready yet:
- 🎨 NFT platform — minting, marketplace, fractional ownership, lending (
nft_*modules). - 🔐 ZKP / private transactions — commitments, range proofs, witness manager (
zkp_*modules). - 📈 Dynamic fee adjustment — congestion-aware with
FeeHistoryManagerand emergency override. - 🌉 Bridge — cross-chain hooks.
- 📊 Predictive analytics — confidence-weighted signals.
Each experimental module has its own test suite gated by the same feature flag; CI does not opt into them, so experiments can iterate quickly without breaking the canonical suite.
We welcome pull requests for new educational scenarios, additional test coverage, performance wins, and documentation. Please read CONTRIBUTING.md — which also acknowledges our Security Response SLA for security disclosures — and:
- Run
cargo fmt --allbefore pushing. - Add tests for any new entry point.
- Add the entry point to
scripts/check_kyc_guards.shif it touches user funds. - Keep PRs focused — one feature or fix per PR.
This project is licensed under the MIT License — see the LICENSE file for details.
- Issues:
https://github.com/<REPO_URL>/issues - Security disclosures:
<SECURITY_EMAIL> - Project:
https://github.com/<REPO_URL>
PeerX Contracts — learn markets the hard way, without losing money.