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

# Vault

> Deposit and withdraw tokens

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

// Initialize SDK with wallet client (see Initialization guide)

// Get asset ID from trading pair
const pair = await sdk.market.getTradingPairBySymbol('BTC/USDC');
const baseAssetId = pair.baseAssetId;   // BTC asset ID
const quoteAssetId = pair.quoteAssetId; // USDC asset ID

// Vault operations (all use assetId - UUID)
await sdk.vault.deposit(quoteAssetId, amount, autoWait?);            // deposit to spot (default)
await sdk.vault.deposit(quoteAssetId, amount, autoWait?, "margin");  // route directly to margin collateral
await sdk.vault.withdraw(quoteAssetId, amount, autoWait?);            // withdraw from spot (default)
await sdk.vault.withdraw(quoteAssetId, amount, autoWait?, "margin");  // withdraw directly from parent margin collateral
await sdk.vault.retryWithdrawal(withdrawalIndex, autoWait?);
await sdk.vault.getVaultAddress();

// Low-level withdrawals (returns executable calldata WITHOUT broadcasting)
await sdk.withdrawals.initiateWithdrawal({ assetId, amount, destination });
await sdk.withdrawals.getWithdrawal(withdrawalIndex);
await sdk.withdrawals.listPendingWithdrawals({ pageSize?, pageToken? });

// Registered deposit addresses: auto-sweep ERC20 deposits into the rollup
await sdk.sweeper.register({ depositTarget? });   // identity comes from the session
await sdk.sweeper.getChains();                    // public: which chains and assets are swept (an unlisted token is not swept)

