Skip to main content
This guide takes you through the full Multi-Asset Collateral 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.
Early access. Multi-Asset Collateral is in early access and is being enabled for accounts in stages (see Multi-Asset Collateral 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). Start with a small amount.
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. 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. 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:

Step 1: Set up and check the asset

The setup reads the asset, including its token contract (contractId), from GET /v1/spotAssets 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, and BRIDGE_VAULT_ADDRESS from the per-environment chainId / verifyingContract table on POST /v1/withdraw.
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.
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 for the holding, then read GET /v1/account:
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):
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 as a DEPOSIT row with spotAssetId, the size as amount, and status: APPLIED. It also streams on the 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. It assumes priv (your Ed25519 key) and api_key (its hex public key) for a key scoped to ACCOUNT_INDEX.
After the fill, the account carries a loan. Example values for about $10 of ETH-USD perp on a subaccount holding only WETH:
  • 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 channel as ratePpm; over REST, GET /v1/lendingAttributes 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:

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. 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). 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 for the asset’s priceSourceMarketId. oraclePrice on GET /v1/spotAssets is the oracle price, which can differ from the mark.
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):
The sale appears on GET /v1/spotFills, and the proceeds as a LOAN_SETTLEMENT_PAYMENT row on GET /v1/accountTransferUpdates.

Step 6: Withdraw the token

Withdraw with POST /v1/withdraw, 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.
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):
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:
To turn the WETH back into native ETH, call withdraw(amount) on the WETH contract:

Common errors

Next steps