Tokenized equity market making system that provides onchain liquidity and captures arbitrage profits.
- Onchain Liquidity: Raindex orders continuously offer to buy/sell tokenized equities at spreads around oracle prices
- Automatic Hedging: When liquidity is taken onchain, the Rust bot executes offsetting trades on traditional brokerages to hedge the change in exposure
- Profit Capture: Earns the spread on every trade while hedging directional exposure
The system enables efficient price discovery for onchain tokenized equity markets by providing continuous two-sided liquidity.
- Supported Executors: Execute hedges through Alpaca Broker API (managed accounts, auto-rebalancing) or dry-run mode for testing
- Real-Time Hedging: WebSocket-based monitoring for near instant execution when onchain liquidity is taken
- Fractional Share Support: Executes fractional shares on Alpaca; dry-run mirrors the same execution model for testing
- Alpaca Hedge Preflight: Checks available offchain shares for sells and cash buying power for buys (includes unsettled T+1 equity-sale proceeds, excludes margin) before submitting Alpaca hedge orders
- Serialized Counter-Trade Submission: Within one bot process, queued and periodic hedge submissions share a lock and reserve budget against active offchain orders before placing new Alpaca counter-trades
- Complete Audit Trail: Database tracking linking every onchain trade to offchain hedge executions
- Exposure Hedging: Automatically executes offsetting trades to reduce directional exposure from onchain fills
- Operator Vault Controls: CLI supports generic ERC20 deposits to and withdrawals from Raindex vaults, with a USDC-specific withdrawal shortcut
- Nix with flakes enabled - For reproducible development environment
- direnv (recommended) - The repo includes an
.envrcthat automatically loads the Nix dev shell when youcdinto the project. Without direnv, runnix developmanually in each terminal
The Rust build has two compile-time dependencies that must be set up before
cargo check will succeed:
- Solidity ABI artifacts - The Rust
sol!macros reference JSON ABI files that are produced by per-feature Nix derivations undernix/and exposed viaST0X_*_ABIenvironment variables when you enter the dev shell -- no submodule checkout or manualforge buildstep required - SQLite database - The
sqlx::query!macros validate SQL against a live database at compile time
git clone https://github.com/ST0x-Technology/st0x.liquidity.git
cd st0x.liquidity
direnv allow # or `nix develop` if not using direnv
sqlx db create # create SQLite database for sqlx macros
sqlx migrate run # apply migrations
cargo check # verify setupSolidity ABIs are produced as per-feature Nix derivations under nix/
(forge-std.nix, pyth.nix, rain-math-float.nix, rain-orderbook.nix) and
exposed to cargo through environment variables set by the dev shell -- no
submodule checkout, no manual forge build required.
To reset the database: sqlx db reset -y
AI agents: For Rust/TypeScript work, run agents inside the dev shell so they
have access to all tooling (e.g., nix develop -c claude). For editing Nix
code, a regular shell is fine.
The application uses TOML configuration files split into plaintext config and
encrypted secrets. See example.config.toml and example.secrets.toml for all
available options. Operational intervals such as
apalis_finished_job_cleanup_interval_secs must be explicitly configured and
non-zero.
Current broker support is limited to alpaca-broker-api and dry-run.
cargo run --bin server -- --config path/to/config.toml --secrets path/to/secrets.tomlManual wrap of tokenized equity into wrapped vault shares (requires rebalancing mode and a configured Base liquidity wallet):
cargo run --bin cli -- --config path/to/config.toml --secrets path/to/secrets.toml wrap-equity --symbol AAPL --quantity 10.5Manual unwrap of wrapped equity shares (requires rebalancing mode and a configured Base liquidity wallet):
cargo run --bin cli -- --config path/to/config.toml --secrets path/to/secrets.toml unwrap-equity --symbol AAPL --quantity 10.5Manual repair of local position tracking after an operator trade or rebalance:
cargo run --bin cli -- --config path/to/config.toml --secrets path/to/secrets.toml repair set-position --symbol SPYM --zero --reason "manual rebalance completed"
cargo run --bin cli -- --config path/to/config.toml --secrets path/to/secrets.toml repair set-position --symbol SPYM --long 100 --price 200 --reason "manual buy not observed by bot"
cargo run --bin cli -- --config path/to/config.toml --secrets path/to/secrets.toml repair set-position --symbol SPYM --short 12.5 --price 200 --reason "manual sell not observed by bot"--price (USDC per share) is required for a nonzero target when the symbol uses
a dollar-value execution threshold and no price is already known; without it the
repaired exposure could never be valued and would never hedge.
set-position is rejected while the symbol still has a pending offchain hedge
order; resolve it first with repair fail-pending-offchain-order, then retry.
Alpaca Broker API (managed accounts, supports auto-rebalancing):
For managed/omnibus accounts. Requires Broker API access from Alpaca. This is the only integration that supports automatic portfolio rebalancing (USDC/equity threshold-based).
Add credentials to your TOML config file under the [broker] section (see
example.config.toml and example.secrets.toml). Alpaca configs must also set
broker.counter_trade_slippage_bps, which controls the buy-side preflight
buffer in basis points.
The system runs on a NixOS host on DigitalOcean, managed by deploy-rs. All infrastructure is defined declaratively in Nix and Terraform.
GitHub Actions (CI/CD)
|
| deploy-rs over SSH
v
NixOS host (DigitalOcean droplet)
├── st0x-hedge (systemd, hedging bot)
├── datasette (systemd, SQLite database explorer)
├── nginx (dashboard + WebSocket proxy)
└── grafana (metrics visualization)
Services are deployed as independent nix profiles, allowing per-service updates and rollbacks without affecting other services.
| File | Purpose |
|---|---|
os.nix |
NixOS system configuration (services, firewall, users) |
deploy.nix |
deploy-rs profiles and deployment wrappers |
rust.nix |
Nix derivation for Rust binaries |
keys.nix |
SSH public keys and role-based access |
infra/secrets.nix |
ragenix secret declarations |
secret/*.toml.age |
Encrypted service configs (decrypted at deploy) |
infra/ |
Terraform for DigitalOcean infrastructure |
disko.nix |
Disk partitioning for nixos-anywhere bootstrap |
# Deploy everything (system config + all service binaries)
nix run .#deployAll
# Deploy only NixOS system configuration (SSH, firewall, systemd units, nginx)
nix run .#deployNixos
# Deploy a specific service profile
nix run .#deployService st0x-hedge
nix run .#deployService datasettenix run .#remote # interactive shell
nix run .#remote -- <command> # run a commandEach service profile maintains a history of deployments. Rollback requires two steps: reverting the nix profile, then restarting the affected services (the profile switch alone does not trigger a restart).
deploy-rs uses legacy (nix-env-style) profiles internally.
nix profile rollback is not compatible — you must use nix-env.
SSH into the host and run:
# Roll back the bot profile to previous deployment
nix-env --profile /nix/var/nix/profiles/per-service/st0x-hedge --rollback
systemctl restart st0x-hedge
# Roll back the Datasette profile
nix-env --profile /nix/var/nix/profiles/per-service/datasette --rollback
systemctl restart datasetteService configs are encrypted with ragenix (age encryption using SSH keys) and
committed to git as .age files. The NixOS host decrypts them at activation
using its SSH key, mounting cleartext to /run/agenix/ (tmpfs).
# Edit an encrypted config
nix run .#secret secret/st0x-hedge.toml.age
# Re-encrypt all secrets after key changes
ragenix --rules ./infra/secrets.nix -rKey access is managed via roles in keys.nix:
roles.ssh- SSH access to the host (operator + CI)roles.infra- can decrypt terraform state (operator + CI)roles.service- can decrypt service configs (operator + host)
Infrastructure is managed with Terraform, wrapped in Nix for reproducibility:
nix run .#tfInit # initialize terraform
nix run .#tfPlan # preview changes
nix run .#tfApply # apply changes
nix run .#tfDestroy # tear down infrastructureTerraform state is encrypted with age and committed to git.
For initial setup of a new host, Terraform provisions a DigitalOcean Ubuntu droplet. nixos-anywhere then converts it to NixOS over SSH:
nix run .#tfApply # provision Ubuntu droplet
nix run .#bootstrap # convert to NixOS (updates host key + rekeys secrets)
nix run .#deployAll # first deployment- CI (
.github/workflows/ci.yaml): Builds all packages, runs tests and clippy inside nix derivations, builds dashboard. Runs on every push. - CD (
.github/workflows/cd.yaml): Deploys to the NixOS host vianix run .#deployAll. Runs on push to master.
To reproduce CI checks locally, use the same dev shell CI uses:
nix develop .#ci-backend -c cargo check --workspace
nix develop .#ci-backend -c cargo nextest run --workspace --all-features
nix develop .#ci-backend -c cargo clippy --workspace --all-targets --all-featuresnix run .#simulate launches a full local simulation of the market making
system using mprocs to run the dashboard and
the bot side-by-side. nix run .#simulate-failures starts the same stack, then
creates failed mint and redemption rebalances whose mock Alpaca provider later
completes and prints the recheck-transfer commands that recover them.
What it does:
- Starts a local Anvil blockchain with deployed Raindex orderbook contracts
- Deploys mock services: Alpaca broker, tokenization API, CCTP attestation
- Creates Raindex liquidity orders — one buy and one sell per symbol (AAPL, TSLA) — all sharing a single USDC vault, with per-symbol equity vaults
- Starts the bot (hedging, equity rebalancing, USDC bridging all enabled)
- Starts the dashboard dev server
- Continuously takes orders at 10-second intervals, simulating users buying and selling tokenized equities
The bot counter-trades each fill on the mock broker, mints/redeems to rebalance equity supply between venues, and bridges USDC via mock CCTP to keep cash balanced. If the system works correctly, the vaults never permanently drain — the bot cycles liquidity back through hedging and rebalancing.
Open http://localhost:5173 to watch the dashboard. Press Ctrl-C to stop.
Workspace crates:
st0x-hedge(root) - Main arbitrage bot: event loop, CQRS/ES aggregates, conductor, dashboard backend, and CLIst0x-dto(crates/dto/) - Dashboard DTOs and TypeScript binding generationst0x-event-sorcery(crates/event-sorcery/) - CQRS/event-sourcing helpers, snapshot-backed loading, compactable observational event streams, and testing utilitiesst0x-execution(crates/execution/) - StandaloneExecutortrait abstraction with Alpaca Broker API and mock implementationsst0x-bridge(crates/bridge/) - Cross-chain bridge abstractions and CCTP implementationst0x-evm(crates/evm/) - EVM wallet, provider, and test-chain supportst0x-finance(crates/finance/) - Shared financial primitives:Symbol,FractionalShares,Usdc,Usd, and related domain typesst0x-float-serde(crates/float-serde/) - Shared Rain Float formatting and serde helpers for workspace wire formatsst0x-float-macro(crates/float-macro/) - Proc-macro for compile-timeFloatliterals (float!(1.5))
flake.nix # Nix flake: packages, devShell, NixOS config
os.nix # NixOS system configuration
deploy.nix # deploy-rs profiles and wrappers
rust.nix # Rust package derivation
disko.nix # Disk partitioning for bootstrap
keys.nix # SSH keys and role-based access
config/
├── prod/
│ └── st0x-hedge.toml # plaintext prod service config
└── staging/
└── st0x-hedge.toml # plaintext staging service config
infra/
├── secrets.nix # ragenix secret declarations
└── ... # Terraform (DigitalOcean)
secret/
└── st0x-hedge.toml.age # encrypted service secrets
dashboard/ # SvelteKit operations dashboard
.github/workflows/
├── ci.yaml # Build, test, clippy, dashboard
└── cd.yaml # Deploy to NixOS host
cargo check # fast compilation check
cargo nextest run --workspace # run all tests
cargo clippy --workspace --all-targets --all-features -- -D clippy::all
cargo fmt # format Rust code
nix fmt # format Nix code (when editing .nix files)All commands are run via nix run .#<name>. Commands that access infrastructure
or secrets decrypt state using your SSH key (~/.ssh/id_ed25519 by default).
Pass -i <path> to use a different key.
Development:
| Command | Usage | Notes |
|---|---|---|
genBunNix |
nix run .#genBunNix |
Regenerates dashboard/bun.nix from bun.lock |
Building (Nix):
| Command | Usage | Notes |
|---|---|---|
st0x-liquidity |
nix build .#st0x-liquidity |
Build + tests |
st0x-clippy |
nix build .#st0x-clippy |
Clippy linting |
st0x-dashboard |
nix build .#st0x-dashboard |
SvelteKit dashboard |
Deployment (requires SSH key for host access and terraform state decryption):
| Command | Usage | Notes |
|---|---|---|
prodDeployAll |
nix run .#prodDeployAll |
Deploy prod system config + all services |
prodDeployNixos |
nix run .#prodDeployNixos |
Deploy prod NixOS system config only |
prodDeployNixosBoot |
nix run .#prodDeployNixosBoot |
Register prod NixOS config for next boot |
prodDeployService |
nix run .#prodDeployService <profile> |
Deploy a single prod service |
stagingDeployAll |
nix run .#stagingDeployAll |
Deploy staging system config + all services |
stagingDeployNixosBoot |
nix run .#stagingDeployNixosBoot |
Register staging NixOS config for next boot |
remote |
nix run .#remote [-- <cmd>] |
SSH into production host |
secret |
nix run .#secret <file.age> |
Edit an encrypted config, then re-encrypt all |
bootstrap |
nix run .#bootstrap |
One-time NixOS install on a new host |
The deploy workflows also expose a broker-migration mode for the one-time
dbus to dbus-broker migration. Use that mode when a NixOS change must be
registered for next boot instead of live-switched: it boot-deploys the system
profile, reboots the host, waits for SSH over Tailscale, verifies dbus-broker,
then runs the normal full deploy to reactivate service profiles. This keeps the
host on the current NixOS default instead of carrying a permanent override back
to classic dbus.
To run the production migration:
- Draft the release tag from
master. - Open the Deploy to Production GitHub Actions workflow.
- Click Run workflow.
- Enter the release tag.
- Set
modetobroker-migration. - Start the workflow and wait for the final
Deploy all profiles after rebootstep to pass. - Use the default
allmode for future production deploys.
Infrastructure (requires SSH key for terraform state decryption):
| Command | Usage | Notes |
|---|---|---|
tfInit |
nix run .#tfInit |
Initialize terraform |
tfPlan |
nix run .#tfPlan |
Preview infrastructure changes |
tfApply |
nix run .#tfApply |
Apply planned changes |
tfDestroy |
nix run .#tfDestroy |
Tear down infrastructure |
tfEditVars |
nix run .#tfEditVars |
Edit terraform vars in $EDITOR |
tfRekey |
nix run .#tfRekey |
Re-encrypt terraform state + vars |
resolveIp |
nix run .#resolveIp |
Print the production host IP |
After changing dashboard/bun.lock, regenerate and format the Nix lockfile:
nix run .#genBunNix
nix fmt -- dashboard/bun.nixCI will fail if bun.nix is out of sync with bun.lock.
- SPEC.md - Complete technical specification and architecture
- docs/domain.md - Domain model, terminology, and naming conventions
- AGENTS.md - Development guidelines for AI-assisted coding
- example.config.toml - Configuration reference
- example.secrets.toml - Secrets reference
Market Making Flow:
- Provide Liquidity: Raindex orders offer continuous two-sided liquidity for tokenized equities at spreads around oracle prices
- Detect Fills: WebSocket monitors orderbook events when traders take liquidity onchain
- Parse Trade: Extract details (symbol, amount, direction, price) from blockchain events
- Accumulate: Batch positions until the configured execution threshold is
reached (typically dollar-based for Alpaca Broker API, whole-share for
dry-run) - Hedge: Execute offsetting market order on traditional brokerage to reduce exposure
- Track: Maintain complete audit trail linking onchain fills to offchain hedges
Profit Model: The system earns the spread on each trade (difference between onchain order price and offchain hedge execution price) while hedging directional exposure.
Note: Alpaca Broker API supports fractional share execution and the bot can
hedge using dollar-value thresholds. dry-run remains available for local
testing with whole-share thresholds when that is operationally useful.