> ## 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.

# Split Vault (yield source)

> RobinSplitVault is the ERC-4626 vault that holds the staking vault's merged USDC.e as hedged YES+NO pairs. Interface, accounting, the compound keeper call, and direct deposits.

`RobinSplitVault` is a UUPS-upgradeable **ERC-4626** vault (`rsvUSDC`, asset USDC.e) that deploys deposits into equal YES+NO conditional-token pairs on a single manager-selected Polymarket market enrolled in Polymarket's holder-rewards programme. The market must be a Conditional Tokens Framework (v1) market; the vault cannot hold [Protocol V2](/polymarket-v2) positions. A pair always merges back to exactly 1 USDC.e, so withdrawals are instant and at par. The staking vault supplies all merged USDC.e here; anyone else can deposit USDC.e directly.

Addresses are on the [contracts overview](/contracts/overview#split-vault-yield-source).

## Accounting

The vault only ever reads its own books, never live balances, when pricing shares:

* **`trackedAssets`** — net deposits plus every yield tranche recognized so far.
* **`totalAssets()`** — `trackedAssets` minus the still-vesting remainder (`unvestedProfit()`). This is what ERC-4626 conversions use.
* **`pendingYield()`** — everything the vault holds above `trackedAssets` (PolyUSD at par, idle USDC.e, extra pairs). Invisible to the share price until the next `compound()`.

`compound()` folds pending yield into a linear vesting schedule. A tranche vests over the period it accrued, clamped to `[baseVestingDuration, 30 days]` (base is 24h on mainnet, floor 8h); it is blended by amount with whatever is still vesting. Net effect: the share price never jumps, yield drips at roughly its earn rate, and neither a daily payout nor a compound backlog can be sniped by a short-lived deposit.

<Note>
  Share decimals are `12` (6 asset decimals + a virtual offset of 6). Yield is
  only recognized while at least `1e12` shares (1 USDC.e at genesis price)
  exist; below that it stays pending (`YieldPending`) rather than accruing to
  virtual shares.
</Note>

## Functions

| Function | Access | What it does |
| - | - | - |
| `deposit` / `mint` / `withdraw` / `redeem` | anyone | Standard ERC-4626. Deposits are split into pairs immediately; withdrawals merge only the pairs needed. |
| `compound()` → `(bool unwrapped, uint256 recognized)` | anyone | The daily keeper call. Unwraps PolyUSD (best effort), splits idle USDC.e into pairs, recognizes pending yield into the vesting schedule. |
| `unwrapRewards(uint256 amount)` | anyone | Unwrap `amount` PolyUSD to USDC.e via the offramp (`0` = all). Reverts loudly; use to drain a backlog a thin offramp cannot cover at once. |
| `setMarket(bytes32 conditionId)` | manager | Merge all pairs, re-split into the new market. `bytes32(0)` exits to idle USDC.e. Binary, non-neg-risk CTF (v1) markets only. |
| `setBaseVestingDuration(uint64)` | manager | Minimum vesting duration for future tranches, in `[8 hours, 30 days]`. |
| `setOfframp(address)` | timelock | Rotate the PolyUSD → USDC.e offramp. Must have code. |
| `activeMarket()` | view | `(conditionId, yesPositionId, noPositionId)`. |
| `pairedAssets()`, `trackedAssets()`, `unvestedProfit()`, `pendingYield()` | view | See Accounting above. |
| `vestingInfo()` | view | `(amount, start, end, baseDuration)` of the current schedule. |
| `lastRecognitionAt()` | view | Timestamp the next tranche's accrual period is measured from. |

## Keeper: calling compound

`compound()` is permissionless and idempotent. Robin runs it daily after Polymarket's payout; anyone may call it more often. If the offramp is paused the PolyUSD leg is skipped and `UnwrapFailed` is emitted — USDC.e and pair yield are still recognized, and the accrual clock stays frozen until an unwrap succeeds so the backlog cannot later drip faster than it was earned.

```solidity theme={null}
(bool unwrapped, uint256 recognized) = splitVault.compound();
// unwrapped == false  → PolyUSD is stuck; eth_call unwrapRewards(0) for the revert reason
```

## Direct deposits from a DepositWallet

Polymarket DepositWallets cannot call `approve`, so they cannot use `deposit` directly. `RobinPushDepositRouter` is a stateless, unowned periphery for that case. Batch **atomically**:

1. `USDC.e.transfer(router, assets)`
2. `router.depositFor(splitVault, assets, wallet)` — mints `rsvUSDC` to `wallet`. Pass `type(uint256).max` to use the router's whole balance.

Redeeming needs no allowance (`redeem` with `owner == msg.sender`).

<Warning>
  Never leave funds in the router between transactions. Anything parked there
  belongs to nobody and can be claimed by anyone via `depositFor` or `rescue`.
</Warning>

## Events

| Event | Fields |
| - | - |
| `Compounded` | `recognized`, `deployed`, `vestingAmount`, `vestingStart`, `vestingEnd` |
| `UnwrapFailed` | (PolyUSD leg skipped; monitor this) |
| `YieldPending` | `pending` (yield found but supply below the recognition floor) |
| `BooksClosed` | `trackedAssets`, `vestingAmount` (last holder exited; residual returns to pending) |
| `MarketUpdated` | `oldConditionId`, `newConditionId` |
| `BaseVestingDurationUpdated` | `oldDuration`, `newDuration` |
| `OfframpUpdated` | `oldOfframp`, `newOfframp` |

## Roles

| Role | Holder | Powers |
| - | - | - |
| `DEFAULT_ADMIN_ROLE`, `DEFAULT_MANAGER_ROLE` | Team multi-sig | `setMarket`, `setBaseVestingDuration`, role admin |
| `TIMELOCKED_ROLE` (self-administered) | RobinTimeLockController | Upgrades, `setOfframp` |

## Next steps

<CardGroup cols={2}>
  <Card title="Capacity" icon="gauge" href="/developers/capacity">
    The Split Vault's headroom is the staking vault's tier-2 deposit limit.
  </Card>

  <Card title="Architecture" icon="sitemap" href="/developers/architecture">
    How the staking vault routes merged USDC.e into the Split Vault.
  </Card>
</CardGroup>


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