Deposits and withdrawals

How money moves in and out of an XT-backed SPOT wallet — the three different network-name tables, the deposit verification loop, the withdrawal status mapping, and where a transfer can end up invisible.

6 min readUpdated 3 August 2026deposits, withdrawals, networks, spot-wallet, memo

A SPOT wallet is a mirror. The coins are in your XT account; the wallet row is the platform's record of which customer is owed what. Every deposit is a deposit to XT, every withdrawal is an XT withdrawal, and both directions pass through network-name translation that is specific to this provider.

That translation is where most operator pain on this product comes from, so it is worth understanding before you take a single deposit.

The platform carries three separate XT network maps, in three files, for three different calls. They do not agree with each other, and none of them falls back to the others. A chain covered by one and not another produces a deposit that arrives on-chain and is never credited.

The three network maps

Where Direction Example values
Deposit address lookup Platform name → XT ETHERC20, BSCBEP20, TRONTRX, POLYGONMATIC
Deposit record creation XT → platform name ERC20ETH, BEP20BSC, TRXTRON, MATICPOLYGON
Deposit verification Platform name → XT's long form TRC20/TRXTron, ERC20/ETHEthereum, BEP20/BSCBNB Smart Chain, POLYGON/MATICPolygon, AVAXAVAX C-Chain, SOLSOL-SOL, BTCBitcoin, LTCLitecoin, DOGEDogecoin, ARBITRUMARB, OPTIMISMOPT, ETCEthereum Classic, BCHBitcoin Cash, BASEBASE

The third table is the one that bites. It returns null for any chain it does not list — not the input unchanged, null. When it returns null, the deposit verification call goes to XT without a chain filter.

There is also a fourth, broader mapping used when the platform asks XT which network id corresponds to a chain name at address-generation time (ETH/ETHEREUMERC20, BSC/BINANCE/BNBBEP20, TRX/TRONTRC20, POLYGON/MATICPOLYGON, AVALANCHE/AVAXAVAX). It only applies after the platform has checked whether the raw chain name is already one of the network keys XT returned, so it acts as a fallback rather than a substitution.

The operational rule: before you enable an asset on a chain, run one real deposit of a trivial amount and watch it credit. Do not infer that a chain works because the deposit address generated.

How a deposit works

  1. The customer picks a currency and network. The network list comes from XT live, not from your database — fetchCurrencies() on every load. If the connection is unauthenticated the list is empty.

  2. The platform asks XT for an address. It tries fetchDepositAddressesByNetwork, then fetchDepositAddresses, then fetchDepositAddress. On the last of those, and only there, the XT network map is applied to the network name.

  3. The customer sends coins and submits the transaction id. The platform maps the chain name back to its own form, creates a SPOT wallet if the customer does not have one for that currency, and records a DEPOSIT transaction keyed on the transaction id.

    The transaction id is unique per deposit: submitting one twice returns "Transaction already exists" rather than crediting twice.

  4. Verification polls XT. A WebSocket-driven schedule calls fetchDeposits for that currency, passing chain as XT's long-form network name. It looks for a deposit whose reference matches.

  5. The wallet is credited. The network fee XT reports is deducted. If the fee is greater than or equal to the deposit, nothing is credited and the customer is told the deposit does not cover the network fee.

A currency mismatch between the wallet and the deposit XT reports voids the whole thing: the schedule stops and the transaction row is deleted.

If step 4 never matches, the coins are in your XT account and the customer's wallet stays at zero. Nothing alerts on this. The recovery path is manual — confirm the deposit in XT's own interface, then credit the wallet through the admin wallet tools. Check the network map first: an unmapped chain is the most common cause.

How a withdrawal works

Withdrawals go out over exchange.withdraw, with XT's network passed as a parameter:

withdrawResponse = await exchange.withdraw(
  currency, providerWithdrawAmount, toAddress, memo, { network: chain }
);

The platform then re-reads the withdrawal from fetchWithdrawals to pick up the real fee and status, and maps XT's status vocabulary onto its own:

XT status Platform status
SUCCESS COMPLETED
SUBMIT PENDING
REVIEW PENDING
AUDITED PROCESSING
AUDITED_AGAIN PROCESSING
PENDING PENDING
FAIL FAILED
CANCEL CANCELLED

A status XT returns that is not in this list is uppercased and used verbatim, which is how an unexpected value reaches your admin screens unchanged.

If the withdrawal cannot be found in the subsequent fetchWithdrawals call, the platform records the fee it expected and marks the withdrawal COMPLETED. That is an optimistic default. On a busy account, or when XT is slow to expose a new withdrawal over the API, a withdrawal can be marked complete before XT has finished reviewing it — and a later FAIL on XT's side will not walk that back. Reconcile withdrawals against XT's own records rather than trusting the platform status alone.

FAILED and CANCELLED both raise an error, which unwinds the customer's balance. The customer's funds are returned; the withdrawal does not silently disappear.

Who pays the network fee

When enabled, the network fee is taken from the withdrawal amount, so the customer receives less than they requested. When disabled, the platform absorbs it and XT is asked to send the full amount

This is a platform setting, not an XT one, and it changes the amount sent to XT. Note that it has no switch on the admin settings screen — the shipped default is off, meaning you pay the network fee on every spot withdrawal:

  • Enabled — the fee is subtracted before the call, and the fee recorded on the transaction is the one XT actually charged.
  • Disabled — the full requested amount is sent, the recorded fee is zero from the customer's point of view, and the network cost comes out of your XT balance.

Get this wrong in the second direction on a high-fee chain and every withdrawal costs you money you never charged for.

Memos and destination tags

XT publishes no memo metadata, so the platform records withdrawMemo: false on every XT network. The withdrawal call does pass a memo argument through to XT when one is supplied, so memos are not broken — but nothing in the imported data tells the customer that a memo is required.

For assets that need a destination tag or memo — and for exchange-to-exchange transfers generally — this is the difference between a credited deposit and a support ticket. If you list one of those assets, say so in the currency's description yourself.

Spot wallets must be enabled at all

None of the above happens if SPOT wallets are switched off platform-wide.

Master switch for spot trading and SPOT wallets. Admin → Trading → Settings → Enable Spot Trading. With it off, spot currencies are filtered out of wallet lists, the deposit screen and transfers
Deposit address expiration. Admin → System → Settings → Wallet → Security

Both are stored as text — the string "true" is on and anything else is off. Reading them as JavaScript booleans is a mistake the codebase makes explicit: every check compares against the string.

Reconciliation and timing

Two jobs keep the mirror honest. Both run on the cron worker, and both are no-ops when there is no active provider or when the ban switch is set.

Job Period What it does
processPendingSpotOrders 60 s Settles open spot orders against XT — full fills, partial fills, cancellations, rejections
processCurrenciesPrices 120 s Refreshes every stored currency price from XT's tickers
processSpotPendingDeposits 15 min Works through pending SPOT deposits

Check them at Admin → System → Cron. A stalled processPendingSpotOrders is the usual explanation for orders that fill on XT but never update in the platform.

One XT-specific accounting quirk

For a BUY, XT reports info.executedQty as the amount spent, not the amount bought. The platform divides it by info.avgPrice before crediting:

if (provider === "xt") {
  if (side === "BUY")  amount = executedQty / avgPrice;
  if (side === "SELL") amount = executedQty;
  cost = amount * avgPrice;
}

You do not need to do anything about this — it is handled. It matters when you compare the platform's order records against a CSV exported from XT and the buy quantities look different. They are the same trade expressed in different units.