> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcus.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Multi-Asset Collateral

> Deposit a token as perp collateral, trade on the automatic USDG loan, repay it, and withdraw the token: a step-by-step API walkthrough with Python code

This guide takes you through the full [Multi-Asset Collateral](https://docs.arcus.xyz/concepts/perpetuals/multi-asset-collateral.md) lifecycle over the API. You deposit a token on-chain, confirm it is credited as collateral, trade a perp on the automatic USDG loan, repay the loan, and withdraw the token. The examples use ETH (held as WETH) on mainnet. Every other eligible token works the same way, minus the wrapping step.

> [!NOTE]
>
> **Early access.** Multi-Asset Collateral is in early access and is being enabled for accounts in stages (see [Multi-Asset Collateral](https://docs.arcus.xyz/concepts/perpetuals/multi-asset-collateral.md) for how to check). A deposit from an address that isn't enabled yet is never credited. The token stays in the deposit contract and can be reclaimed after the deposit expires (see [If a deposit fails](https://docs.arcus.xyz/concepts/perpetuals/multi-asset-collateral.md#if-a-deposit-fails)). Start with a small amount.

> [!WARNING]
>
> This guide moves real funds on mainnet. The example amount is about \$25 of WETH, and the steps are written so you can stop after any of them.

## Prerequisites

* **An enabled wallet** with the token on **Robinhood Chain** mainnet, plus a little native ETH for gas. Chain IDs are listed under [Environments](https://docs.arcus.xyz/concepts/spot-integration.md#environments). For ETH collateral you only need native ETH, since step 2 wraps it.
* **A subaccount to use.** The examples use `accountIndex` `1`, a spare subaccount, so the collateral and loan are kept apart from your main account. A subaccount with USDG uses that USDG before it borrows anything.
* **An API key scoped to that subaccount** (or to all subaccounts), for steps 4 and 5 only. See [API keys and `accountIndex`](https://docs.arcus.xyz/concepts/subaccounts#api-keys-and-accountindex). Steps 1–3 and 6 don't need an API key.
* **A Robinhood Chain mainnet RPC endpoint** (your node or RPC provider).
* Python with `web3`, `eth-account` and `cryptography`:

```bash theme={null}
pip install web3 eth-account cryptography
```

## Step 1: Set up and check the asset

The setup reads the asset, including its token contract (`contractId`), from [`GET /v1/spotAssets`](https://docs.arcus.xyz/api-reference/public/get-spot-collateral-assets) and asks the `BridgeVault` whether it accepts deposits of that token. Fill in the chain values from the docs rather than copying them from elsewhere: `CHAIN_ID` from [Environments](https://docs.arcus.xyz/concepts/spot-integration.md#environments), and `BRIDGE_VAULT_ADDRESS` from the per-environment `chainId` / `verifyingContract` table on [`POST /v1/withdraw`](https://docs.arcus.xyz/api-reference/exchange/submit-withdrawal).

```python theme={null}
import json, time, urllib.error, urllib.request
from decimal import Decimal, ROUND_DOWN
from web3 import Web3
from eth_account import Account

BASE     = "https://api.arcus.xyz"
RPC      = ROBINHOOD_CHAIN_RPC_URL     # your Robinhood Chain mainnet RPC endpoint
CHAIN_ID = ROBINHOOD_CHAIN_ID          # mainnet chain ID: see /concepts/spot-integration#environments
VAULT    = Web3.to_checksum_address(BRIDGE_VAULT_ADDRESS)  # mainnet verifyingContract in the POST /v1/withdraw table
ACCOUNT_INDEX = 1
ASSET    = "WETH-USD-SPOT"

wallet = Account.from_key(PRIVATE_KEY)
addr   = wallet.address.lower()
w3     = Web3(Web3.HTTPProvider(RPC))
assert w3.eth.chain_id == CHAIN_ID

def canonical(obj):
    return json.dumps(obj, separators=(",", ":"), sort_keys=True).encode()

def call(method, path, body=None, headers=None):
    """Return (HTTP status, parsed JSON). 4xx/5xx are returned, not raised."""
    data = canonical(body) if body is not None else None
    req = urllib.request.Request(BASE + path, data=data, method=method,
        headers={"Content-Type": "application/json", **(headers or {})})
    try:
        with urllib.request.urlopen(req, timeout=15) as r:
            return r.status, json.loads(r.read() or b"{}")
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read() or b"{}")

asset = next(a for a in call("GET", "/v1/spotAssets")[1]["spotAssets"]
             if a["spotAssetDisplayName"] == ASSET)
ASSET_ID = asset["spotAssetId"]                     # e.g. 9 for WETH-USD-SPOT
TOKEN    = Web3.to_checksum_address(asset["contractId"])  # the token contract (WETH for ETH)
STEP     = Decimal(asset["stepSize"])               # e.g. 0.0000001

ERC20 = [
    {"name": "deposit",   "type": "function", "stateMutability": "payable", "inputs": [], "outputs": []},  # WETH only
    {"name": "withdraw",  "type": "function", "inputs": [{"name": "wad", "type": "uint256"}], "outputs": []},  # WETH only
    {"name": "approve",   "type": "function", "inputs": [{"name": "spender", "type": "address"}, {"name": "amount", "type": "uint256"}], "outputs": [{"type": "bool"}]},
    {"name": "balanceOf", "type": "function", "stateMutability": "view", "inputs": [{"name": "owner", "type": "address"}], "outputs": [{"type": "uint256"}]},
    {"name": "decimals",  "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"type": "uint8"}]},
]
BRIDGE_VAULT = [
    {"name": "initiateDeposit", "type": "function", "inputs": [
        {"name": "owner", "type": "address"}, {"name": "accountIndex", "type": "uint16"},
        {"name": "token", "type": "address"}, {"name": "amount", "type": "uint256"}],
     "outputs": [{"name": "inboxIndex", "type": "uint64"}]},
    {"name": "depositConfig", "type": "function", "stateMutability": "view",
     "inputs": [{"name": "token", "type": "address"}],
     "outputs": [{"type": "tuple", "components": [
         {"name": "allowed", "type": "bool"}, {"name": "minAmount", "type": "uint256"}]}]},
]
token = w3.eth.contract(address=TOKEN, abi=ERC20)
vault = w3.eth.contract(address=VAULT, abi=BRIDGE_VAULT)
DECIMALS = token.functions.decimals().call()        # e.g. 18

allowed, min_amount = vault.functions.depositConfig(TOKEN).call()
assert asset["enabled"] and allowed, f"{ASSET} is not accepting deposits"
print(f"{ASSET} id={ASSET_ID} oracle={asset['oraclePrice']} ltvInitial={asset['ltvInitial']} "
      f"min deposit={Decimal(min_amount) / 10**DECIMALS}")
```

Only deposit an asset for which `depositConfig` returns `allowed: true`. An asset can be listed on `GET /v1/spotAssets` before the vault accepts it.

## Step 2: Wrap, approve and deposit

Size the deposit in whole multiples of the asset's `stepSize`, so the credited size is a whole number of base quantums when you withdraw it later. Then send three transactions:

1. **Wrap** native ETH into WETH with `deposit()`. Skip this for a Stock Token.
2. **Approve** the `BridgeVault` to pull the token. Token deposits go to the `BridgeVault` directly, not through the USDG deposit proxy.
3. **Call `initiateDeposit(owner, accountIndex, token, amount)`**. `owner` must be the sending wallet.

```python theme={null}
def send(fn, value=0):
    tx = fn.build_transaction({"from": wallet.address, "value": value,
                               "nonce": w3.eth.get_transaction_count(wallet.address)})
    receipt = w3.eth.wait_for_transaction_receipt(
        w3.eth.send_raw_transaction(wallet.sign_transaction(tx).raw_transaction))
    assert receipt.status == 1, f"reverted: {receipt.transactionHash.hex()}"
    return receipt

usd   = Decimal("25")
units = (usd / Decimal(asset["oraclePrice"])).quantize(STEP, rounding=ROUND_DOWN)  # 0.0098795 WETH
wei   = int(units * 10**DECIMALS)
assert wei >= min_amount, "below the vault's minimum deposit"

have = token.functions.balanceOf(wallet.address).call()
if have < wei:
    send(token.functions.deposit(), value=wei - have)                       # 1. wrap ETH -> WETH
send(token.functions.approve(VAULT, wei))                                   # 2. approve the BridgeVault
dep = send(vault.functions.initiateDeposit(wallet.address, ACCOUNT_INDEX, TOKEN, wei))  # 3. deposit
print("deposit tx:", dep.transactionHash.hex())
```

Keep the deposit transaction hash. If the deposit is never credited, the `DepositInitiated` event in its receipt carries the `inboxIndex` you need to reclaim it.

## Step 3: Confirm the credit

Poll [`GET /v1/spotPositions`](https://docs.arcus.xyz/api-reference/public/get-spot-positions) for the holding, then read [`GET /v1/account`](https://docs.arcus.xyz/api-reference/public/get-account):

```python theme={null}
q = f"address={addr}&accountIndex={ACCOUNT_INDEX}"
for _ in range(60):
    holdings = call("GET", f"/v1/spotPositions?{q}")[1].get("spotPositions") or {}  # {} or null when empty
    if Decimal(holdings.get(str(ASSET_ID), {}).get("size", "0")) >= units:
        break
    time.sleep(5)
else:
    raise SystemExit("not credited yet: check the deposit tx and your account's approval")

status, account = call("GET", f"/v1/account?{q}")
print({k: account.get(k) for k in ("equity", "freeCollateral", "borrowHeadroom", "loanBalance", "marginState")})
```

On a subaccount you had never used, the deposit itself activates the account, so `GET /v1/account` goes from `404` to `200`. An example mainnet response after depositing 0.0098795 WETH (address and ids replaced with placeholders):

```json theme={null}
{
  "accountIndex": 1,
  "address": "0xyouraddress",
  "netQuoteBalance": "0",
  "equity": "24.964607345",
  "freeCollateral": "0",
  "positions": {},
  "netDepositsByAsset": [
    { "spotAssetId": 9, "spotAssetDisplayName": "WETH-USD-SPOT", "netAmount": "0.0098795" }
  ],
  "loanBalance": "0",
  "unsettledInterest": "0",
  "marginState": "healthy",
  "borrowHeadroom": "12.482303672",
  "spotPositions": {
    "9": {
      "spotAssetId": 9,
      "spotAssetDisplayName": "WETH-USD-SPOT",
      "size": "0.0098795",
      "value": "24.964607345",
      "oraclePrice": "2526.91"
    }
  }
}
```

What to check:

* **The token was kept, not converted.** `netQuoteBalance` (your USDG) is still `0`, and the WETH appears under `spotPositions`.
* **It counts as collateral immediately.** `equity` includes the token's full value, and `borrowHeadroom` is about value × `ltvInitial` (\$24.96 × 0.5). There is no "enable" step.
* **`freeCollateral` is `0`.** That's expected: it counts perp-side cash only. Use `borrowHeadroom` and `marginState` instead.

The deposit also appears on [`GET /v1/accountTransferUpdates`](https://docs.arcus.xyz/api-reference/public/get-account-transfer-updates) as a `DEPOSIT` row with `spotAssetId`, the size as `amount`, and `status: APPLIED`. It also streams on the [`spotPositions`](https://docs.arcus.xyz/api-reference/channels#spotpositions) channel with `reason: DEPOSIT`.

## Step 4: Trade a perp on the loan

There is no borrow call. Place a cross-margin perp order as usual, and when the subaccount has no USDG to cover it, Arcus lends USDG against your collateral automatically. This step reuses the order signing from [Trade over REST](https://docs.arcus.xyz/guides/rest-trading.md). It assumes `priv` (your Ed25519 key) and `api_key` (its hex public key) for a key scoped to `ACCOUNT_INDEX`.

```python theme={null}
TIF_IOC = 2

mkt = next(m for m in call("GET", "/v1/markets")[1]["markets"] if m["marketDisplayName"] == "ETH-USD")
MID, TICK, MSTEP = mkt["marketId"], Decimal(mkt["tickSize"]), Decimal(mkt["stepSize"])

def to_int(value, unit):
    n = Decimal(str(value)) / Decimal(unit)
    assert n == n.to_integral_value(), f"{value} is not a multiple of {unit}"
    return int(n)

def market_order(side, qty, reduce_only):
    # A MARKET order still carries a protective price within 10% of the mark.
    mark  = Decimal(mkt["markPrice"])
    price = (mark * (Decimal("1.02") if side == "BUY" else Decimal("0.98"))).quantize(TICK)
    ts, good_til = time.time_ns(), int(time.time() * 1_000_000) + 40 * 86_400 * 1_000_000
    payload = (f'{{"ad":"{addr}","ai":{ACCOUNT_INDEX},"ct":{ts},"g":{good_til * 1000},'
               f'"m":{MID},"op":1,"p":{to_int(price, TICK)},"q":{to_int(qty, MSTEP)},'
               f'"r":{int(reduce_only)},"s":{0 if side == "BUY" else 1},"t":{TIF_IOC},"v":1}}')
    return call("POST", "/v1/placeOrder", {
        "address": addr, "accountIndex": ACCOUNT_INDEX, "marketId": MID,
        "orderSide": side, "orderType": "MARKET", "quantity": str(qty), "price": str(price),
        "timeInForce": "IOC", "reduceOnly": reduce_only, "goodTilTime": str(good_til), "timestamp": ts},
        {"X-API-Key": api_key, "X-Timestamp": str(ts), "X-Signature": priv.sign(payload.encode()).hex()})

print(market_order("BUY", "0.004", reduce_only=False))   # about $10 of ETH-USD
time.sleep(2)
account = call("GET", f"/v1/account?{q}")[1]
print({k: account.get(k) for k in ("loanBalance", "freeCollateral", "borrowHeadroom", "marginState")})
```

After the fill, the account carries a loan. Example values for about \$10 of ETH-USD perp on a subaccount holding only WETH:

```json theme={null}
{ "loanBalance": "1.122", "freeCollateral": "-1.12", "marginState": "healthy" }
```

* **`loanBalance`** is the USDG lent to you: the order's initial margin, plus the fee and any unrealized loss.
* **`freeCollateral` is negative.** That's by design on a borrowing account. `marginState: healthy` is the health signal.
* **No transfer row is created.** Borrowing isn't a transfer, so the loan shows only as `loanBalance`.
* **Interest accrues** on `loanBalance` at the current borrow rate. The rate streams on the [`lendingRates`](https://docs.arcus.xyz/api-reference/channels#lendingrates) channel as `ratePpm`; over REST, [`GET /v1/lendingAttributes`](https://docs.arcus.xyz/api-reference/public/get-global-lending-attributes) returns it as `borrowRatePpm`. Each settlement appears as an `INTEREST_SETTLEMENT` row on `GET /v1/accountTransferUpdates`.

Close the position with a reduce-only order. The loan for its margin goes away, but any realized loss, fees and interest stay as a smaller loan:

```python theme={null}
print(market_order("SELL", "0.004", reduce_only=True))
time.sleep(2)
account = call("GET", f"/v1/account?{q}")[1]
print("loan after close:", account.get("loanBalance"), "+ interest", account.get("unsettledInterest"))
```

## Step 5: Repay the loan

A loan left after you close stays until you pay it down. Deposit USDG to the subaccount, or sell some of the collateral with [`POST /v1/settleLoan`](https://docs.arcus.xyz/api-reference/exchange/sell-spot-collateral-to-settle-the-accounts-loan). `settleLoan` sells at the mark price minus a small spread set by Arcus and never below your `minPrice`. It never sells more than the loan and accrued interest need, so you can overshoot `maxQuantity`.

`settleLoan` signs the **legacy** message, `timestamp + "settleLoan" + canonical_json(body)`, like `cancelAllOrders` (see [Authentication](https://docs.arcus.xyz/api-reference/authentication.md)). `maxQuantity` must be a multiple of the asset's `stepSize`, and `minPrice` a multiple of its `tickSize`. Base `minPrice` on the mark price, which is what the sale executes against: read it from [`GET /v1/prices`](https://docs.arcus.xyz/api-reference/public/get-live-prices-for-all-markets) for the asset's `priceSourceMarketId`. `oraclePrice` on `GET /v1/spotAssets` is the oracle price, which can differ from the mark.

```python theme={null}
asset  = call("GET", f"/v1/spotAssets?spotAsset={ASSET_ID}")[1]["spotAssets"][0]
mark   = Decimal(call("GET", "/v1/prices")[1][str(asset["priceSourceMarketId"])]["markPrice"])
held   = Decimal(call("GET", f"/v1/spotPositions?{q}")[1]["spotPositions"][str(ASSET_ID)]["size"])
owed   = Decimal(account["loanBalance"]) + Decimal(account["unsettledInterest"])

body = {
    "address": addr, "accountIndex": ACCOUNT_INDEX, "spotAssetId": ASSET_ID,
    "maxQuantity": format(min(held, (owed * 2 / mark).quantize(STEP)), "f"),   # overshoot is safe
    "minPrice": format((mark * Decimal("0.98")).quantize(Decimal(asset["tickSize"]), ROUND_DOWN), "f"),  # your own floor, e.g. 2% under mark
}
ts = time.time_ns()
sig = priv.sign(str(ts).encode() + b"settleLoan" + canonical(body)).hex()
status, settled = call("POST", f"/v1/settleLoan?address={addr}", body,
                       {"X-API-Key": api_key, "X-Timestamp": str(ts), "X-Signature": sig})
print(status, settled)
```

The response tells you what happened. `200` with `status: APPLIED` means the sale ran, so compare `executedQuantity` with `maxQuantity` and check `newLoanBalance`. `202` with `status: ACK` means the engine hadn't confirmed in time; watch the account for the result. `422` with `status: REJECTED` carries a `rejectReason`. The shape of an `APPLIED` response, per the API reference (illustrative values):

```json theme={null}
{
  "requestId": "…",
  "address": "0x…",
  "accountIndex": 1,
  "spotAssetId": 9,
  "maxQuantity": "0.0004",
  "minPrice": "2476.86",
  "executedQuantity": "0.0000208",
  "executionPrice": "2520.36",
  "proceeds": "0.0524",
  "repaidAmount": "0.0524",
  "newLoanBalance": "0",
  "status": "APPLIED"
}
```

The sale appears on [`GET /v1/spotFills`](https://docs.arcus.xyz/api-reference/public/get-spot-fills), and the proceeds as a `LOAN_SETTLEMENT_PAYMENT` row on `GET /v1/accountTransferUpdates`.

## Step 6: Withdraw the token

Withdraw with [`POST /v1/withdraw`](https://docs.arcus.xyz/api-reference/exchange/submit-withdrawal), setting `spotAssetId`. A token withdrawal is signed by the wallet with the **`SpotAssetWithdraw`** EIP-712 type, under its own domain, `"Arcus Spot Withdraw"`. It uses the same `chainId` and `verifyingContract` (the `BridgeVault`) as a USDG withdrawal. A signature over the USDG `Withdraw` type is rejected.

`amount` is an **integer string of the asset's base quantums**: size ÷ `stepSize`. To withdraw 0.0098795 WETH at a `stepSize` of 0.0000001, send `"98795"`. A decimal such as `"0.0098795"` is rejected. Sign the same integer as the `uint256` `amount`.

```python theme={null}
held = Decimal(call("GET", f"/v1/spotPositions?{q}")[1]["spotPositions"][str(ASSET_ID)]["size"])
quantums = held / STEP
assert quantums == quantums.to_integral_value()
quantums = int(quantums)                                        # 98795 for 0.0098795 WETH

nonce  = str(time.time_ns())
signed = Account.sign_typed_data(wallet.key,
    {"name": "Arcus Spot Withdraw", "version": "1", "chainId": CHAIN_ID, "verifyingContract": VAULT},
    {"SpotAssetWithdraw": [
        {"name": "ethereumAddress", "type": "address"},
        {"name": "accountIndex",    "type": "uint8"},
        {"name": "spotAssetId",     "type": "uint16"},
        {"name": "amount",          "type": "uint256"},
        {"name": "nonce",           "type": "string"}]},
    {"ethereumAddress": wallet.address, "accountIndex": ACCOUNT_INDEX, "spotAssetId": ASSET_ID,
     "amount": quantums, "nonce": nonce})

status, wd = call("POST", "/v1/withdraw", {
    "ethereumAddress": addr, "accountIndex": ACCOUNT_INDEX, "spotAssetId": ASSET_ID,
    "amount": str(quantums), "nonce": nonce,
    "signature": {"r": "0x" + signed.r.to_bytes(32, "big").hex(),
                  "s": "0x" + signed.s.to_bytes(32, "big").hex(),
                  "v": hex(signed.v)}})
print(status, wd)                                               # 202, status PENDING

for _ in range(60):                                             # wait for the terminal status
    rows = call("GET", f"/v1/accountTransferUpdates?{q}&limit=20")[1].get("accountTransferUpdates") or []
    row = next((r for r in rows if r.get("id") == wd["withdrawalId"]), None)
    if row and row["status"] != "PENDING":
        print(row["status"])                                    # APPLIED
        break
    time.sleep(2)
```

You can withdraw only what isn't needed to cover an open loan at initial LTV, so repay first to withdraw everything. An example mainnet `202` response (address and ids replaced with placeholders):

```json theme={null}
{
  "withdrawalId": "b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "ethereumAddress": "0xyouraddress",
  "accountIndex": 1,
  "spotAssetId": 9,
  "spotAssetDisplayName": "WETH-USD-SPOT",
  "amount": "0.0098795",
  "status": "PENDING",
  "submittedAt": 1791818578908919
}
```

The response and the transfer row show the decimal size in `amount`, even though you sent base quantums. The transfer row once it reaches `APPLIED`:

```json theme={null}
{
  "type": "WITHDRAWAL",
  "id": "b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "status": "APPLIED",
  "amount": "0.0098795",
  "accountIndex": 1,
  "spotAssetId": 9,
  "spotAssetDisplayName": "WETH-USD-SPOT",
  "sourceAccountId": "…",
  "destinationAccountId": "ROBINHOOD_CHAIN"
}
```

To turn the WETH back into native ETH, call `withdraw(amount)` on the WETH contract:

```python theme={null}
send(token.functions.withdraw(int(held * 10**DECIMALS)))       # optional: unwrap WETH -> ETH
```

## Common errors

| Symptom | Cause and fix |
| - | - |
| `initiateDeposit` reverts with `DepositNotAllowed` | The vault doesn't accept this token yet. Check `depositConfig(token).allowed`, and that you used the `contractId` from `GET /v1/spotAssets`. |
| `initiateDeposit` reverts with `DepositBelowMinimum` | `amount` is under `depositConfig(token).minAmount`. Deposit more. |
| `initiateDeposit` reverts with `NotOwner` | `owner` must be the wallet sending the transaction. |
| Native ETH sent, nothing credited as collateral | Native ETH isn't a collateral token. Wrap it with `deposit()` on the WETH contract and deposit the WETH. ETH sent through the app's main Deposit flow is converted to USDG instead. |
| Deposit confirmed on-chain but never credited | Early access may not be enabled for your address yet. The token stays in the vault. Once the deposit has expired, reclaim it with `cancelDeposit(inboxIndex)`. |
| Transfer row with `REJECTED_SUPPLY_CAP_EXCEEDED` or `REJECTED_COLLATERAL_DISABLED` | The deposit would breach a supply cap, or the asset is disabled. See [If a deposit fails](https://docs.arcus.xyz/concepts/perpetuals/multi-asset-collateral.md#if-a-deposit-fails). |
| Order rejected on a spot-only subaccount | The order needs more than `borrowHeadroom`, or would push the loan past the per-account loan limit. Reduce the size, or deposit more collateral. |
| `POST /v1/withdraw` returns `400` | `amount` isn't a positive integer of base quantums (decimals are rejected), or `spotAssetId` is unknown. |
| `POST /v1/withdraw` returns `401` | The signature doesn't recover to `ethereumAddress`. Check that you signed `SpotAssetWithdraw` (not `Withdraw`) under `"Arcus Spot Withdraw"`, with the right `chainId`, `BridgeVault` and `spotAssetId`. |
| `POST /v1/withdraw` returns `409` | The `nonce` was already used. Sign again with a fresh nanosecond timestamp. |
| Withdrawal row ends `REJECTED_INSUFFICIENT_COLLATERAL` | Removing that much collateral would leave the loan under-collateralized. Repay first, or withdraw less. |
| Withdrawal row ends `REJECTED_INSUFFICIENT_SPOT_BALANCE` | The account holds less than `amount`. Re-read the size from `GET /v1/spotPositions`. |
| `settleLoan` returns `422` `NO_LOAN` or `NO_HOLDING` | There is nothing to repay, or the account holds none of that asset. |
| `settleLoan` returns `422` `PRICE_BELOW_MIN_PRICE` | The spread-discounted price fell below your `minPrice`. Re-sign with a lower floor. |
| `settleLoan` returns `422` `NO_BUYER` | No buyer could take the sale right now. Nothing was sold; retry later or deposit USDG. |
| `settleLoan` returns `401` | It uses the legacy message (`timestamp + "settleLoan" + canonical_json(body)`), not the typed order payload. |

## Next steps

* [Multi-Asset Collateral](https://docs.arcus.xyz/concepts/perpetuals/multi-asset-collateral.md): LTV, interest, liquidation and worked examples.
* [Subaccounts](https://docs.arcus.xyz/concepts/subaccounts.md): keep collateral and loans in their own `accountIndex`.
* [Trade over REST](https://docs.arcus.xyz/guides/rest-trading.md) and [Trade over WebSocket](https://docs.arcus.xyz/guides/websocket-trading.md): order signing in full.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.