Account Demolisher
Concepts

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 close

The 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

AreaPathResponsibility
Orchestrationsrc/lib/orchestrator/The flow machine, its actors, the executor, failure taxonomy, journal
Auditsrc/lib/stellar/Account audit, mergeability, path finding, classic transaction builder
DeFi adapterssrc/lib/adapters/One directory per protocol (blend, aquarius, soroswap), positions
Plansrc/lib/plan/Generator, tree, classic batcher, hydration, simulator, price impact
Sorobansrc/lib/soroban/RPC, simulation, allowances, held tokens, sign-time guards, fee caps
Safetysrc/lib/safety/Exchange registry, scam heuristics, slippage, high-value gate
Walletsrc/lib/wallet/Connector abstraction over Stellar Wallets Kit, secret-key connector
Mediatorsrc/lib/mediator/Flow client, forward construction, envelope validator, recovery records
Multisigsrc/lib/multisig/Signing requests, partial-XDR merging, bundle freshness
Metricssrc/lib/metrics/The usage beacon, event schema, on-chain close marker
Serversrc/server/Mediator secret, signing relay, rate limiter, metrics store, close check
Configsrc/lib/config/Networks, pinned contracts, CSP, public environment

Server surface

RoutePurpose
GET /api/mediator/signMints an exchange-forward flow bound to a destination, network and memo
POST /api/mediator/signCo-signs one forward envelope that meets the flow's terms
POST /api/mediator/reissueRenews a flow token, expired or not, while its forwarding account exists
/api/plan, /api/plan/[id], /api/plan/[id]/sign, /api/plan/[id]/eventsThe multisig signing relay
POST /api/metricsRecords a visit, a funnel step, or a close it verifies on Horizon
GET /api/metricsThe 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:

  • generatePlan is 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.
  • batchClassicDemolition is a pure function from an account audit and the resolved paths to an ordered list of classic batches.

Trust boundaries

BoundaryTrustWhat bounds it
BrowserHighestThe account owner's key lives here or in the wallet and nowhere else
WalletSeparate signerShows its own confirmation. The app verifies the signed envelope is the transaction it built
ServerTrusted for every closeServes 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 RPCUntrustedThey 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
LumeusUntrustedOptional 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 contractsUntrustedOnly 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.

On this page