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

# Depositing (staking)

> Stake Polymarket YES/NO tokens into the vault — the pull path (approve, batchDeposit, revoke) and the push path (a single CTF transfer).

Staking deposits Polymarket conditional tokens (YES/NO outcome tokens) into `RobinStakingVault`, which pairs and merges them to USDC, deposits that USDC into a yield strategy, and mints you ERC-1155 shares (one share token per market per side). There are two ways to deposit, and which one you use depends on the kind of Polymarket account you hold.

| Path     | Wallet kinds                      | Calls                                                                   | Notes                                                   |
| -------- | --------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------- |
| **Pull** | Safe proxy only                   | `setApprovalForAll(true)` → `batchDeposit` → `setApprovalForAll(false)` | The vault pulls tokens. DepositWallets cannot use this. |
| **Push** | Both (Safe proxy + DepositWallet) | a single `safeBatchTransferFrom` into the vault                         | The only path a DepositWallet can use.                  |

Tokens live in the user's resolved Polymarket account (a `DepositWallet` or a legacy Gnosis Safe proxy), not their EOA. Resolve the wallet once and key every read and write on it — see [Wallets](/developers/wallets). DepositWallets submit through the Polymarket relayer, which blocks `approve`, so they are restricted to the push path; Safe proxies submit through `Safe.execTransaction` and can use either.

## Sorting rule

<Warning>
  In every batch call, `conditionIds` **must be sorted strictly ascending with
  no duplicates**, or the call reverts with `UnsortedConditionIds`. All other
  batch arrays (`questionIds`, `yesAmounts`, `noAmounts`, and the push
  `ids`/`values`) must use that **same permutation** so each index lines up with
  its market.
</Warning>

## Pull deposit (Safe proxy only)

The pull path is three calls wrapped in a single `Safe.execTransaction` batch: grant the vault approval on `ConditionalTokens`, let it pull and process your tokens via `batchDeposit`, then revoke the approval in the same transaction so no standing allowance is left behind.

<Steps>
  <Step title="Approve the vault">
    `ConditionalTokens.setApprovalForAll(vault, true)`.
  </Step>

  <Step title="Deposit">
    `RobinStakingVault.batchDeposit(...)` — the vault pulls the outcome tokens,
    pairs and merges them, deposits to yield startegies, and mints shares.
  </Step>

  <Step title="Deposit">
    `RobinStakingVault.batchDeposit(...)` — the vault pulls the outcome tokens,
    pairs and merges them, deposits to the yield strategy, and mints shares.
  </Step>

  <Step title="Revoke">
    `ConditionalTokens.setApprovalForAll(vault, false)`.
  </Step>
</Steps>

<Note>
  DepositWallets **cannot** use the pull path — the Polymarket relayer blocks
  `approve` calls to the CTF. Use the push path below instead.
</Note>

The `batchDeposit` signature:

```solidity theme={null}
function batchDeposit(
    bytes32[] conditionIds,   // strictly ascending, no duplicates
    bytes32[] questionIds,    // Polymarket questionId per market, same permutation
    uint256[] yesAmounts,     // 6-decimal integers (YES=0)
    uint256[] noAmounts,      // 6-decimal integers (NO=1)
    uint256 nonZeroLength,    // count of non-zero amounts across yes+no
    uint256 referralCode      // 0 if none
) external;
```

| Param                      | Meaning                                                                                                                                                                                                                                                                                                                                                                    |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conditionIds`             | The `bytes32` Polymarket condition ids, strictly ascending with no duplicates.                                                                                                                                                                                                                                                                                             |
| `questionIds`              | The Polymarket `questionId` per market, from Polymarket Gamma, aligned to `conditionIds`. Used only to auto-initialise a market on its **first ever deposit** (the vault checks `conditionId == keccak256(abi.encodePacked(oracle, questionId, uint256(2)))`). Ignored for already-initialised markets, but the array must still be present, aligned, and the same length. |
| `yesAmounts` / `noAmounts` | Amounts to stake per side as 6-decimal integers (`UNDERLYING_DECIMALS=6`). Either side may be zero; only the row as a whole must be non-empty.                                                                                                                                                                                                                             |
| `nonZeroLength`            | The number of non-zero entries across `yesAmounts` and `noAmounts`.                                                                                                                                                                                                                                                                                                        |
| `referralCode`             | A referral code, or `0` for none.                                                                                                                                                                                                                                                                                                                                          |

```ts theme={null}
import { encodeFunctionData } from "viem";

// Three calls, batched into one Safe.execTransaction.
const approve = encodeFunctionData({
  abi: conditionalTokensAbi,
  functionName: "setApprovalForAll",
  args: [VAULT, true],
});

const deposit = encodeFunctionData({
  abi: stakingVaultAbi,
  functionName: "batchDeposit",
  args: [conditionIds, questionIds, yesAmounts, noAmounts, nonZeroLength, 0n],
});

