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.
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. 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
Pull deposit (Safe proxy only)
The pull path is three calls wrapped in a singleSafe.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.
1
Approve the vault
ConditionalTokens.setApprovalForAll(vault, true).2
Deposit
RobinStakingVault.batchDeposit(...) — the vault pulls the outcome tokens,
pairs and merges them, deposits to yield startegies, and mints shares.3
Deposit
RobinStakingVault.batchDeposit(...) — the vault pulls the outcome tokens,
pairs and merges them, deposits to the yield strategy, and mints shares.4
Revoke
ConditionalTokens.setApprovalForAll(vault, false).DepositWallets cannot use the pull path — the Polymarket relayer blocks
approve calls to the CTF. Use the push path below instead.batchDeposit signature:
deposit.ts in the robin-markets-integration example repo for the full Safe batch, including the TWAP prepend.
Push deposit (both wallet kinds)
The push path is a singleConditionalTokens.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:
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.
idsare the on-chain ERC-1155 token ids — the YES/NO positionIds. Polymarket’s/positionsdata API returns the held side asassetand the unheld side asoppositeAsset.- The vault rebuilds its own
(ids, values)from its cached positionIds and revertsPushDepositMismatchif yours do not match, orUnsolicitedTransferif the transfer is not from the CTF ordatafails to decode.
deposit-push.ts in the example repo for the end-to-end flow, including wallet resolution and relayer submission.
TWAP first
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 for the refresh
flow.Capacity
The vault has a finite, two-tier deposit capacity (it’s global, not per-market), and an over-capacitybatchDeposit reverts. Preview a batch before submitting — over HTTP with GET /api/v1/capacity, or on-chain with RobinLens.checkBatchDepositCapacity, which checks both tiers:
CapacityExceeded, tier 2 reverts SupplyOverflow. See Capacity for the tiers, the API, and reading the raw headroom.
Errors
Next steps
Withdrawing
Burn shares to redeem outcome tokens plus organic yield.
TWAP refresh
Keep a market’s TWAP fresh before depositing.
Wallets
Resolve the user’s Polymarket account and pick the transport.