Thanks for helping build SO4 Markets. This guide covers how to set up the project, where things live, and how to get your changes merged.
Prerequisites
- Rust stable (install via rustup)
- Optional:
cargo-watchfor hot-reloading during development (cargo install cargo-watch)
Clone and build
git clone git@github.com:SO4-Markets/so4-oracle.git
cd so4-oracle
# Build all workspace crates
cargo build --workspace
# Type-check without building artifacts
cargo check --workspace
# Run all tests
cargo test --workspaceEnvironment variables
Key variables (see oracle/src/config.rs for the full list):
| Variable | Description |
|---|---|
PRICE_FEED_CONFIG |
JSON array of TokenConfig entries (see config/tokens.json for the schema) |
STELLAR_RPC_URL |
Soroban RPC endpoint |
HORIZON_URL |
Stellar Horizon endpoint |
NETWORK_PASSPHRASE |
Test SDF Network ; September 2015 (testnet) or mainnet equivalent |
KEEPER_SECRET_KEY |
Stellar secret key for the keeper account |
ORACLE_CONTRACT_ID |
Deployed oracle contract address |
ADMIN_API_TOKEN |
Bearer token for admin routes (/oracle/status, /metrics, etc.) |
Run locally
cargo run -p oracle
# → listening on 0.0.0.0:3000 (or whatever BIND_ADDR is set to)Watch mode (rebuilds on save):
cargo watch -x "run -p oracle"so4-oracle/
├── oracle/ Long-running Axum/Tokio binary — price loop, keeper loop, HTTP API
│ └── src/
│ ├── main.rs Entry point: starts server, price loop, keeper loop
│ ├── config.rs Config loading from env vars
│ ├── state.rs AppState shared across all tasks
│ ├── price_loop.rs Periodic price fetching and on-chain submission
│ ├── keeper_loop.rs Periodic keeper task execution (orders, deposits, withdrawals)
│ ├── metrics.rs In-memory counters exposed at GET /metrics
│ ├── api/
│ │ ├── mod.rs Router: /health, /ready, /prices, /metrics, /oracle/status, etc.
│ │ ├── prices.rs Public price feed and health/readiness handlers
│ │ └── admin.rs Admin-only status and metrics handlers
│ ├── binance.rs Binance price source
│ ├── coinbase.rs Coinbase price source
│ ├── pyth.rs Pyth price source
│ └── fixed.rs Fixed-price source (for stablecoins)
├── shared/
│ └── config/src/lib.rs TokenConfig struct + parse_token_configs() — shared by oracle
├── config/
│ └── tokens.json Example token config for local development
└── tests/ Integration tests
There is no Cloudflare Worker, wrangler.toml, or apis/ crate in this repository. The oracle is a plain Axum binary deployed via Docker (see Dockerfile) on Fly.io / Railway (see fly.toml, railway.json).
Once running, the oracle exposes:
| Route | Auth | Description |
|---|---|---|
GET /health |
None | Always returns {"status":"ok"} — liveness probe |
GET /ready |
None | Returns 200 only when price cache is warm and loops are not stale |
GET /prices |
None | Current cached prices for all configured tokens |
GET /metrics |
Bearer | Cycle counts and latency gauges |
GET /oracle/status |
Bearer | Price cache + cycle status |
GET /keeper/status |
Bearer | Pending keeper operations + recent executions |
GET /keeper/balance |
Bearer | Current keeper account XLM balance |
GET /oracle/failed-submissions |
Bearer | Ring buffer of failed on-chain submissions |
All open issues are tracked on GitHub Issues. Issues are labelled:
| Label | Meaning |
|---|---|
good first issue |
Self-contained, well-defined, good starting point |
bug |
Something is broken |
documentation |
Docs, comments, diagrams |
enhancement |
New feature or improvement |
infrastructure |
CI, Docker, deploy scripts, tooling |
Before starting, leave a comment on the issue so no one duplicates effort.
- Fork the repo (external contributors) or create a branch (team members).
- Branch naming:
feat/short-description,fix/short-description,test/short-description. - Make your changes. Keep commits focused — one logical change per commit.
- Run checks locally before opening a PR:
cargo fmt --all cargo clippy --all-targets -- -D warnings cargo test --workspace - Open a PR against
main. Fill in the PR template. - Request a review from a maintainer.
- Title: Start with a type prefix:
feat:,fix:,test:,docs:,chore:. - Description: What does this do, and why? Link the relevant issue (
Closes #N). - Tests: New functionality must include tests. Bug fixes should include a regression test.
- No partial implementations: If a function is not yet complete, leave it as a stub with
todo!()rather than committing broken logic. - No unnecessary refactors: Keep PRs focused on the stated issue.
cargo fmtis enforced in CI. Run it before pushing.cargo clippy -- -D warningsmust pass. Address all warnings.- No comments explaining what code does — names should do that. Add a comment only when the why is non-obvious.
- No emojis in code or commit messages.
- Unit tests go in the same file:
#[cfg(test)] mod tests { ... }. - Integration tests go in
tests/at the workspace root. - For HTTP endpoint tests, use
axum::testorreqwestagainst a spawned server.
type(scope): short summary (≤72 chars)
Optional body — explain the why, not the what.
Types: feat, fix, test, docs, chore, refactor
Scopes: oracle, shared, config, workspace
Open a discussion on GitHub or drop a message in the team channel. Don't open an issue just to ask a question.