// Token approvals (also use assetId - UUID)
await sdk.vault.approve(quoteAssetId, amount, autoWait?);
await sdk.vault.getAllowance(quoteAssetId);
await sdk.vault.needsApproval(quoteAssetId, amount);
```

## Vault Operations

<ResponseField name="deposit" type="(assetId: string, amount: bigint, autoWait?: boolean, target?: DepositTarget, options?: DepositOptions) => Promise<TransactionResult>">
  Deposit tokens into vault. Uses asset ID (UUID) from trading pair.

  **Parameters:**

  * `assetId`: string - Asset UUID (from trading pair)
  * `amount`: bigint - Token amount in smallest unit
  * `autoWait?`: boolean - Wait for on-chain confirmation (default: true)
  * `target?`: `"spot" | "margin"` - Destination ledger (default: `"spot"`)
    * `"spot"` — credits the spot wallet (unchanged behavior)
    * `"margin"` — routes the deposit straight into the parent margin account's collateral, skipping the separate `transferCollateralToParentMarginAccount` step. Deposits that cannot be routed to margin (e.g. unsupported asset) fall back to spot automatically.
  * `options?`: `DepositOptions` - Deposit options. `asErc20: true` deposits the chain's wrapped-native asset (e.g. WSEI) as its ERC20 through approve + `depositERC20`, instead of the native-coin default below; it is ignored for every other asset. The ERC20 path is not auto-approved — `deposit` throws a `ContractError` (`code === "CONTRACT_ERROR"`) with `revertReason === "APPROVAL_REQUIRED"` if the allowance is short, so `approve` (or `needsApproval` with the same option) first.

  **Example:**

  ```typescript theme={null}
  const pair = await sdk.market.getTradingPairBySymbol('BTC/USDC');
  const assetId = pair.quoteAssetId; // USDC

  // Standard spot deposit (unchanged)
  await sdk.vault.deposit(assetId, amount);

  // Route directly into margin collateral
  await sdk.vault.deposit(assetId, amount, true, "margin");
  ```

  <Note>
    **Wrapped-native assets deposit as native coin.** When the asset is the chain's wrapped-native token (e.g. WSEI) — the `wrappedNative: true` flag on its [balance row](/developers/account-balances#balance-model) — `deposit` routes to the vault's payable `depositNative` and sends `amount` as `msg.value`; the vault wraps it on entry. **No ERC20 approval is required or checked** for these assets, so you can skip the `approve` / `needsApproval` step. Every other asset keeps the approve + `depositERC20` flow. Withdrawals of a wrapped-native asset pay out native coin (the vault unwraps), decided server-side with no request change.

    To deposit the wrapped-native asset from a wallet that already holds the wrapped ERC20 itself, pass `{ asErc20: true }` as the fifth argument: `deposit` then takes the standard approve + `depositERC20` path (not `depositNative`), and `needsApproval(assetId, amount, { asErc20: true })` reads the real allowance instead of returning `false`.
  </Note>
</ResponseField>

<ResponseField name="withdraw" type="(assetId: string, amount: bigint, autoWait?: boolean, source?: WithdrawalSource) => Promise<WithdrawResult>">
  Withdraw tokens from vault. Posts to `/api/v1/withdrawals`, waits for merkle-proof-backed `executeWithdrawal(...)` calldata, then submits the transaction on-chain.

  **Parameters:**

  * `assetId`: string - Asset UUID (from trading pair)
  * `amount`: bigint - Token amount in smallest unit
  * `autoWait?`: boolean - Wait for on-chain confirmation (default: true)
  * `source?`: `"spot" | "margin"` - Source ledger (default: `"spot"`)
    * `"spot"` — debits the spot wallet
    * `"margin"` — directly debits withdrawable collateral from the parent margin account

  When `autoWait` is `true` (default), the call waits for the withdrawal's on-chain merkle-proof calldata before submitting. The default wait timeout is **1 minute** (60 seconds), polling every 5000 ms. Override these via `WithdrawalRetryOptions { pollIntervalMs?: number; timeoutMs?: number }`.

  When `autoWait` is `false`, the call returns immediately after the withdrawal is registered — it does **not** poll for the merkle proof. Use this when you want to initiate the withdrawal and complete the on-chain step later:

  ```typescript theme={null}
  const { withdrawalIndex, status } = await sdk.vault.withdraw(assetId, amount, false);
  // status === 'awaiting_proof' — proof not yet available, no transaction submitted

  // Later, once the proof is confirmed:
  await sdk.vault.retryWithdrawal(withdrawalIndex);
  ```

  **Returns:** `WithdrawResult`:

  * `withdrawalIndex`: number — the index allocated by the matching engine. Store this — you'll need it if the on-chain submission fails and you want to retry
  * `hash?`: string — transaction hash; absent when `autoWait` is `false`
  * `status`: string — `"awaiting_proof"` when `autoWait` is `false`; transaction status otherwise
</ResponseField>

<ResponseField name="retryWithdrawal" type="(withdrawalIndex: number, autoWait?: boolean) => Promise<WithdrawResult>">
  Re-submit a withdrawal whose on-chain transaction failed or was dropped. Fetches the same merkle-proof-backed calldata for that `withdrawalIndex` and resubmits it. Does not allocate a new withdrawal slot — safe to call multiple times.

  **Parameters:**

  * `withdrawalIndex`: number — returned by the original `withdraw()` call

  **Returns:** Same `WithdrawResult` shape as `withdraw()`.
</ResponseField>

<ResponseField name="getVaultAddress" type="() => Promise<string>">
  Get the vault contract address for the current network.

  **Returns:** Vault contract address (e.g., `0x1234...`)

  **Use cases:**

  * Add vault address to wallet for direct contract interaction
  * Verify transactions on blockchain explorers
  * Advanced integrations requiring direct contract access

  **Example:**

  ```typescript theme={null}
  const vaultAddress = await sdk.vault.getVaultAddress();
  console.log("Vault contract:", vaultAddress);
  // Example: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
  ```

  **Network-specific:**

  * Staging (testnet): Returns Atlantic-2 vault address
</ResponseField>

## Low-Level Withdrawals

The `sdk.withdrawals` API exposes the raw withdrawal calldata flow. Unlike
`sdk.vault.withdraw` — which waits for the merkle-proof-backed calldata **and**
broadcasts the transaction on-chain — `sdk.withdrawals` returns the ABI-encoded
`executeWithdrawal(...)` calldata **without** broadcasting. Use it when you want
to submit the transaction yourself (custom signer, relayer, batching, or
inspection).

<ResponseField name="withdrawals.initiateWithdrawal" type="(request: InitiateWithdrawalRequest) => Promise<WithdrawalResponse>">
  Initiate a withdrawal. Authenticated (master accounts with the withdraw permission). Debits the caller's balance via the matching engine and allocates a withdrawal index. Does **not** submit on-chain.

  **Request:**

  * `assetId`: Asset UUID to withdraw
  * `amount`: Raw token amount in the smallest unit, as a stringified positive integer
  * `destination`: On-chain address that will receive the withdrawal (EVM, 0x-prefixed)

  The destination must differ from the configured vault contract address; using that address returns `400` before the balance is debited.

  **Throws:** `400` for an invalid amount, address or asset, including the vault as destination; `401` without valid authentication; `403` without withdrawal permission or for an unsupported account type; `404` when the asset is absent; `429` when the movement budget is exhausted; `500` on an internal failure; `503` when the matching engine is unavailable or maintenance blocks the write. Do not initiate a second withdrawal after an ambiguous transport failure; reconcile the original first.

  **Returns:** `WithdrawalResponse`:

  * `withdrawalIndex`: number — matches `executeWithdrawal.index` on-chain
  * `vaultAddress`: string — vault contract the calldata is submitted to
  * `calldata`: string — ABI-encoded `executeWithdrawal(...)` calldata; empty until the merkle proof is available
</ResponseField>

<ResponseField name="withdrawals.getWithdrawal" type="(withdrawalIndex: number) => Promise<WithdrawalResponse>">
  Re-fetch a previously-initiated withdrawal's executable calldata. Public — no auth required.

  **Parameters:**

  * `withdrawalIndex`: number — returned by `initiateWithdrawal()`

  **Returns:** Same `WithdrawalResponse` shape as `initiateWithdrawal()`.
</ResponseField>

<ResponseField name="withdrawals.listPendingWithdrawals" type="(params?: ListPendingWithdrawalsParams) => Promise<ListPendingWithdrawalsResponse>">
  List the caller's withdrawals that are still awaiting on-chain root confirmation, newest first. Authenticated and scoped to the caller's own account. An entry here has no calldata yet: once it is confirmed it leaves this list, and `getWithdrawal()` returns its calldata.

  **Parameters:** all optional

  * `pageSize?`: number — `1`–`1000`, default `20`. A larger value is rejected, not clamped
  * `pageToken?`: string — opaque cursor: omit it (or send `""`) to start from the newest pending withdrawal, then pass the prior response's `nextPageToken` to continue. The walk seeks on `(createdAt, withdrawalIndex)` descending, so a withdrawal requested mid-walk cannot make the walk repeat or drop a row

  There is no `page`: the SDK rejects a leftover one with a `ValidationError` before a request is sent, and the server answers `400`.

  **Returns:** `ListPendingWithdrawalsResponse`:

  * `withdrawals`: `PendingWithdrawal[]` — each with `withdrawalIndex`, `assetId`, `assetSymbol`, raw `amount` (smallest unit, stringified integer), `destination`, `status` (always `"pending"`) and RFC 3339 `createdAt`
  * `total`, `pageSize`, `totalPages`: number — exact counts
  * `nextPageToken`: string — pass back as `pageToken`; empty once the walk is exhausted. This listing is one ordered read, so a page shorter than `pageSize` is a definitive end

  **Throws:** `400` for invalid pagination; `401` without valid authentication; `429` when the enforced read budget is exhausted; `500` on an internal failure.
</ResponseField>

## Registered Deposit Addresses

Instead of the `approve` → `deposit` flow above, you can give a user a **stable, deterministic deposit address** and have deposits of the watched assets swept into the rollup automatically. Only the assets `sweeper.getChains` (below) lists are indexed — the transfer indexer filters logs to those token addresses — so an unlisted ERC20 sent to the address is not swept. Every `(application, user, deposit target)` triple maps to one such address; registering the triple makes the backend monitor it and forward a watched asset once its accumulated balance reaches that asset's minimum, crediting the user under the application — no per-deposit signature or transaction from your app.

<ResponseField name="sweeper.register" type="(body?: RegisterSweeperRequest) => Promise<RegisterSweeperResponse>">
  Register a deposit address so it is monitored and swept. **Requires authentication** — call `sdk.login()` first.

  Both halves of the identity come from the session: the application is the one `sdk.login()` was called with, and the credited address is the session's own. Neither is a request field, so this can only ever register the caller's own address under their own application — and the body carries nothing but the target.

  **Request — `RegisterSweeperRequest`** (optional; omit it entirely for a spot address):

  * `depositTarget?`: `"spot" | "margin"` — which ledger swept deposits **ask** to be credited to. Defaults to `"spot"`.
    * `"spot"` — credits the spot wallet (unchanged behavior).
    * `"margin"` — asks for the parent margin account's collateral, skipping the separate `transferCollateralToParentMarginAccount` step. A request, not a guarantee — see the note below.

  **Returns — `RegisterSweeperResponse`:**

  * `status`: `"registered"` (the triple was added by this call) or `"already_registered"` (it was known before).
  * `sweeperAddress`: string — the deposit address for this triple. Stable, and valid to publish and fund **before** any contract exists there.
  * `initialSweepCheck`: boolean — whether the one-time registration balance check has completed. `false` right after a fresh registration; it runs in the background and flips once every pre-existing balance has been swept.

  Idempotent and safe to retry within the current forwarder-based derivation: re-registering an existing triple returns `already_registered` with the same derived address. A completed initial check is never repeated; if `initialSweepCheck` is still `false`, registration schedules it again, while an already-running check is deduplicated.

  <Warning>
    **Pre-v1.0.53 registrations need an operator migration.** Calling register for one returns and monitors the new forwarder-derived address in the running resolver, but does not replace that row's stored address; after a resolver restart, warmup reloads only the old value. Pause deposits for that registration and do not display or fund the new address until Monaco confirms its stored row was migrated. Re-registration alone is not restart-durable.
  </Warning>

  **Example:**

  ```typescript theme={null}
  const { status, sweeperAddress, initialSweepCheck } = await sdk.sweeper.register();

  console.log(`Deposit address (${status}): ${sweeperAddress}`);
  // Show it to the user and let them fund it — deposits of the watched assets
  // (the ones `sweeper.getChains` lists) sweep in automatically.

  // Or ask for the margin address instead — a different address:
  const margin = await sdk.sweeper.register({ depositTarget: "margin" });
  ```

  **Errors:** `400` (a `depositTarget` that is neither `spot` nor `margin` — the SDK rejects it before sending — or a body that still carries the removed `clientId` or `userAddress`, which unknown-field rejection turns into a `400` rather than ignoring it; that one only reaches you from an untyped or hand-rolled request, since the typed request has no such fields), `401` (no or invalid session), `403` (the session's application is no longer active), `429` (the service-wide admission limit on *new* registrations — they create durable state, so they are bounded across all callers; re-registering an existing address is never limited, and a refused call stores nothing. The body's `retryAfter` is the limiter's own remaining wait in seconds, not a fixed window and not the 24-hour default other limits use — the bucket refills continuously, so it is usually short. Honor the value rather than assuming one), `500` (a server-side failure while registering), and `503` (the sweeper could not complete the registration — several unrelated conditions share this status, from missing configuration to an unreachable chain or store). Treat a `503` as an **unknown** outcome, not a negative one: the registration is stored before the reply is composed, so a failure reported after that write is indistinguishable from one reported before it, and the status alone never tells you which happened. Retrying is always safe — registration is idempotent, and the retry answers `registered` or `already_registered` according to whether the first attempt had landed. Both are success and both carry the same address, so re-register rather than reporting failure, and accept either status.
</ResponseField>

<ResponseField name="sweeper.getChains" type="() => Promise<GetSweeperChainsResponse>">
  List the chains and assets the sweeper watches. Public — it takes no arguments and needs no session, so a deposit page can show the supported set before the user logs in.

  Each `SweeperChain` carries `chainId` (the EVM chain id, a number — the service refuses ids above `Number.MAX_SAFE_INTEGER`, so it is always exact), a short `name`, a `hub` flag marking the settlement chain that deposits on every other listed chain are bridged into, and its `assets`. Each `SweeperChainAsset` carries `symbol`, the `tokenAddress` to send on that chain, `decimals` (`0` when the token does not publish them), and `minSweepRaw` — the minimum sweepable balance in raw base units, as a decimal string because raw amounts can exceed `Number.MAX_SAFE_INTEGER`. A balance strictly below the minimum stays on the deposit address until a later deposit brings the total up to it; a balance exactly at the minimum sweeps.

  **Example:**

  ```typescript theme={null}
  const { chains } = await sdk.sweeper.getChains();

  for (const chain of chains) {
    console.log(`${chain.name} (${chain.chainId})${chain.hub ? " — settlement hub" : ""}`);
    for (const asset of chain.assets) {
      console.log(`  ${asset.symbol} ${asset.tokenAddress} min ${asset.minSweepRaw}`);
    }
  }
  ```

  The listing is all-or-nothing: rather than publish a partial set, the resolver refuses the whole call whenever any configured chain cannot be advertised. How current it is depends on the chain's token source — a hub backed by the active-asset registry picks up a listing or delisting on the resolver's own refresh, with no restart, while a chain configured with an explicit token list changes only when that configuration is rolled out. The gateway also serves the last answer from a short cache. The two intervals stack — at the defaults the resolver refreshes its asset registry every 60 seconds and the gateway caches the resolver's answer for 60 seconds — so a change can take close to two minutes to appear. Treat the listing as recent, not live.

  **Errors:** `500` (a server-side failure) and `503` — both safe to retry. There is no `401`: the call is unauthenticated. The `503` covers more than a missing configuration: a configured chain disabled by its own configuration, a chain whose id the resolver has not yet verified against its RPC (an unreachable RPC included — until it verifies, no indexer is watching that chain, so it must not be advertised), a chain whose RPC reports an id outside the JSON-safe range, and a resolver that could not answer at all.
</ResponseField>

<Note>
  **Bind a deposit to the exact `(chainId, tokenAddress)` pair `getChains` returns.** Never resolve a token by `symbol` alone: the same symbol can front a different contract on another chain, and a deposit sent to the wrong contract or the wrong chain is not recoverable. Show the user the chain and the token address from the same entry you took the minimum from.
</Note>

<Note>
  **One registration covers every listed chain.** The address `register` returns is identical on, and watched on, every chain `getChains` lists, so there is nothing chain-specific to pass at registration. Use `getChains` to tell a depositor where they may send and which token address and minimum applies there — not to choose where to register.
</Note>

<Warning>
  **The deposit target is part of the address.** `"spot"` and `"margin"` derive **two different deposit addresses** for the same application and user, and each address carries its own target. Sending to the spot address never credits margin, whichever target you registered most recently. Show the user the address returned by the registration whose target you want, and keep the two apart in your UI.
</Warning>

<Note>
  **`"margin"` is a routing request, not a guarantee.** A deposit that arrives at a margin address but cannot be routed to collateral — an unsupported collateral asset, or a margin account that fails validation — is credited to the **spot wallet** instead, exactly as it is for a direct `deposit(..., "margin")`. Funds are never stranded, but do not treat a margin deposit address as proof the credit landed in collateral: read the balance back (`getParentMarginAccountSummary`) rather than assuming.
</Note>

<Note>
  Deriving the address off-chain: `@0xmonaco/contracts` exports `predictSweeperAddress({ factory, forwarder, clientId, user, depositTarget? })`, which returns the same EIP-55 checksummed address without an RPC round-trip. The `forwarder` is the fixed delegation target every clone points at — read it once from `factory.forwarder()`; the sweeper implementation is deliberately not a derivation input, so logic upgrades never move addresses. It still takes `clientId` explicitly — off-chain derivation has no session to read it from, and it is your own application's id — useful to display or fund an address before registering. Pass the same `depositTarget` you register with, or the prediction points at the other ledger's address. `encodeDepositApplicationData(clientId, target)` exposes the underlying encoding, and the `SWEEPER_ABI` / `SWEEPER_FACTORY_ABI` exports cover direct contract interaction.
</Note>

<Note>
  **`SweepParams` binds a sweep to an exact amount.** The executor-supplied struct the ABIs encode for `sweep(token, params)` / `sweepWithParams` leads with `amount`, ahead of `minSwapOut` and `minBridgeOut` — the exact balance the quote was computed for. The sweep moves exactly that much and leaves any excess, such as a deposit that landed after the quote, for the next re-quoted sweep, so the minimums always cover the full swept amount and a later arrival can never dilute them. A balance that has fallen below the quoted amount reverts with `StaleQuote` rather than sweeping less against minimums quoted for more: re-quote and retry. `amount: 0n` binds nothing and sweeps the execution-time balance, and is accepted **only** on the direct CCTP route, whose burn is exact-amount and carries no minimum that could go stale — every other route (Stargate, swap-then-bridge, and the hub unswap) rejects the sentinel with `QuoteNotBound`. A sweep against an empty balance reverts with `NothingToSweep`. Both errors are declared on `SWEEPER_ABI` only — `SWEEPER_FACTORY_ABI` carries the new `amount` field on `sweepWithParams` but declares neither — so decode factory-route reverts with both ABIs, not the factory's alone. Address derivation does not read `SweepParams`, so `predictSweeperAddress(...)` is unaffected by the struct's shape.
</Note>

## Token Approvals

<ResponseField name="approve" type="(assetId: string, amount: bigint, autoWait?: boolean) => Promise<TransactionResult>">
  Approve vault to spend tokens. Uses asset ID (UUID).
</ResponseField>

<ResponseField name="getAllowance" type="(assetId: string) => Promise<bigint>">
  Check current token allowance. Uses asset ID (UUID).
</ResponseField>

<ResponseField name="needsApproval" type="(assetId: string, amount: bigint, options?: DepositOptions) => Promise<boolean>">
  Check if approval is needed before deposit. Uses asset ID (UUID). Returns `false` for a `wrappedNative` asset (e.g. WSEI) without reading the on-chain allowance — its deposit goes through the payable `depositNative`, which consumes no allowance. Pass `{ asErc20: true }` to check the real allowance for depositing that wrapped-native asset as its ERC20 (matching a `deposit` with the same option).
</ResponseField>

## Deprecated Methods

<Warning>
  **vault.getBalance() has been deprecated** and no longer functions as of v0.5.5. Use the Profile API instead.
</Warning>

The `vault.getBalance()` method is still exported for compatibility but always throws an `APIError` with `statusCode: 410` client-side, without sending a request. For balance queries, use the Profile API which provides more detailed information:

```typescript theme={null}
// ❌ Deprecated (throws error in v0.5.5+)
const balance = await sdk.vault.getBalance(assetId);

