Account Demolisher
Protocol

Exchange integration

For exchange operators. What a close delivers to your deposit address, how the registry and memo rules work, and how to add an entry.

Account Demolisher is a non-custodial closer that people run in their own browser. It is not a service you call, and it holds no user keys. Someone can close an account into your deposit address without any integration on your side.

Whether that close is credited depends on one thing: the close has to arrive as a payment carrying the deposit memo. The app does that for every destination it knows needs a memo: an address in its compiled-in registry, or an account that publishes the SEP-29 memo flag.

Why a plain merge is not enough

ACCOUNT_MERGE moves an account's whole balance, including its reserve, to a destination. Exchanges credit payments carrying a deposit memo, not merges, so a merge lands at your address with nothing that attributes it to a customer.

What a memo-required destination receives

The close routes through an ephemeral mediator account created for that one close:

  1. The closing account funds a fresh mediator with 2 XLM and merges into it, in one transaction.
  2. The server co-signs a two-operation forward from the mediator: a native PAYMENT to your deposit address carrying the memo, then an ACCOUNT_MERGE of the mediator into the same address.
  3. The mediator account is removed by that same transaction.

From your side this is a native XLM payment with a memo, followed in the same transaction by an ACCOUNT_MERGE of about 1 XLM. The payment carries everything above the mediator's minimum balance and the forward's fee. The merge carries the rest: the mediator's minimum balance, two base reserves (1 XLM at the current 0.5 XLM base reserve), plus whatever part of the fee the network did not charge. An exchange that credits only payments does not credit that remainder. You do not need to run a SEP server, expose an API, or change how your deposit addresses work.

Only XLM reaches a memo-required destination. The close sells every other balance for XLM first, or returns it to its issuer when it has no market, and it never sends a Soroban token or a classic credit balance there.

The mediator forward protocol has the cryptographic detail.

The registry

src/lib/safety/cex-registry.ts. Each entry is a CexInfo:

export interface CexInfo {
  readonly address: string; // G... deposit account id
  readonly name: string; // name shown in the UI
  readonly requiresMemo: boolean;
  readonly memoType?: CexMemoType; // "text" | "id" | "hash" | "return"
  readonly acceptedMemoTypes?: readonly CexMemoType[]; // instead of memoType, when several are accepted
  readonly minimumDeposit?: string;
  readonly verifiedAt: string; // ISO date the address and policy were checked
  readonly source: string; // citation for both
}

Thirteen entries, every one requiring a memo:

ExchangeAddressMemo typePublished minimum
KrakenGA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKTMtext1 XLM
BinanceGAHK7EEG2WWHVKDNT4CEQFZGKF2LGDSW2IVM4S5DP42RBW3K6BTODB4Aid0.1 XLM
Binance DepositsGABFQIK63R2NETJM7T673EAMZN4RJLLGP3OFUEJU5SZVTGWUKULZJNL6id
BitfinexGAWPTHY6233GRWZZ7JXDMVXDUDCVQVVQ2SXCSTG3R3CNP5LQPDAHNBKLtext
BitstampGA3NTBDIKQVDDM6ZDKJLGXJFESWJ636AGRIW34RH5WL24LUMX3YASKX2id
KuCoinGAJ4BSGJE6UQHZAZ5U5IUOABPDCYPKPS3RFS2NVNGFGFXGVQDLBQJW2Ptext
UpbitGAW4E6NGM4NPNX2LO2BKDPCCTUX3FJLKWHPU4VQPGBIBQGD6JTVF5C7Ctext
Upbit DepositsGCZLCMUM4Q4H324CTT3753P6656CYR4KPLHYFPGYPGE6JVHONGLNRDI5id or text
CEX.IOGB3RMPTL47E4ULVANHBNCXSXM2ZA5JFY5ISDRERPCXNJUDEO73QFZUNKtext
Crypto.comGB67TJFJO3GUA432EJ4JTODHFYSBTM44P4XQCDOFTXJNNPV2UKUJYVBFtext
RobinhoodGB2ES2N326MZK4EGJBKN3ZARCQ5RTFQSAWIJAAKFVIIIJSCC35TXIMLBtext
Coinbase DepositsGB5CLRWUCBQ6DFK2LR5ZMWJ7QCVEB3XKMPTQUYCDIYB4DRZJBEW6M26Dtext
Blockchain.comGARAR5QR7WRL24MQMSO4INWV7C5SE4EE2YVXTLD6ORONYFHSUAGZYSLNtext

