Architecture
The module map, how a close flows through the app, and where the trust boundaries fall.
The app is a Next.js 16 application served by next start behind a reverse proxy. One page,
/demolish, drives the whole close through an XState machine, a pure plan generator, and an
executor that walks the plan on chain.
Request flow
/demolish (React)
└─ pageFlowMachine src/lib/orchestrator/page-flow-machine.ts
├─ discover src/lib/orchestrator/actors/discover.ts
│ ├─ auditAccount Horizon
│ ├─ scanAllowances Soroban RPC events, then allowance() on chain
│ ├─ DirectContractProvider Blend, Aquarius, Soroswap positions
│ ├─ discoverHeldTokens standalone SEP-41 tokens
│ └─ gauge rewards and payout trustline gaps
├─ preview src/lib/orchestrator/actors/preview.ts
│ ├─ surgeFeeBase, resolveCreditPaths
│ ├─ generatePlan pure
│ ├─ hydratePlanTransactions builds each Soroban step
│ ├─ simulatePreviewTree Soroban RPC simulation
│ ├─ refuseUnsafeSteps the sign-time checks, run early
│ └─ assessMergeability
└─ execute src/lib/orchestrator/actors/execute.ts
├─ executePlanTreeOnChain src/lib/orchestrator/executor.ts
└─ submitBundle a collected multisig closeThe machine's states are idle, discovering, previewing, awaiting_confirmation,
executing, succeeded, prepared, failed and cancelled. prepared ends a run that only
prepares the close, because a Blend backstop withdrawal stays locked after it.
Module map
| Area | Path | Responsibility |
|---|---|---|
| Orchestration | src/lib/orchestrator/ | The flow machine, its actors, the executor, failure taxonomy, journal |
| Audit | src/lib/stellar/ | Account audit, mergeability, path finding, classic transaction builder |
| DeFi adapters | src/lib/adapters/ | One directory per protocol (blend, aquarius, soroswap), positions |
| Plan | src/lib/plan/ | Generator, tree, classic batcher, hydration, simulator, price impact |
| Soroban | src/lib/soroban/ | RPC, simulation, allowances, held tokens, sign-time guards, fee caps |
| Safety | src/lib/safety/ | Exchange registry, scam heuristics, slippage, high-value gate |
| Wallet | src/lib/wallet/ | Connector abstraction over Stellar Wallets Kit, secret-key connector |
| Mediator | src/lib/mediator/ | Flow client, forward construction, envelope validator, recovery records |
| Multisig | src/lib/multisig/ | Signing requests, partial-XDR merging, bundle freshness |
| Metrics | src/lib/metrics/ | The usage beacon, event schema, on-chain close marker |
| Server | src/server/ | Mediator secret, signing relay, rate limiter, metrics store, close check |
| Config | src/lib/config/ | Networks, pinned contracts, CSP, public environment |
Server surface
| Route | Purpose |
|---|---|
GET /api/mediator/sign | Mints an exchange-forward flow bound to a destination, network and memo |
POST /api/mediator/sign | Co-signs one forward envelope that meets the flow's terms |
POST /api/mediator/reissue | Renews a flow token, expired or not, while its forwarding account exists |
/api/plan, /api/plan/[id], /api/plan/[id]/sign, /api/plan/[id]/events | The multisig signing relay |
POST /api/metrics | Records a visit, a funnel step, or a close it verifies on Horizon |
GET /api/metrics | The aggregate metrics, behind a bearer token |
Held-token conversions go through the pinned Soroswap router on chain, so the server calls no swap or quote API.
Two pieces run outside the routes. src/proxy.ts sets the Content-Security-Policy on every
document response, and src/instrumentation.ts refuses to start the server when
TRUSTED_PROXY_HOPS is not a whole number of at least 1.
The server never receives a user key, never builds a transaction for a user to sign, and never submits one.
Purity boundaries
Two core pieces make no network calls, which keeps them deterministic and testable:
generatePlanis pure over(audit, positions, allowances, destination, options). Its only impurity is reading the clock to estimate a Blend backstop unlock date, which tests pass in.batchClassicDemolitionis a pure function from an account audit and the resolved paths to an ordered list of classic batches.
Trust boundaries
| Boundary | Trust | What bounds it |
|---|---|---|
| Browser | Highest | The account owner's key lives here or in the wallet and nowhere else |
| Wallet | Separate signer | Shows its own confirmation. The app verifies the signed envelope is the transaction it built |
| Server | Trusted for every close | Serves the app code every close runs, so a compromised server can change what the page builds, the destination included, before you sign. That holds for a direct close as much as for an exchange close. Holds the HMAC master that derives every forwarding account's key, which only an exchange close uses. Direct closes never use the mediator, but they still run the code the deployment serves |
| Horizon and Soroban RPC | Untrusted | They supply the state and simulations the plan is built from. Before each signature the app checks the call target, the authorization tree and the fee, so a lying endpoint cannot widen what is signed |
| Lumeus | Untrusted | Optional history index that adds candidates to the allowance, held-token and Aquarius scans. Each one is read on chain before the plan uses it |
| On-chain contracts | Untrusted | Only pinned entry points, plus a router-resolved Aquarius pool for a gauge claim, can be the root of a signed DeFi call, each held to the exact authorization tree it needs. Allowance revokes and token transfers target token contracts found by scanning the account, which anyone can create and send it, so each is held to one exact call shape |
The security model covers what defends each boundary.
Rebuilding from fresh state
The plan you review is not byte for byte what gets submitted. Before each classic phase the executor audits the account again, quotes the conversions again, and rebuilds the batch, and it refuses a conversion whose fresh price is worse than the reviewed one by more than the slippage tolerance. Each Soroban step is rebuilt and simulated again right before it is signed.
This makes a stale sequence number or time bound self-correcting, and it means an account that changed during review is closed as it is, within the limits the review set.
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.