// ✅ Use Profile API instead
const balance = await sdk.profile.getUserBalanceByAssetId(assetId);
console.log('Available:', balance.availableBalance);
console.log('Locked:', balance.lockedBalance);
console.log('Total:', balance.totalBalance);
```

**Why this changed:**

* Profile API provides more detailed balance information (available, locked, total)
* Consolidates balance queries with other profile data
* Improves performance with backend optimizations

**Get all balances:**

```typescript theme={null}
const { balances } = await sdk.profile.getUserBalances({ pageSize: 100 });
balances.forEach(b => {
  console.log(`${b.symbol}: ${b.totalBalance} (${b.availableBalance} available)`);
});
```

See [Profile API documentation](/sdk/typescript/profile) for more details.

## Getting Asset IDs

Asset IDs (UUIDs) are obtained from trading pairs. Each trading pair has `baseAssetId` and `quoteAssetId`.

```typescript theme={null}
// Get asset IDs from trading pair
const pair = await sdk.market.getTradingPairBySymbol('BTC/USDC');

// pair contains:
// - baseAssetId: UUID for the base token (e.g., BTC)
// - quoteAssetId: UUID for the quote token (e.g., USDC)
// - baseTokenContract: Contract address for base token
// - quoteTokenContract: Contract address for quote token
```

| Method | Parameter | Description |
| - | - | - |
| `deposit()` | `assetId` (UUID) | Monaco asset identifier |
| `withdraw()` | `assetId` (UUID) | Monaco asset identifier |
| `approve()` | `assetId` (UUID) | Monaco asset identifier |
| `needsApproval()` | `assetId` (UUID) | Monaco asset identifier |
| `getAllowance()` | `assetId` (UUID) | Monaco asset identifier |


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