HKUDS/Vibe-Trading · error · UsdMObservationError

Binance USD-M Shadow Account supports the USDT asset only

Error message

Binance USD-M Shadow Account supports the USDT asset only

What it means

A non-USDT asset row in the futures assets array has a non-zero balance in any tracked balance field. The Shadow Account supports USDT collateral only, so any leftover non-USDT balance (dust, USDC, BUSD, BNB fee balances) is rejected.

Source

Thrown at agent/src/trading/connectors/binance/usdm.py:251

    balance_fields = (
        "walletBalance",
        "marginBalance",
        "availableBalance",
        "initialMargin",
        "positionInitialMargin",
        "openOrderInitialMargin",
        "maintMargin",
        "unrealizedProfit",
    )
    for asset in assets:
        symbol = str(asset.get("asset") or "").upper()
        balances = tuple(_number(asset.get(field), field) for field in balance_fields)
        if symbol == "USDT":
            if usdt_asset is not None:
                raise UsdMObservationError("Binance USD-M assets must include exactly one USDT row")
            usdt_asset = asset
        elif any(value != 0 for value in balances):
            raise UsdMObservationError("Binance USD-M Shadow Account supports the USDT asset only")
    if usdt_asset is None:
        raise UsdMObservationError("Binance USD-M assets must include USDT")
    return usdt_asset


def _require_coherent_totals(
    account: Mapping[str, float],
    positions: list[dict[str, Any]],
    close_enough: CloseEnough,
) -> None:
    comparisons = (
        (
            account["total_unrealized_pnl"],
            sum(row["unrealized_pnl"] for row in positions),
        ),
        (
            account["total_initial_margin"],
            sum(row["initial_margin"] for row in positions),

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Convert/transfer all non-USDT balances out of the futures wallet (use Binance's Convert-to-USDT or transfer to spot)
  2. Disable multi-assets mode so only USDT collateral applies
  3. Start with a clean sub-account funded solely with USDT
  4. Sweep BNB and other dust via the conversion endpoint

Example fix

# before: wallet holds {USDT: 5000, USDC: 12.3}
# after: convert or transfer out
POST /sapi/v1/asset/dust (or futures transfer) -> wallet holds {USDT: 5012.3}
Defensive patterns

Strategy: validation

Validate before calling

acct = await client.futures_account()
nonzero = [a['asset'] for a in acct['assets'] if a['asset'].upper() != 'USDT' and any(float(a.get(f, 0) or 0) != 0 for f in ('balance', 'availableBalance', 'crossUnPnl'))]
if nonzero:
    raise RuntimeError(f"non-USDT balances present: {nonzero}")

Type guard

def wallet_is_usdt_only(assets: list[dict]) -> bool:
    return all(
        str(a.get('asset', '')).upper() == 'USDT'
        or all(float(a.get(f, 0) or 0) == 0 for f in ('balance', 'availableBalance', 'crossUnPnl'))
        for a in assets
    )

Try / catch

try:
    obs = await connector.read_account_observation()
except UsdMObservationError as e:
    if "USDT asset only" in str(e):
        raise RuntimeError("convert or transfer out non-USDT futures balances, then retry")
    raise

Prevention

When it happens

Trigger: Holding residual USDC/BUSD/BNB in the futures wallet; airdropped dust; receiving multi-asset collateral; auto-conversion settings leaving non-USDT balances.

Common situations: Prior trading on multi-asset mode, BNB fee discount balances, unswept conversion dust, or using the same account previously with USDC-margined contracts.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/6ae1b7c6c096a4f8. Report an issue: GitHub.