USDC
Stable. USDC is one balance on MetaFlux. The perp collateral account and
the spendable spot-USDC balance are the same number. One exception: a
standard-mode account holds two — see the standard-mode split.
TL;DR
- One pool. A perp order, a spot buy, an Earn deposit and a withdrawal all spend the same USDC, gated on the same free collateral.
- The concept still carries several ids:
asset: 0on bridge and withdraw surfaces,asset: 100on spot market and balance surfaces, an ERC-20 on the EVM, and a separate contract on every external chain. - Two number planes.
/infoand most/exchangefields are whole-USDC decimal strings.bridge_withdrawand the EVM token are 6-decimal integers. Mixing them is an error of 10⁶. usd_class_transferis rejected on a unified account. There is no second pool to move to. A splitstandardaccount is the one exception — see the standard-mode split.
The four identities
One concept, four addressing schemes. This is the table to read before you write any code that names USDC.
| Surface | How USDC is addressed | Number plane |
|---|---|---|
| Perp collateral (the pool) | Not a ledger row. It is the account's own balance, read as account_value / withdrawable. Bridge deposits, bridge_withdraw and core_evm_transfer address it as asset: 0 | whole-USDC decimal string |
| Spot token | Asset id 100 — the quote of every */USDC pair and the id of the USDC row in markets_meta tokens[] and in account_state spot.balances[] | whole-USDC decimal string |
| EVM token | ERC-20 at the fixed predeploy 0x0000000000000000000000000000000000010000 | 6-decimal integer |
| External chains | Each chain's own USDC contract, held in MetaBridge custody | 6-decimal integer on the MTF wire |
asset: 0 and asset: 100 both mean USDC. They are not two currencies and,
on this network, not two balances either. 0 is the collateral-plane id the
bridge and withdraw paths use; 100 is the spot-ledger token id the market and
balance surfaces use. Which id an action wants is fixed per action — it is
not a free choice:
bridge_withdrawandcore_evm_transfertake0. Oncore_evm_transfer,asset: 100is rejected withasset not linked to an EVM contract— the spot USDC token id carries no EVM contract binding, because the EVM-side USDC is reached through the collateral-plane id instead.assetdefaults to0; leave it alone.- The spot market and balance surfaces —
markets_meta,account_statespot.balances[], and every*/USDCpair'squote— use100.
One pool
MetaFlux does not hold a separate spendable spot-USDC ledger. Every USDC
movement — perp margin, spot buys, spot-order escrow, Earn deposits, spot-margin
positions, account-to-account sends, EVM moves and external withdrawals — debits
and credits one account balance: the settled USDC balance that
account_state reports in its USDC row and folds into
account_value.
The spot token id 100 still exists. It names the */USDC pairs and it still
keys the escrow bucket that holds USDC locked behind a resting spot bid. What
it no longer keys is a second spendable balance.
Self-hosted networks differ. The unified rule is active from block 0 on the
public network (chainId 114514) and on mainnet (chainId 8964). A network
running any other chain id keeps two separate USDC ledgers — a spot balance
and a perp collateral balance — until its validators arm the change by a
two-thirds stake vote. On such a network usd_class_transfer is a working
action. Check which world you are in with eth_chainId.
Which balance your order spends
All of them spend the pool. What differs is the gate.
| You do this | It spends | Gate |
|---|---|---|
| Open or add to a perp position | the pool | initial margin ≤ free collateral |
| Place a spot buy | the pool | the buyable size is clamped to free collateral, not to any spot balance |
| Rest a spot bid | the pool | the quote cost moves out of the pool into escrow (hold) at admission |
| Place a spot sell | the base token on the spot ledger | you must own the base |
| Deposit to Earn | the pool | amount ≤ free collateral |
| Open a spot-margin position | the pool | its initial margin is held against the pool |
bridge_withdraw / core_evm_transfer / a send | the pool | amount ≤ free collateral |
Free collateral is the one budget every debit above is measured against:
free collateral = settled USDC balance
− initial margin held by open CROSS perp positions
− initial margin held by open spot-margin positions
− any impending funding CHARGE
The gate figure is signed; the published field is not. The admission and
withdrawal checks compare a debit against the raw value above, which goes
negative when open profit funds the held margin. The API publishes the same
budget as withdrawable, clamped:
withdrawable = max(0, free collateral)
Read it from account_state.withdrawable
or the lighter account_state with detail: "margin". A
withdrawable of "0" therefore means "nothing to take out", NOT "the account
is broke" — see account value. Two rules
behind the formula are worth stating:
- Unrealised gains never count. Free collateral folds an impending funding debit but never an unrealised profit — so a paper gain does not fund a new order, a spot buy or a withdrawal.
- Nothing is subtracted twice. A committed lock (spot escrow, an Earn deposit, a spot-margin post) is debited out of the balance at the moment it commits. It is gone from the balance, not carried as a separate held term.
Because it is one pool, the two directions are real and intended:
- A resting spot bid lowers your perp margin headroom for as long as it rests.
- A perp loss lowers what you can spend on spot or supply to Earn.
Cancel the bid and the escrow returns to the pool.
The standard-mode split
Live from node 0.9.7, block 5,710,001. An account already in standard at the
swap keeps one balance until it leaves the mode and enters again.
From the swap, an account that enters standard mode holds two USDC
wallets:
- The perp wallet is the collateral account. Perp margin, funding, liquidation, ADL, vaults and bridge withdrawals read and write this one.
- The spot wallet is spot token
100. Spot orders, spot fills, spot fees,send_assetof USDC, Core→EVM transfers and Earn read and write this one. An Earn deposit the spot wallet cannot fund is refusedPRECONDITION_FAILEDwith the messageinsufficient balance, notASSET_INSUFFICIENT_BALANCE. usd_class_transferis the only lane that crosses. It moves one amount from one wallet to the other.
Four rules follow, and each is deliberate:
- A perp loss cannot reach the spot wallet. A split account's perp bankruptcy is absorbed by the insurance fund and ADL, never by its spot USDC.
- A split account has no reservations. It is refused spot margin
(
spot margin is not available in standard mode) and every reservation (a split standard account has no reservations). - Each wallet funds its own orders, with no cap. Perp and option orders are
admitted against the perp wallet's free collateral. Spot orders are admitted
against the spot wallet. A spot order the spot wallet cannot fund is refused:
insufficient spot balance. - Only entry splits. An account already in
standardat the swap keeps one balance until it leaves the mode and enters again. Leaving folds the spot wallet back into the pool.
Two parts of the rules above ship with the next node release after 0.9.7: the
refusal of every reservation in rule 2, and the whole of rule 3. The spot-margin
refusal in rule 2 is live now. Until the release, a live node refuses only a
nonzero spot reservation on a split account
(spot has its own wallet in standard mode; no spot reservation). It still caps
perp and option orders by the perp and option reservations, and it accepts an
unfunded spot order as a no-op.
Reading the two wallets. account_value and withdrawable on
account_state are the perp wallet.
The USDC row of spot.balances is the spot wallet, and total − hold is what a
new spot order may spend. Add account_value and that row's total for the
account total.
Moving USDC
| Move | How | What it costs |
|---|---|---|
| Perp ↔ spot class | usd_class_transfer, split standard accounts only | No protocol fee. Rejected on every other account — see below |
| To another MetaFlux account | send_asset | No protocol fee |
| Parent ↔ sub-account | sub_account_transfer / sub_account_spot_transfer | No protocol fee |
| Core → EVM | core_evm_transfer | No protocol fee; the amount is rescaled ×10⁶ |
| EVM → Core | An EVM burn transaction — not an /exchange action | EVM gas |
| Off the network | bridge_withdraw | A bridge withdraw fee, withheld from the released amount |
usd_class_transfer is rejected, with
USDC is unified; no class transfer needed. Nothing is lost — the move it used
to perform has no destination now.
The rejection is in the shared handler, so every route to it rejects: the
/exchange action, the CoreWriter UsdClassTransfer from an EVM contract, and a
self-send_asset on asset 100 that crosses the spot/perp boundary (which is
routed into the same handler by design).
Every debit path is free-collateral gated; no credit path is. A send, an EVM move or a withdrawal is rejected when it would eat collateral that is margining an open position — you cannot withdraw your way below maintenance margin. An incoming credit only raises free collateral, so it needs no gate.
The bridge withdraw fee is a governance parameter in 6-decimal units,
withheld from the released amount: you are debited the gross amount, the
outbound message carries the net, and the difference accrues to the protocol.
The action rejects when amount does not exceed the fee. This page does not
publish the live fee value — read the rejection, or quote the withdrawal in your
client and compare gross against released.
Precision, surface by surface
| Surface | Field | Unit | 1 USDC looks like |
|---|---|---|---|
POST /info reads | account_value, withdrawable, spot.balances[*].total / .hold | whole-USDC decimal string | "1" |
POST /exchange send_asset | amount | whole-USDC decimal string | "1" |
POST /exchange core_evm_transfer | amount | whole-USDC decimal string | "1" |
POST /exchange bridge_withdraw | amount | 6-decimal integer (uint64) | 1000000 |
EVM ERC-20 at 0x…010000 | balanceOf, transfer | 6-decimal integer | 1000000 |
| Bridge deposit attestations | amount | 6-decimal integer | 1000000 |
The one conversion:
evm_or_bridge_units = whole_usdc × 1_000_000
bridge_withdraw is the exception, and it is a 10⁶ trap. It is the only
MTF-native USDC field that is a bare integer in base units rather than a
decimal string. "amount": 1000000 there is 1 USDC. The same literal on
send_asset or core_evm_transfer is 1,000,000 USDC — those fields are
whole-USDC strings. Check which action you are signing before you fill the field.
Both planes are exact: MetaFlux holds USDC as a fixed-point decimal, never a float, and the ×10⁶ rescale only repositions the decimal point. See two price planes for the separate question of price scaling.
What the account reads report
Two reads claim to show "your USDC". They do not agree, and one of them shows nothing at all.
account_state
POST /info account_state is the read to
use.
| Field | What it is | The rule behind it |
|---|---|---|
account_value | Mark-aware equity: settled USDC plus unrealised perp PnL, unrealised funding and spot-margin unrealised PnL | This is the figure the liquidation engine judges you on. Split standard account: the perp wallet only |
spot.balances[0] (name: "USDC", signing_id: 100) | total = settled USDC plus escrow; hold = USDC escrowed behind resting spot bids | total deliberately excludes unrealised PnL, so it never moves with the mark. total − hold is not spendable: hold is spot escrow only and never holds perp margin, so the subtraction leaves the margin in. Use withdrawable. Split standard account: the row is the spot wallet alone, so total − hold is what a spot order may spend — see the standard-mode split |
withdrawable | What a new order, send, withdrawal or Earn deposit may consume | The budget above, clamped at zero. Split standard account: a USDC send and a spot order read the spot wallet (insufficient spot balance), and so does an Earn deposit (insufficient balance). withdrawable bounds perp and option orders and withdrawals only — see the standard-mode split |
Why two numbers. account_value and spot.balances[0].total both look like "my
USDC" and differ by unrealised PnL. Use account_value for equity and risk; use
spot.balances[0].total for cash that has actually settled. The USDC row is always
present, even at zero.
One ledger, one read
account_state spot.balances carries the WHOLE
token ledger: the unified USDC pool in row 0, and every spot token after it.
There is no second balance read to merge in.
Read withdrawable, not total − hold. hold is spot order escrow only.
USDC that margins an open perpetual position stays in total and never enters
hold, so the subtraction leaves the margin in and overstates the budget.
Cost basis rides on the same rows.
avg_entry_px is the per-token acquisition
price spot PnL needs. The USDC row always reads null — a cost basis on the
quote asset in terms of itself has no meaning.
A worked check
Claim the testnet faucet, which grants 3000 USDC and
10 MTF, then read account_state:
account_value: "3000", andspot.balancescarries the USDC row (signing_id 100,total "3000") and an MTF row (asset 104,total "10").
One call, both rows. That is the unification.
USDC on the MetaFlux EVM
USDC on the MetaFlux EVM is Circle's FiatTokenV2_2
implementation behind a proxy, seeded at genesis at the fixed address
0x0000000000000000000000000000000000010000, with 6 decimals. It is a
real ERC-20 — balanceOf, transfer, approve — and, being the Circle
implementation, it carries permit (EIP-2612) and transferWithAuthorization
(EIP-3009).
Core → EVM is core_evm_transfer.
The Core debit is atomic at commit and the EVM credit is minted on the next EVM
block, so the queued credit is always fully backed. Optional calldata attached to
the transfer never unwinds the credit: if it reverts, the transfer still
stands — read its receipt.
EVM → Core is not an /exchange action. It must originate as an EVM
transaction that burns the EVM USDC; the node then mirrors the confirmed burn
onto the Core balance. Posting core_evm_transfer with to_evm: false is
rejected. The rule behind it: crediting Core without a confirmed burn would mint
value out of nothing.
Full mechanics and timings: Core ↔ EVM transfers.
USDC from outside
All USDC enters and leaves MetaFlux through the self-built MetaBridge custody bridge, co-signed by two thirds of active validator stake. There is no Circle CCTP path.
- A deposit credits the pool directly — it lands as collateral, ready to margin a perp or fund a spot buy, with no second step.
- A withdrawal is
bridge_withdrawwithasset: 0. Only USDC is bridgeable today; any other asset id is rejected. - The destination-chain release is asynchronous: the Core debit is immediate at commit, the payout follows co-signing and relay.
Not covered here
- The live bridge withdraw-fee value. The mechanism is above; the number is a
governance parameter and no
/inforead publishes it. - Per-chain USDC contract addresses. See the bridge page.
- Non-USDC collateral. Cross-asset collateral haircuts are a portfolio-margin topic.
See also
- Margin modes — how the initial and maintenance margin that reduce free collateral are computed
- Spot — the escrow model behind
hold - Earn — the lending pool USDC can be supplied to
- Bridge — deposits, withdrawals and the co-signing pipeline