An encrypted-orderflow DEX on Stellar/Soroban. Swap orders are meant to be encrypted before they leave the user's browser, batched, and settled atomically — so no single party (not a validator, not the backend, not a miner-equivalent) can see or front-run an order before it executes.
Status: working proof-of-concept on Stellar testnet. Orders are encrypted in the browser with real t-of-n threshold encryption and are only decrypted, with verifiable validator shares, after their batch is sealed. Validators generate the key together and can run as separate processes. See Status before trusting this with real funds.
Live demo: link coming soon (Stellar testnet). No wallet extension needed: click Try demo wallet for a throwaway key in your tab, or connect Freighter. A banner shows exactly what's on-chain and what's simulated.
Run the whole system locally, with 5 validators in separate containers:
docker compose up --build # then open http://localhost:8080
docker compose stop validator-2 validator-4 # still works: 3 of 5 sufficeHosting on Render, Vercel or Netlify, and enabling on-chain sealing, are covered in docs/DEPLOY.md.
frontend/ React + TypeScript + Vite. Wallet connect (Freighter), swap
form, batch monitor, order history.
backend/ Express + TypeScript API. Order intake, batching, and the
bridge that calls the Soroban contracts.
contracts/ Soroban smart contracts (Rust): order-vault (batch
attestation) and settlement-engine (AMM + atomic settlement).
Each has its own README with setup/run instructions: frontend · backend · contracts
User signs in: Freighter signs a never-submittable challenge tx -> session token
User submits a swap
-> browser encrypts it to the validator group key
(ElGamal KEM on ristretto255 + AES-256-GCM, bound to the user's address)
-> POST /api/orders carries ciphertext only; the backend stores it with no plaintext
-> batching loop seals pending orders into a batch
-> order-vault.create_batch seals it on-chain: sha256 over its ordered ciphertexts
-> each validator reads that seal from Soroban itself, then returns y_i*R plus a
DLEQ proof, refusing if the ciphertexts don't match; bad shares are discarded
-> any t valid shares combine to decrypt; each payload is validated
-> settlement-engine.settle_batch executes the swaps atomically against its AMM pool
-> orders revealed and marked settled (or failed, with a reason)
Full cryptographic design, threat model, and remaining gaps: docs/THRESHOLD_ENCRYPTION.md.
What's real and tested:
- Threshold encryption (
backend/src/crypto/): t-of-n ElGamal KEM over ristretto255 with AES-256-GCM, Shamir-shared keys, Chaum–Pedersen DLEQ proofs on every decryption share, and Lagrange combination. The browser runs a byte-identical copy of the encryption code, and a test enforces it. - Validator coordination (
backend/src/services/validatorService.ts): shares are collected per sealed batch, proofs are verified against registered keys, and up ton − tbyzantine validators are tolerated. A bad order fails on its own without blocking the batch. - Wallet-signature auth (
backend/src/services/authService.ts): a SEP-10-style challenge, signed in Freighter and verified as ed25519 against the claimed account. No more trusting a header. - Contracts (
contracts/):order-vaultandsettlement-engine, deployed to testnet.settle_batchexecutes a real x*y=k AMM swap with fees, verified against on-chain token balances. - Distributed key generation (
backend/src/crypto/dkg.ts): validators jointly generate the threshold key (Joint-Feldman with complaints), so no dealer ever knows the group secret. Shares travel encrypted to their recipient, and dealers who cheat are disqualified. - Validators as separate processes (
backend/src/validator/): each holds one share and an identity key, behind an authenticated HTTP API. The coordinator is treated as hostile: every DKG message is signed, and share-harvesting tricks abort the DKG instead of leaking. Runnpm run validators:devfor a 5-process cluster. - On-chain batch sealing, verified by validators
(
backend/src/chain/): each batch's ordered ciphertexts are committed toorder-vaultbefore decryption. Every validator reads that commitment from Soroban itself and refuses to decrypt anything that doesn't match, so a pending order can't be decrypted early, even by the backend. The batch monitor links each batch to its sealing transaction. - Deployable demo: Docker Compose stack (each validator container mounts only its own key), a Render blueprint, a demo wallet for reviewers without Freighter, per-IP rate limiting, and an in-app banner that reports which parts run on-chain and which are simulated.
- Tests and CI: 73 backend tests (crypto, DKG, malicious coordinator, remote validators, batch sealing, end-to-end pipeline, auth), 11 frontend tests, the contract suites, and a Docker image build. They run on every PR, together with a source-integrity check that blocks hidden or obfuscated code.
What's not real yet:
- The backend still chooses batch composition. It can't decrypt
without sealing publicly first, but it could censor orders or seal tiny
batches. Validators don't enforce a batch policy yet, and
settle_batchdoesn't yet cross-check the seal. - No independent operators yet. Validators can run on separate
machines, but today they're run by one party (in-process by default, or
validators:devon one host). See the design doc. - The on-chain vault records sealed batches, not individual orders. The backend database is still the mempool. Users can't yet submit orders directly on-chain.
- Schema setup uses
sequelize.syncrather than real migrations.
| Contract | Address |
|---|---|
| order-vault | CD6IHURX2DJIKYZLDD24VZTAL347NC6UUW5WYJEF6VJRCAYSULRWTNW4 |
| settlement-engine | CCXVBFI5D34VFKIKEHDOPLTED7UOFXNVJHEUZYAMXTFPNAFRIWRJYYCR |
See CONTRIBUTING.md for local setup and open issues for what's up for grabs — the gaps listed above are exactly where help is most needed.
