Architecture
The module map, how a request flows through the app, and where the trust boundaries fall.
The app is a Next.js 16 application. One page drives the whole closure, backed by a state machine, a pure plan generator, and an executor that walks the plan on chain.
Request flow
A closure runs through /demolish, which is a React page wired to an XState machine.
/demolish (React)
└─ pageFlowMachine src/lib/orchestrator/page-flow-machine.ts
├─ discover
│ ├─ auditAccount Horizon
│ ├─ DirectContractProvider on-chain DeFi discovery
│ └─ enumerateAllowances Soroban RPC
├─ preview
│ ├─ resolveCreditPaths
│ ├─ generatePlan pure
│ ├─ hydratePlanTransactions
│ └─ simulateNode real Soroban RPC simulation
└─ execute
└─ executePlanTreeOnChain src/lib/orchestrator/executor.tsThe machine's states are idle, discovering, previewing, awaiting_confirmation,
executing, succeeded, failed, and cancelled.
Module map
| Area | Path | Responsibility |
|---|---|---|
| Flow machine | src/lib/orchestrator/page-flow-machine.ts | Screen sequencing, discovery and preview actors |
| Executor | src/lib/orchestrator/executor.ts | The on-chain topological walk |
| Audit | src/lib/stellar/account-audit.ts | Account state, mergeability, multisig detection |
| Discovery | src/lib/adapters/ | One subdirectory per protocol, plus positions/ |
| Plan | src/lib/plan/ | generator, tree, classic-batcher, simulator, hydration |
| Wallet | src/lib/wallet/ | Connector abstraction over Stellar Wallets Kit |
| Mediator | src/lib/mediator/ | Client, forward construction, envelope validator |
| Multisig | src/lib/multisig/ | Signing requests and partial-XDR merging |
| Server | src/server/ | Signing relay, mediator secret, rate limiting |
| Config | src/lib/config/ | Networks, contracts, CSP, environment |
Server surface
Three route groups, and nothing else:
| Route | Purpose |
|---|---|
/api/mediator/sign | Mints a flow and co-signs one validated forward envelope |
/api/plan, /api/plan/[id], /api/plan/[id]/sign, /api/plan/[id]/events | The multisig signing relay |
/api/soroswap | Server-side proxy to the Soroswap aggregator |
The server never receives a user key, never builds a transaction on a user's behalf, and never submits one.
Purity boundaries
Two core pieces take no network calls at all, which is what makes them testable and deterministic:
generatePlanis pure over(audit, positions, allowances, destination, options). Its only impurity is reading the clock to estimate a Blend backstop unlock date, and tests pass a fixed value.batchClassicDemolitionis a pure function from an account audit to an ordered list of classic batches.
Everything that touches the network sits outside those two.
Trust boundaries
| Boundary | Trust level | Why |
|---|---|---|
| Browser | Highest | The user's key lives here and nowhere else |
| Server | Bounded | Holds one HMAC master secret, never a user key; its powers are constrained by strict validators |
| Horizon and Soroban RPC | Untrusted for integrity | Reads are treated as hints and re-verified before signing |
| Wallet | Separate trusted signer | Shows its own confirmation and signs on its own |
| On-chain contracts | Untrusted | Checked against the allow-list immediately before every signature |
The security model covers what defends each boundary.
Rebuilding from fresh state
A plan you review is not the transaction that gets submitted. Before each classic phase the executor re-reads the account, re-runs the audit, re-resolves credit paths, and re-batches from that fresh state. Soroban nodes are rebuilt and re-simulated the same way.
This is what makes a stale sequence number self-correcting, and what stops a read from a hostile RPC at preview time deciding what you actually sign.
The docs site
This documentation is a separate Next.js app in docs/, tracked in the same repository.
It runs on port 3020 in development.