Soroban smart contracts for the Vatix prediction market protocol on Stellar.
Core smart contracts powering Vatix prediction markets, written in Rust for the Stellar Soroban platform.
| Contract | Crate | Status | Description |
|---|---|---|---|
| Market | contracts/market |
✅ Implemented | Market creation, position trading, oracle resolution, and settlement |
| Treasury | contracts/treasury |
✅ Implemented | Protocol fee collection from withdrawal events; admin-controlled fee withdrawal |
| Outcome Token | contracts/outcome-token |
✅ Implemented | Fungible SAC-compatible tokens representing YES/NO market outcomes |
| Resolution | contracts/resolution |
✅ Implemented | Standalone oracle-based outcome resolution with dispute window |
The Market contract can optionally wire supporting modules via admin-configured contract addresses. Once registered:
set_treasuryregisters a Treasury contract address that receives fee deposits fromwithdraw_unused_collateral.set_outcome_token_contractregisters an Outcome Token contract that mints/burns tokens when market positions change.set_resolution_contractregisters a Resolution contract that gatesresolve_marketuntil a candidate is finalized.- When configured,
withdraw_unused_collateralcomputes a fee, transfers it to the Treasury, and records it viacollect_fee.
- Market Contract: Market creation, trading, and settlement logic
- Treasury: Fee collection and protocol management
- Outcome Token: Mint/burn YES/NO outcome share tokens
- Resolution Contract: Challenge-window lifecycle for oracle resolution candidates
| Contract | Crate | Status | Notes |
|---|---|---|---|
| Market | contracts/market |
✅ Complete | Trading, deposit, withdraw, settlement |
| Outcome Token | contracts/outcome-token |
✅ Complete | Mint/burn YES/NO tokens; callable only by market contract |
| Resolution | contracts/resolution |
✅ Complete | Challenge-window lifecycle for oracle candidates |
| Treasury | contracts/treasury |
✅ Complete | Fee collection, custody, and admin withdrawal |
- Language: Rust
- Platform: Stellar Soroban
- Testing: Soroban SDK test utilities
- Build: Cargo
- Binary outcome markets (Yes/No)
- Share minting and trading
- Oracle-based resolution
- Fee distribution
- Market expiration and settlement
The Market Contract still owns the final resolve_market(market_id, outcome, signature) state transition. The separate Resolution Contract adds the missing on-chain challenge window that mirrors the backend ResolutionCandidate flow:
propose(proposer, market_id, outcome, signature, evidence_uri, challenge_window_seconds)stores a signed candidate and publishes itschallenge_deadline.challenge(challenger, candidate_id, challenge_uri)can be called until the deadline. A challenged candidate cannot be finalized.finalize(finalizer, candidate_id)succeeds only after the challenge window closes and returns the candidate payload.- The backend or registered factory then submits the finalized candidate to
MarketContract::resolve_market, using the stored outcome and oracle signature.
contracts/resolution is intentionally a lifecycle and registration layer, not a replacement settlement engine. initialize(admin, factory, market_contract) registers the factory/market relationship so off-chain services can discover which resolution contract guards a market deployment.
The Market Contract emits the following events for off-chain indexing and tracking:
| Event | Topics | Fields | Description |
|---|---|---|---|
contract_initialized_event |
admin |
initialized_at: u64 |
Emitted when the contract is initialized with an admin |
market_created_event |
market_id |
creator: Address, question: String, end_time: u64 |
Emitted when a new market is created |
collateral_deposited_event |
user, market_id |
amount: i128, new_total: i128 |
Emitted when a user deposits collateral into a market |
collateral_withdrawn_event |
user, market_id |
amount: i128, new_total: i128 |
Emitted when a user withdraws collateral from a market |
position_updated_event |
market_id, user |
yes_shares: i128, no_shares: i128, locked_collateral: i128 |
Emitted when a user's position is updated after trading |
trade_executed_event |
market_id, user |
quantity: i128, price_bps: i128, side_yes: bool, executed_at: u64 |
Emitted when a user executes a trade (buy or sell) |
position_limit_exceeded_event |
market_id, user |
side_yes: bool |
Emitted when a trade would result in negative shares |
market_resolved_event |
market_id |
resolver: BytesN<32>, outcome: bool, resolved_at: u64 |
Emitted when a market is resolved with an oracle-signed outcome |
position_settled_event |
market_id, user |
payout: i128, settled_at: u64 |
Emitted when a user's position is settled and payout is transferred |
oracle_signature_verified_event |
market_id |
outcome: bool, verified_at: u64 |
Emitted when an oracle signature is verified during resolution |
fee_calculated_event |
market_id, user |
fee_amount: i128, available_after_fee: i128 |
Emitted when a fee is calculated during withdrawal |
validation_failed_event |
context |
error_code: u32 |
Emitted when validation fails, recording context and error code |
Off-chain indexers can efficiently filter events using the topic indices:
- By Market: Subscribe to events with
market_idtopic to track all activity in a specific market - By User: Subscribe to events with
usertopic to track all activity for a specific user - By Trade: Listen for
trade_executed_eventto capture all trades with quantity, price, and side information
Next.js 16 app for prediction-market UI (mock data + Freighter wallet stub).
pnpm install
pnpm dev # http://localhost:3002
pnpm build:web- Rust toolchain (stable, with
wasm32-unknown-unknowntarget) - Stellar CLI (v21.4.0+) - Install from stellar.org/docs/tools/cli
- Node 20+ and pnpm 8+ (for web app and scripts)
# Install Rust and add WASM target
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# Install Stellar CLI (macOS/Linux)
# See https://developers.stellar.org/docs/tools/cli for other platforms
curl -L https://github.com/stellar/stellar-cli/releases/download/v21.4.0/stellar-cli-21.4.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv stellar /usr/local/bin/
# Verify installation
stellar --versionAll contract builds use the canonical command: stellar contract build.
This is the single source of truth across Makefile, CI, and deployment scripts to ensure byte-for-byte identical WASM artifacts. The output path is always:
target/wasm32v1-none/release/<contract-name>.wasm
# Prerequisites: Rust toolchain, Soroban CLI
cd contracts/market && cargo build
cd ../treasury && cargo build
cd ../outcome-token && cargo build
cd ../resolution && cargo buildGenerate 375 onboarding issues (125 per repo) — see scripts/issues/README.md.
pnpm issues:generate
pnpm issues:publish # requires gh authAll deployment scripts use the canonical stellar contract build command to ensure artifact consistency.
Builds and deploys the contract to Stellar testnet using the unified build toolchain.
What it does:
- Builds the contract using
stellar contract build(same as Makefile and CI) - Locates the WASM artifact at
target/wasm32v1-none/release/*.wasm - Deploys via
stellar contract deploy --wasm <path> --network testnet - Outputs the contract ID for downstream use
# Set credentials
export TESTNET_SECRET_KEY="S..."
# Deploy to testnet (uses stellar contract build internally)
bash scripts/deploy-testnet.shEnvironment variables:
TESTNET_SECRET_KEY(required) - Funded testnet account secret keySOROBAN_NETWORK(optional) - Network name (default:testnet)CONTRACT_DIR(optional) - Contract to build/deploy (default:contracts/market)WASM_PATH(optional) - Explicit WASM path override
Generic deployment script for any configured network.
# Deploy to testnet
bash scripts/deploy.shRequires Stellar CLI and a funded account. Set
SOROBAN_NETWORKandSOROBAN_ACCOUNTenv vars before running.
To verify your local WASM matches what CI produces, compare hashes:
# Build locally
cd contracts/market
stellar contract build
# Compute and verify hash
bash ../../scripts/verify-wasm-hash.sh contracts/market
# Or use the Makefile target
make verifyThe script outputs the SHA256 hash of the WASM artifact. Compare this with:
- CI build artifacts (download from GitHub Actions)
- Builds from other developers
- Previously deployed contract hashes
Identical hashes confirm the build is reproducible across environments.
Artifact mismatch risks:
- ❌ Local testing with one WASM, deploying another
- ❌ CI tests passing but deployed contract failing
- ❌ Inability to reproduce production builds
With unified stellar contract build:
- ✅ Same WASM locally, in CI, and deployed
- ✅ Reproducible builds across environments
- ✅ Confidence that tested code is deployed code
Unified Build Command: All contracts use stellar contract build as the canonical build command.
This ensures:
- ✅ Identical artifacts across local builds, CI, and deployments
- ✅ Optimized WASM for Soroban runtime
- ✅ No drift between development and production builds
# Build any contract
cd contracts/market
stellar contract build
# Or use the Makefile convenience target
make buildThe Makefile, CI workflow (.github/workflows/ci.yml), and deployment scripts (scripts/deploy-testnet.sh) all use this same command to guarantee artifact consistency.
All builds output to:
target/wasm32v1-none/release/<contract-name>.wasm
Example artifacts:
vatix_market_contract.wasmvatix_treasury_contract.wasmvatix_outcome_token_contract.wasmvatix_resolution_contract.wasm
The scripts/ directory contains utility scripts for deployment, invocation, and contributor issue generation. Full documentation is in scripts/issues/README.md.
Smoke-tests a deployed contract by invoking one of its functions via the Soroban CLI. Used in CI to verify that the contract binary is callable after deployment.
CONTRACT_ID=your_contract_id bash scripts/invoke-example.shNote: Currently an echo guard. Replace with a real
stellar contract invokecall once the contract is deployed to a target network — see the TODO comment in the script.
The contracts/market/Makefile provides convenience targets for day-to-day contract work.
| Target | Description |
|---|---|
build |
Canonical build: Compiles using stellar contract build to produce optimized WASM |
test |
Run all unit and integration tests (depends on build) |
fmt |
Format all Rust source with cargo fmt --all |
clean |
Remove build artefacts via cargo clean |
# From the repo root
cd contracts/market
make # default — builds WASM using stellar contract build
make test # build then run the full test suite
make fmt # auto-format source files
make clean # wipe target/ directoryThe build target uses stellar contract build, which is the same command used by:
- CI pipeline (
.github/workflows/ci.yml) - Deployment scripts (
scripts/deploy-testnet.sh) - TypeScript bindings generation (
scripts/generate-bindings.ts)
This unified approach prevents artifact mismatches and ensures the WASM built locally is byte-for-byte identical to what's deployed and tested in CI.
Clippy is Rust's official linter and is enforced in CI. All warnings are treated as hard errors via -D warnings, so the build fails if any lint fires.
# Run from the contract directory
cd contracts/market
cargo clippy -- -D warningsTo suppress a lint where it is intentionally acceptable, add a targeted attribute in the source rather than weakening the global flag:
#[allow(clippy::lint_name)]
fn my_function() { ... }The CI step is defined in .github/workflows/ci.yml and runs automatically on every push and pull request.
Smart contract security is critical. All contracts will undergo:
- Extensive unit testing
- Integration testing
- External audits before mainnet deployment
Contribution guidelines coming soon. For now, check out vatix-docs for project information.
MIT License
Part of the Vatix Protocol