Each entry's source cites the stellar.expert exchange directory, plus the exchange's own deposit documentation where one exists. Upbit shows deposit addresses only to logged-in customers, so the two Upbit entries rest on the directory alone, which tags both as exchange accounts that require a memo. Upbit Deposits accepts an id or a text memo, because no Upbit-published source names the type.

minimumDeposit records the published terms. No code path reads it, so it does not gate a close.

Refused destinations

REFUSED_DESTINATIONS lists exchange accounts that are not deposit addresses: seven Upbit cold storage, cold wallet and withdrawal accounts from the same directory. A close to one is refused whatever the memo, and the mediator route refuses to mint a flow for one (REFUSED_DESTINATION), because a merge into an exchange's internal wallet is credited to no customer.

Adding or correcting an entry

The registry is source code, not runtime configuration. A pasted or discovered address never adds itself, which is the same posture the contract allow-list takes.

  1. Append a CexInfo to KNOWN_CEXES with your deposit address, name, requiresMemo, the memoType your deposits need (or acceptedMemoTypes when you accept several), and a source citing where the address and policy were verified.
  2. lookupCex(address) then returns your entry, closes to it route through the mediator, and the memo gate enforces your memo type.

Keep verifiedAt and source accurate. They make the address auditable, and a wrong address here sends funds to the wrong place.

Publishing config.memo_required under SEP-29 on your deposit account already routes closes through the mediator without a registry entry, with any well-formed memo accepted.

Memo enforcement

requireMemoEnforcement(destination, memo, { sep29MemoRequired }) gates the close before it starts. The Configure step reads the destination from Horizon first, and refuses to continue while the destination does not exist or cannot be read, because an unreadable destination is not confirmed free of a memo requirement. The checks then run in this order:

#ConditionOutcome
1The destination is a refused exchange walletRefused
2Not in the registry, and no SEP-29 flagAllowed with no memo requirement
3Not in the registry, SEP-29 flag setA non-empty, well-formed memo of any type
4In the registry, no memo suppliedRefused, naming the exchange and the memo type
5The memo type is not one the entry acceptsRefused
6The memo value is empty after trimmingRefused
7The value is malformed for its typeRefused with the format error

Format rules, from validateMemoFormat:

TypeRule
idDigits only, in the uint64 range. Surrounding whitespace is trimmed
textAt most 28 bytes, counted as UTF-8 bytes, not characters
hash, returnExactly 32 bytes, as 64 hex characters or base64 (44 characters with padding)

The memo builder decodes hash and return values with the same function, so a value that passes always encodes. validateMemoFormat also runs in the general pre-flight, so a malformed memo never reaches a transaction builder on any destination.

The memo's content cannot be checked against your books from a browser, so the app validates only the type and shape. The person closing the account is responsible for the value. Review shows the memo, and the mediator binds it into the flow token so the forward carries exactly that memo.

Closes to an address with no memo requirement

They run as a direct ACCOUNT_MERGE to the address, and a memo the person closing the account enters rides on the merge transaction. An exchange that credits only payments does not credit that, and Send the funds to an exchange tells people to withdraw to a wallet they control and send a normal payment instead.

Limits

Standalone Soroban tokens are not delivered. A memo-required close forwards XLM only, so the app tells the person closing the account to move standalone tokens they want to keep to a personal wallet first.

A shared account cannot close to a memo-required destination. The mediator route is several transactions, and a multisig signing request collects signatures on exactly one.

A self-hosted build does not run the mediator. A deployment built with NEXT_PUBLIC_DEPLOYMENT_MODE=self-hosted refuses closes that need it.

Running your own instance

An exchange that wants to run the closer under its own domain with its own mediator secret can self-host it. The secret is a seed used only to derive throwaway per-flow keys, so you generate one and fund nothing. See Self-hosting.

On this page