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.
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 | ETH → ERC20, BSC → BEP20, TRON → TRX, POLYGON → MATIC |
| Deposit record creation | XT → platform name | ERC20 → ETH, BEP20 → BSC, TRX → TRON, MATIC → POLYGON |
| Deposit verification | Platform name → XT's long form | TRC20/TRX → Tron, ERC20/ETH → Ethereum, BEP20/BSC → BNB Smart Chain, POLYGON/MATIC → Polygon, AVAX → AVAX C-Chain, SOL → SOL-SOL, BTC → Bitcoin, LTC → Litecoin, DOGE → Dogecoin, ARBITRUM → ARB, OPTIMISM → OPT, ETC → Ethereum Classic, BCH → Bitcoin Cash, BASE → BASE |
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/ETHEREUM → ERC20, BSC/BINANCE/BNB → BEP20, TRX/TRON →
TRC20, POLYGON/MATIC → POLYGON, AVALANCHE/AVAX → AVAX). 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
-
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. -
The platform asks XT for an address. It tries
fetchDepositAddressesByNetwork, thenfetchDepositAddresses, thenfetchDepositAddress. On the last of those, and only there, the XT network map is applied to the network name. -
The customer sends coins and submits the transaction id. The platform maps the chain name back to its own form, creates a
SPOTwallet if the customer does not have one for that currency, and records aDEPOSITtransaction keyed on the transaction id.The transaction id is unique per deposit: submitting one twice returns "Transaction already exists" rather than crediting twice.
-
Verification polls XT. A WebSocket-driven schedule calls
fetchDepositsfor that currency, passingchainas XT's long-form network name. It looks for a deposit whose reference matches. -
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
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.
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.