const revoke = encodeFunctionData({
  abi: conditionalTokensAbi,
  functionName: "setApprovalForAll",
  args: [VAULT, false],
});
```

See `deposit.ts` in the robin-markets-integration example repo for the full Safe batch, including the [TWAP](/developers/twap) prepend.

## Push deposit (both wallet kinds)

The push path is a **single** `ConditionalTokens.safeBatchTransferFrom(wallet, vault, ids, values, data)` into the vault. The vault's `onERC1155BatchReceived` hook decodes `data` and runs the entire deposit pipeline atomically — no approval, no standing allowance, one vault-side call. This is the only path a DepositWallet can use, and it works for Safe proxies too.

The `data` argument carries exactly the same payload as `batchDeposit`:

```solidity theme={null}
data = abi.encode(
    bytes32[] conditionIds,
    bytes32[] questionIds,
    uint256[] yesAmounts,
    uint256[] noAmounts,
    uint256   nonZeroLength,
    uint256   referralCode
);
```

### Building `ids` and `values`

`ids` and `values` are **dense and sorted**. Iterate markets in ascending `conditionId`; for each market, push the YES side first (if `yesAmount > 0`), then the NO side (if `noAmount > 0`), skipping zero sides. `ids.length` must equal `nonZeroLength`.

* `ids` are the on-chain ERC-1155 token ids — the YES/NO **positionIds**. Polymarket's `/positions` data API returns the held side as `asset` and the unheld side as `oppositeAsset`.
* The vault rebuilds its own `(ids, values)` from its cached positionIds and reverts `PushDepositMismatch` if yours do not match, or `UnsolicitedTransfer` if the transfer is not from the CTF or `data` fails to decode.

```ts theme={null}
import { encodeAbiParameters, encodeFunctionData } from "viem";

// Dense, sorted: per market YES then NO, skipping zero sides.
const ids: bigint[] = [];
const values: bigint[] = [];
for (const r of sortedRows) {
  if (r.yesAmount > 0n) {
    ids.push(r.yesPositionId); // Polymarket `asset` / `oppositeAsset`
    values.push(r.yesAmount);
  }
  if (r.noAmount > 0n) {
    ids.push(r.noPositionId);
    values.push(r.noAmount);
  }
}
if (BigInt(ids.length) !== nonZeroLength)
  throw new Error("ids.length must equal nonZeroLength");

// ABI-encode the deposit payload the hook decodes (same args as batchDeposit).
const data = encodeAbiParameters(
  [
    { type: "bytes32[]" },
    { type: "bytes32[]" },
    { type: "uint256[]" },
    { type: "uint256[]" },
    { type: "uint256" },
    { type: "uint256" },
  ],
  [conditionIds, questionIds, yesAmounts, noAmounts, nonZeroLength, 0n],
);

const transfer = encodeFunctionData({
  abi: conditionalTokensAbi,
  functionName: "safeBatchTransferFrom",
  args: [wallet, VAULT, ids, values, data],
});
```

See `deposit-push.ts` in the example repo for the end-to-end flow, including wallet resolution and relayer submission.

## TWAP first

<Note>
  Each market's TWAP must be **fresh** before the vault accepts a deposit for it
  — the TWAP determines how yield is split between the YES and NO sides. Prepend
  a `RobinTwapOracle.submitTwap(...)` call to your batch (or skip it if Robin
  already submitted on-chain). See [TWAP](/developers/twap) for the refresh
  flow.
</Note>

## Capacity

The vault has a finite, **two-tier** deposit capacity (it's global, not per-market), and an over-capacity `batchDeposit` reverts. Preview a batch before submitting — over HTTP with `GET /api/v1/capacity`, or on-chain with `RobinLens.checkBatchDepositCapacity`, which checks **both** tiers:

```ts theme={null}
const ok = await publicClient.readContract({
  address: LENS,
  abi: lensAbi,
  functionName: "checkBatchDepositCapacity",
  args: [conditionIds, yesAmounts, noAmounts], // pass the WHOLE batch — capacity is vault-wide
});
if (!ok) throw new Error("Deposit would exceed vault capacity");
```

Tier 1 reverts `CapacityExceeded`, tier 2 reverts `SupplyOverflow`. See [Capacity](/developers/capacity) for the tiers, the API, and reading the raw headroom.

## Errors

| Error                                 | Cause                                                                                                                                                                              |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UnsortedConditionIds`                | `conditionIds` not strictly ascending, or contains a duplicate.                                                                                                                    |
| `ZeroAmount`                          | A counted amount is zero.                                                                                                                                                          |
| `LengthMismatch`                      | Batch arrays are not the same length.                                                                                                                                              |
| `CapacityExceeded(needed, available)` | Deposit exceeds the vault's tier-1 (internal) capacity. See [Capacity](/developers/capacity).                                                                                      |
| `SupplyOverflow(remaining, supplied)` | Paired USDC exceeds the vault's tier-2 (external ERC-4626) capacity.                                                                                                               |
| `MarketSideBroken`                    | A market side is not in a depositable state.                                                                                                                                       |
| `UnlistedCondition(conditionId)`      | The `conditionId` is not listed in the vault.                                                                                                                                      |
| `MarketNotInitialized(conditionId)`   | The market has not been initialised. Both paths auto-initialise on a market's first deposit using the supplied `questionIds`, so an aligned, correct `questionId` must be present. |
| `PushDepositMismatch`                 | Push `ids`/`values` do not match the vault's cached positionIds.                                                                                                                   |
| `UnsolicitedTransfer`                 | The transfer is not from the CTF, or `data` failed to decode.                                                                                                                      |

## Next steps

<CardGroup cols={3}>
  <Card title="Withdrawing" icon="arrow-up-from-bracket" href="/developers/withdrawals">
    Burn shares to redeem outcome tokens plus organic yield.
  </Card>

  <Card title="TWAP refresh" icon="clock" href="/developers/twap">
    Keep a market's TWAP fresh before depositing.
  </Card>

  <Card title="Wallets" icon="wallet" href="/developers/wallets">
    Resolve the user's Polymarket account and pick the transport.
  </Card>
</CardGroup>
