Account Demolisher
How it works

Account audit

What is read from Horizon, how the merge verdict is computed, and which conditions block a close.

auditAccount(publicKey, network) in src/lib/stellar/account-audit.ts produces the read-only snapshot the plan is built from. It is uncached: every call reads Horizon again.

What it reads

CallNotes
loadAccount(publicKey)Balances, signers, thresholds, flags, data entries, sponsorship counts
The latest ledgerRead after the account, so the clock is never older than the state
offers().forAccount(publicKey).limit(200)Pages until a page holds fewer than 200
claimableBalances().claimant(publicKey).limit(200)The same 200-per-page loop
liquidityPools().liquidityPoolId(poolId)One per pool-share trustline, in parallel
loadAccount(issuer)One per distinct issuer of a claimable-now credit balance the account has no trustline for

Offers, claimable balances and pools load concurrently. A pool read that fails fails the whole audit with one error naming every failed pool, because the pool reserves size the withdraw floors. Issuer lookups run eight at a time, each retried on a rate limit or server error. A missing issuer leaves its balances untagged; any other failure fails the audit.

The ledger clock (LedgerClock: sequence and close time) travels with the audit. Claimable-balance predicates, transaction time bounds and the merge sequence ceiling are judged against it, never against the browser's clock.

Details of the parse

The master key is the signer whose key equals the account id, not the first ed25519 signer. masterWeight is its weight, or 0 when no such signer exists.

A claimable balance is marked claimableNow only when this account's predicate holds at the ledger close time and stays true for the whole signing window (300 seconds by default).

An untrusted credit claimable balance carries issuerAuthRequired, the issuer's auth_required flag. false means the close can open the trustline and then claim. A balance in the account's own asset is tagged issuedByAccount instead: the claim burns it and needs no trustline.

Account not found

A Horizon 404 raises AccountNotFoundError with the message Stellar account not found on this network: <publicKey>. Any other error is rethrown unchanged, so a server error is never mistaken for a missing account.

Coverable sponsorships

An account cannot sponsor its own trustlines, offers, signers or data entries, so the only entries in num_sponsoring that the close releases on its own are claimable balances the account created with itself as a claimant. computeCoverableSponsorships(accountId, claimableBalances, claimedBalanceIds) counts those that are claimable now and in the given claim list, once per claimant, because the network counts a sponsored claimable balance once per claimant.

The stored audit value, sponsorship.coverable, assumes every balance the audit sees as collectable is claimed: claimable now and, for a credit asset, the account's own asset, an authorized trustline with room left (after what the pool withdraws credit to it), or no trustline and an issuer that does not require authorization. Everything in num_sponsoring beyond coverable is sponsorship for another account.

The merge verdict

The audit stores a first verdict, computeMergeability(flags, sponsorship):

  1. flags.authImmutable gives AUTH_IMMUTABLE.
  2. numSponsoring - coverable > 0 gives IS_SPONSOR, with a detail naming the count.
  3. Otherwise mergeable.

The full check is assessMergeability(audit, { ledger, claimedBalanceIds, transactionsBeforeMerge }). The preview runs it against the claims the plan really makes and every transaction the account still sources before the merge, and the executor runs it again on a fresh audit before every classic phase. It returns the first blocker in the order a user resolves them:

ReasonCondition
AUTH_IMMUTABLEThe auth_immutable flag is set
FROZEN_TRUSTLINEA positive credit balance on a trustline the issuer deauthorized
IS_SPONSORnum_sponsoring exceeds the self-sponsored claimable balances the plan claims
SEQNUM_TOO_FARThe sequence number after the transactions before the merge is at or above (next ledger) << 32, which the network refuses as ACCOUNT_MERGE_SEQNUM_TOO_FAR

The SEQNUM_TOO_FAR detail names the first ledger the merge can land in and an estimate of the wait.

Multisig detection

computeRequiresMultisig is masterWeight === 0 || masterWeight < max(low, medium, high). The close touches all three thresholds: the merge and set_options are high, path payments, payments, change_trust and offers are medium, and claims are low. The three are independent, so medium or low can sit above high.

What blocks the page

Two conditions take over the page before any plan is built:

BlockCondition
Auth immutableflags.authImmutable
Sponsoring for othersnumSponsoring > coverable on the stored audit

Everything else that keeps a close from starting is a review blocker: the plan-specific mergeability verdict, a balance with no disposal, a refused conversion, a failed required step, and the rest listed in Clear a blocked closure.

Execution and recovery covers the checks the executor runs before the merge.

On this page