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.
HyperEVM supports prefunded fill ingestion and hedging with canonical USDC.
Operators fund its equity, USDC and HYPE gas manually. An enabled hedged
HyperEVM chain requires an explicit HYPE threshold in
[alerts.low_balance_thresholds]; transport-only entries retain their existing
configuration. Active mode, gas valuation and automated rebalancing on HyperEVM
remain unavailable.
Robinhood Chain (4663) is declared the same way and ships as a prefunded
hedge-only secondary: fills on its two launch equities (wtDNUT, wtFGI) are
ingested, validated against USDG and hedged, with rebalancing = "disabled" on
every asset and no inventory adapter mapped. The build can also value its gas
(it pays gas in ETH), so active is reachable; rebalancing remains unavailable
there.
- Supported Executors: Execute hedges through Alpaca Broker API (managed accounts, auto-rebalancing); tests and local simulation run it in mock mode
- Real-Time Hedging: WebSocket-based monitoring for near instant execution when onchain liquidity is taken
- Fractional Share Support: Executes fractional shares on Alpaca
- 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
- Public Incident Messaging: The
scripts/turnkey-messageutility prepares, signs, verifies, sends, and reads zero-value Base messages from an explicitly selected Turnkey wallet, with inventory representation optional for throwaway-wallet testing - Orchestrator-Mode Mint Authorization: For assets issuance serves through
an
ST0xOrchestratorvault, signs an EIP-712 MintAuthV1 recipient authorization (nonce persisted before delivery, byte-identical retries) and delivers it to issuance before the mint can complete; vault-direct assets are untouched (see SPEC.md "Mint Recipient Authorization")
- 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, rain-math-float.nix, rain-orderbook.nix, and
raindex-governance.nix -- the shared RaindexInventory ABI) 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
nonzero, as must inventory_divergence_threshold (the number of consecutive
offchain polls that must diverge from the inventory view before the poller
escalates a forced snapshot reconciliation).
Each hedged chain requires its own order_fill_poll_interval_secs. Fill polling
and catch-up use fixed inclusive eth_getLogs limits: 1000 blocks on Base,
Ethereum and Robinhood, 50 on HyperEVM. These constants require no extra config.
Catch-up queues are independent per chain; the checkpoint advances after each
batch succeeds, so a later batch failure resumes at the first unscanned block
rather than restarting the range. See
OrderFillMonitor for retry behavior and
the catch-up throughput check required before go-live.
When equities are configured, [pricing].ws_url and the encrypted
[pricing].api_key are also required. Remote endpoints must use wss://;
plaintext ws:// is limited to st0x-pricing and loopback. The server
subscribes to the pricing service's Raindex wt<symbol> stream for
dashboard-only USD values; an outage leaves those values unavailable without
stopping hedging or falling back to a trade fill.
The [chains.<name>.trading] table requires an explicit inventory_mode
("legacy" or "managed") and a vault_owner address (the on-chain owner the
vaults are keyed by; no fallback). "managed" additionally requires an
inventory address (the shared RaindexInventory the bot operates via
OPERATOR_ROLE) and is forbidden from being set under "legacy". Its required
inventory_adapters is an array of { venue, operator } records attributing
public operator addresses to venues such as Bebop and Uniswap v4; unknown
operators remain visibly unattributed. This deployment metadata belongs in
plaintext config, not secrets or environment variables. Trade protocol v3
preserves configured and unknown onchain venues; older protocol versions
collapse adapter and Unknown Onchain venues to Raindex for compatibility. See
the [chains.base] block in example.config.toml for the full field
documentation.
The chain registry enforces four rules at startup, each failing closed rather than skipping the chain:
- Every
[chains.<name>]table needs a matching[chains.<name>]secrets entry supplying itsrpc_url, and vice versa. Acting on a chain with no endpoint, and holding an endpoint for a chain with no addresses, both leave fund routing undefined. - Any number of chains may carry a
[trading]table -- each hedged chain drives its own fill watcher and its own vault polling -- but exactly one of them must setprimary = true. Zero or several primary claimants leave the chain the rebalancing and cash paths read undefined, so startup fails naming them. - At least one chain must be configured, and at least one of those must trade.
- Every
[chains.<name>]entry must declarerequired_confirmationsdirectly on the chain table (not inside[trading]). There is no default: the depth encodes that chain's reorg behaviour, so omitting it fails config parsing.
Current broker support is limited to the Alpaca Broker API: secrets
type = "alpaca-broker-api" (stored API keys) or its keyless authentication
variant type = "alpaca-broker-api-kms" (Cloud KMS signed client assertions).
For local whole-system testing use the simulation apps (nix run .#simulate).
validate-config runs the startup validation without starting the server or
reaching any external service:
# Config half only -- no secrets, no network, no clock.
cargo run --bin validate-config -- --config config/prod/st0x-hedge.toml
# Everything the deploy gate checks, including the config/secrets cross-checks.
cargo run --bin validate-config -- --config path/to/config.toml --secrets path/to/secrets.tomlWithout --secrets it judges the config file alone: schema (unknown keys are
rejected), the port, chain, asset and [rebalancing] cross-field rules, and
every value the config carries on its own. What it cannot see is what the
secrets file supplies -- broker credentials, per-chain rpc_urls, wallet keys,
pricing and issuance API keys -- which stays the deploy gate's job. Both modes
run the same checks the bot runs at boot, so a rule broken here breaks startup.
Because the secrets-free mode needs nothing but the file, CI validates every
config the repository ships (config/**/*.toml, example.config.toml,
e2e/config.toml) on every pull request, via the
every_repo_config_passes_config_only_validation test in st0x-config. A
config edit is therefore caught in the pull request that makes it, rather than
by a deployed service refusing to boot.
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 liquidity wallet for the selected network):
cargo run -p st0x-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 liquidity wallet for the selected network):
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml unwrap-equity --symbol AAPL --quantity 10.5Both commands default to Base and resolve addresses from the selected chain's
[chains.<name>.trading.assets.equities] table. Other configured trading chains
resolve the same way, so do not pass --registry:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml wrap-equity --symbol RKLB --quantity 0.1 --network ethereumWhen the selected network has no trading table, pass its st0x.registry token list as the fallback:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml wrap-equity --symbol RKLB --quantity 0.1 --network ethereum --registry path/to/st0x.registry/token-lists/ethereum.jsonThe vault, allowance, transfer, donate and dividend commands (vault-deposit,
vault-withdraw, vault-withdraw-usdc, reset-allowance, transfer-equity,
donate-equity, dividend-bump) take the same --network flag and run on that
chain's [chains.<name>.trading] table (no --registry: they need the chain's
orderbook). transfer-equity records the chain it ran on and is resumed on that
same chain:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml vault-deposit --amount 10 --token 0x... --vault-id 0x... --network ethereumManual cancellation of an open Alpaca order by the id printed at placement:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml cancel 61e7b016-9c91-4a97-b912-615c9d365c9dA cancel for an id the broker does not know reports it as unknown, and a cancel for an order that already filled or was cancelled reports it as no longer cancelable — neither is an error.
Manual repair of local position tracking after an operator trade or rebalance:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml position set --symbol SPYM --zero --reason "manual rebalance completed"
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml position set --symbol SPYM --long 100 --price 200 --reason "manual buy not observed by bot"
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml position set --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.
position set is rejected while the symbol still has a pending offchain hedge
order; resolve it first with position release-hedge, then retry.
After verifying a missing daily snapshot mark against a historical-price source, set the preceding regular-session close through the audited CQRS repair command:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml portfolio-snapshot set --day 2026-07-20 --symbol AAPL --usd-mark 211.18 --observed-at 2026-07-17T20:00:00Z --source "Nasdaq historical close" --reason "repair missing snapshot mark"The repair updates every captured location for that symbol and day without changing the live position price. If the read model needs recovery, stop the bot first (the rebuild replays events read at its start; concurrent captures would force it to be re-run), then replay all captured balances and corrections with:
cargo run -p st0x-cli -- --config path/to/config.toml --secrets path/to/secrets.toml view rebuild --aggregate portfolio-snapshot --allAlpaca 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 and the protection bound on extended-hours limit orders;
broker.extended_hours_reprice_timeout_secs, the ordinary extended-hours
reprice cadence; broker.close_flatten_reprice_timeout_secs, the faster cadence
inside close-flatten; broker.extended_hours_close_flatten_window_secs, the
length of the final window before weekends, exchange holidays, or an unknown
next session; and broker.close_flatten_cross_max_bps, the maximum cross at the
session close. The maximum cross must be at least counter_trade_slippage_bps,
since the ramp starts there, and no more than 9,999 bps, the global
counter-trade slippage ceiling. All five are required and have no implicit
defaults. Ordinary extended-hours orders retain their 300-second timeout, while
close-flatten orders use the dedicated 60-second timeout and cross progressively
wider until the session closes.
Extended-hours limit orders use an ordered reference chain: an optional current
bid/ask quote source, the broker's position mark, then an emergency
delayed_sip quote. The current deployment has no primary quote provider, so
its effective behaviour remains mark first, delayed SIP second. The executor
capability is already present for a future source such as Alpaca SIP, and the
mark remains the fallback if that source is missing or fails. No market-data
feed config is exposed until a real provider is selected. See
ADR 0019.
Inside the close-flatten window the cross ramps linearly from
counter_trade_slippage_bps at the window's start to
close_flatten_cross_max_bps at the close, so each reprice crosses further than
the last and the bot converges on a fill before the gap. Outside it,
extended-hours orders keep the flat counter_trade_slippage_bps band. Only once
every reference source has failed does the attempt dead-letter (counted by
hedge_dead_lettered_total{symbol,reason}). Transient failures in queued
PlaceHedge attempts, such as timeouts and 5xx, receive three durable redrives
after 1s, 2s, and 4s; exhausting that budget increments the same dead-letter
metric. Scan-time transient and rate-limited preflight failures instead wait for
CheckPositions to re-enqueue the hedge on its next scan.
The liquidity bot, dashboard, and datasette run as OCI containers on a Google Compute Engine VM. Nix builds the container images reproducibly; the VM pulls its own deploy state and runs them. Staging and production are separate GCP projects.
GitHub Actions
|
| build OCI images (Nix), push to Artifact Registry, sign attestation
v
Artifact Registry (europe-west3-docker.pkg.dev/t0-artifacts/t0-liquidity)
|
| CD rewrites images.env in the GCS config bucket
v
GCE VM (roll timer polls images.env, verifies the liquidity-image
attestation, then restarts the container stack)
- bot (server binary; also ships /bin/st0x-cli for operators)
- dashboard (nginx serving the SvelteKit build, proxies the API to bot:8001)
- datasette (SQLite database explorer)
- exporter (metrics; image managed by t0.devops terraform)
The VM verifies the Binary Authorization liquidity-image attestation of every digest before it adopts it, so it never runs an unsigned image. A digest counts as deployed only after the container stack restarts successfully; an image that crash loops leaves the deploy failed rather than published.
At startup, the bot validates staged config/secrets and, for Turnkey wallets, runs a read-only policy coverage check for every startup MAX approval. Coverage requires an allow policy whose consensus the authenticated API user can satisfy alone and whose target condition provably applies; applicable or unprovable denies take precedence. Missing token or wrapper coverage fails startup with the symbol and contract details.
| 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 |
Reach a VM over IAP with gcloud (docs/cli-ops.md covers the CLI it runs):
# Staging
gcloud compute ssh t0-liquidity-staging --project t0-liquidity-staging \
--zone europe-west3-b --tunnel-through-iap
# Production
gcloud compute ssh t0-liquidity --project t0-liquidity \
--zone europe-west3-b --tunnel-through-iapService 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 the packages, runs tests and clippy inside Nix derivations, and builds the dashboard. It never reaches a deployed machine. Runs on pull request activity and pushes tomaster. - CD (
.github/workflows/build-oci.yml): on every push tomaster, builds the OCI images with Nix, pushes them to Artifact Registry, signs the Binary Authorization attestation, and rewritesimages.envin the staging config bucket so the staging VM adopts the new digests. Authenticated with a GitHub OIDC token federated into Google IAM: no stored keys and no human identity, and each run records the person who scheduled it. - Release (
.github/workflows/release-tag.yml): pushing avX.Y.Ztag labels the images already built and attested for that commit and cuts the GitHub release. It does not build or deploy. - Production is promoted from
t0.devops, not this repo: promote a digest proven in staging intoterraform/production-liquidity/images.yamland merge. The apply requires 2 of 4 approvers (Juan, Alastair, Kais, Josh).
Track staging and production deploys in the Grafana deployments dashboard: https://grafana.t0trade.com/d/t0-deployments/deployments
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 the full-system chaos eventual-consistency e2e
test (full_system_concurrent) with mprocs
running the dashboard and bot side-by-side. Trades fire in randomized order with
delayed broker fills; between rounds the test injects chaos (bot restarts, NAV
bumps, asset add/remove, broker latency) and then asserts hedging, mint, and
USDC rebalancing still converge. Open http://localhost:5173 to watch the
dashboard while it runs. Set SIMULATE_EXIT_AFTER_CHAOS=1 to exit once
assertions pass instead of idling for dashboard inspection.
nix run .#simulate-market runs the infinite market simulation instead —
continuous user trades at ~10-second intervals. Use this when you want to
observe long-running liquidity cycling rather than a single bounded chaos
scenario.
nix run .#simulate-14d starts the same stack as simulate-market, but
preloads 14 days of seeded hedge-latency, mint, redemption, and USDC-rebalance
history so Performance tab trends and the Transfers panel are populated
immediately.
nix run .#simulate-trade-outcomes starts the same stack as simulate-market,
but rotates every counter-trade through the three outcomes the trade history
renders: filled, rejected by the broker, and cancelled by the broker after a
partial fill. Use it to inspect the Status column, the venue/asset/time-range
filters, and the accepted/filled/unfilled breakdown in the detail panel without
waiting for a real failure.
nix run .#simulate-failures starts the same stack as simulate-market, then
creates failed mint and redemption rebalances whose mock Alpaca provider later
completes and prints the transfer recheck commands that recover them.
What simulate-market 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 simulate-14d variant also preloads 14 days of history -- hedge-latency
cycles, equity mints, equity redemptions, and USDC rebalances (alternating
Alpaca<->Base direction) -- before live trades begin, so the Performance tab's
percentile charts and rebalance-stage breakdown, and the dashboard's Transfers
panel, all show a trend immediately instead of waiting for historical data to
accumulate. The dashboard's default 1W view renders the most recent week of
that seed at daily granularity; switch to 2W to see the full 14-day history,
still at daily granularity (within ~12h of the bot starting -- the seed is a
fixed point in time, so a much longer-running session ages its oldest day out of
the 2W window).
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.
Press Ctrl-C to stop.
Workspace crates:
st0x-hedge(root) - Main arbitrage bot: event loop, CQRS/ES aggregates, conductor, and dashboard backendst0x-cli(crates/cli/) - Operator command-line applicationst0x-config(crates/config/) - TOML/secrets loading and runtime context assembly; restricted to thest0x-hedgeandst0x-cliapplication cratesst0x-dto(crates/dto/) - Dashboard DTOs and TypeScript binding generationst0x-execution(crates/execution/) - StandaloneExecutortrait abstraction with Alpaca Broker API and mock implementationsst0x-tokenization(crates/tokenization/) - StandaloneTokenizertrait abstraction with Alpaca tokenization API and mock implementationsst0x-bridge(crates/bridge/) - Cross-chain bridge abstractions and CCTP implementationst0x-raindex(crates/raindex/) -Raindextrait and shared domain types for Rain OrderBook vault operationsst0x-registry(crates/registry/) - Shared reference-data registry:SymbolCache(token address -> symbol) and per-symbolget_symbol_lockst0x-wrapper(crates/wrapper/) -Wrappertrait and ERC-4626 wrap/unwrap domain typesst0x-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))
st0x-event-sorcery is an external git dependency (lives in the separate
event-sorcery repo) and is
not a workspace crate.
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 core service secrets
└── st0x-hedge-pricing.toml.age # encrypted pricing credential overlay
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)Debug builds use debug = "line-tables-only" (workspace Cargo.toml) and the
Linux dev shells link with mold (flake.nix), which keeps the st0x-hedge test
binary well under 1 GB and takes about 20 s of link time off every test cycle.
Panics still report file and line. The default and ci-backend shells refuse
to start unless mold is on PATH and the target rustflags variable carries
-fuse-ld=mold, so cargo cannot silently fall back to the stock linker. To
inspect a binary anyway:
readelf -p .comment target/debug/deps/st0x_hedge-<hash> | grep mold. Nix
release builds (nix build, OCI images) are not affected.
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 |
|---|---|---|
prodDeployNixos |
nix run .#prodDeployNixos |
Deploy prod NixOS system config only |
prodDeployService |
nix run .#prodDeployService <profile> |
Deploy a single prod service |
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 |
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)
- 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.