# Token Transfers

Use token transfers to move collateral between a wallet and a Meridian subaccount. For internal balance moves between quote-token pools, see [Token Conversions](/developer-guides/trading-api/token-conversions).

## Get Token Configuration

Do not hard-code token addresses, decimal places, fees, or minimum amounts. Get the current configuration from the API:

```http
GET /v1/token?order=asc&limit=100
```

For each token, check these fields before you build a transfer:

* `address` and `erc20Decimals`
* `depositEnabled` and `withdrawEnabled`
* `depositFee` and `withdrawFee`
* `minDeposit`

API balance amounts and signed transfer amounts use decimal strings with a maximum precision of 9 decimal places. An ERC-20 contract call uses the token's native decimal places.

## Deposit

A deposit is an onchain transaction to the exchange contract. It has this sequence:

1. Get the exchange address from `GET /v1/rpc/config`.
2. Get the deposit token from `GET /v1/token`.
3. Approve the exchange contract to spend the ERC-20 token.
4. Call `deposit` on the exchange contract.
5. Wait for the API to process the confirmed onchain event.
6. Check the balance with `GET /v1/subaccount/balance`.

The deposit transaction creates a pending deposit onchain. The sequencer later includes a deposit action in an action batch. The contracts then move the pending amount into the subaccount balance. The deposit is not available for trading before this second onchain step is complete.

The deposit function is:

```solidity
function deposit(
    bytes32 subaccount,
    address depositToken,
    uint256 amount,
    bytes32 referralCode
) external;
```

`subaccount` is a UTF-8 name that is right-padded with zero bytes to 32 bytes. `amount` is in the ERC-20 token's smallest unit. Use `bytes32(0)` when there is no referral code.

The first valid deposit for an owner registers a subaccount. The API can show the new subaccount before its `registeredBlockNumber` is available. Wait for registration before you submit an order.

:::warning
Send deposits only through a supported exchange contract function. A direct ERC-20 transfer to the exchange contract does not identify the destination subaccount.
:::

## Check a Deposit

Query the current balance:

```http
GET /v1/subaccount/balance?subaccountId=SUBACCOUNT_UUID
```

Query transfer history:

```http
GET /v1/token/transfer?subaccountId=SUBACCOUNT_UUID&types=DEPOSIT&orderBy=createdAt&order=desc
```

Each balance record identifies one token pool. The important values are:

* `amount`: the recorded token balance.
* `available`: the balance that is available for new orders or withdrawal.
* `totalUsed`: the balance that orders and positions use.

## Withdraw

A withdrawal has an offchain authorization step and an onchain transfer step:

1. The account owner signs an `InitiateWithdraw` EIP-712 message.
2. The client sends the signed request to `POST /v1/token/{tokenId}/withdraw`.
3. The sequencer validates the request and submits the withdrawal action onchain.
4. The contract records a pending withdrawal and debits the subaccount balance.
5. The account owner or an authorized claimer calls `finalizeWithdraw`.
6. The contract transfers the token to the destination address.

A linked signer cannot authorize a withdrawal.

The request has this form:

```json
{
  "data": {
    "account": "0xOWNER_ADDRESS",
    "subaccount": "0xBYTES32_SUBACCOUNT",
    "token": "0xTOKEN_ADDRESS",
    "amount": "100.5",
    "lzDestinationAddress": "0xLEFT_PADDED_BYTES32_ADDRESS",
    "lzDestinationEid": 0,
    "nonce": "1785811200000000000",
    "signedAt": 1785811200
  },
  "signature": "0xEIP712_SIGNATURE"
}
```

For a same-chain withdrawal, set `lzDestinationEid` to `0`. Encode the destination EVM address as a left-padded `bytes32` value in `lzDestinationAddress`.

The exchange checks the available balance after it includes unrealized funding and position-fee adjustments. It rejects a request that would make the effective balance insufficient. Unrealized trading profit is not withdrawable collateral.

## Finalize a Withdrawal

The exchange normally uses an authorized claimer to finalize eligible withdrawals. The owner can also finalize a pending withdrawal:

```solidity
function finalizeWithdraw(address account, bytes32 withdrawDigest) external payable;
```

Get `withdrawDigest` and the current state from:

```http
GET /v1/token/withdraw?subaccountId=SUBACCOUNT_UUID&orderBy=createdAt&order=desc
```

Do not submit the same digest more than once. The contract deletes the pending record before it transfers funds.

## Cross-Chain Withdrawals

The contracts can use LayerZero OFT for a configured cross-chain USD withdrawal. This function is available only when Meridian has configured the destination route. Non-USD tokens cannot use this route.

Robinhood Chain testnet does not currently have a configured OFT route. Use a same-chain withdrawal in that environment. See [Contracts and Networks](/protocol-reference/contracts).

## Transfer Status

Use these endpoints for status and history:

* `GET /v1/token/transfer` lists transfer activity. Use `types` to filter deposits or withdrawals.
* `GET /v1/token/withdraw` lists withdrawal requests and their finalization state.
* `GET /v1/subaccount/balance` shows the current balance for each token pool.

Treat the API response and the onchain receipt as separate evidence. A successful API request means that Meridian accepted the request. It does not mean that the final onchain transfer is complete.

See [Message Signing](/developer-guides/trading-api/message-signing) for signing fields and timing rules.
