ConditionalTokens. To convert them into XO USDC, the smart account must call redeemPositions as an ERC-4337 UserOperation through the XO bundler.
Mainnet bundler: https://mainnet-bundler.xo.market/
When a position is redeemable
GET /claimable?address=<smart-account> lists positions the orderbook considers settled. Treat that list as a discovery hint, not the on-chain gate.
ConditionalTokens.redeemPositions reverts with result for condition not received yet until the oracle has reported payouts. That happens when the market is closed on-chain (after the resolution period), not merely when the winner is recorded.
Confirm on-chain before sending a UserOperation:
0, wait and poll. A non-zero denominator means both resolved winners and voided refunds can be redeemed with the same call.
XO CLOB markets are binary. The manager reports
[1, 0] or [0, 1] when a winner is recorded, and [1, 1] when the market is voided. You do not choose an amount — redeemPositions burns the smart account’s full balance in each requested index set and pays stake × numerator / denominator in XO USDC.Call shape
Always pass both index sets. The losing side burns with a zero payout (a no-op on the transfer). Passing both also covers voided markets, where each side pays out at 50%.
msg.sender must be the smart account that holds the tokens. That is why the call is wrapped in a UserOperation executed by the smart account — signing redeemPositions from the owner EOA as a normal transaction does not redeem the smart account’s positions.
Submit through the bundler
- Encode
redeemPositionscalldata against Conditional Tokens (0xCcf1b2C676E2f9A13e5674be0e0DdFF6d0B13909). - Build an ERC-4337 v0.7 UserOperation whose inner call is that calldata (
to= Conditional Tokens,value=0). - Attach the XO paymaster (address from onboarding) with empty
paymasterData. The bundler estimates paymaster gas limits. - Sign the bundler-computed
userOpHashwith the owner EOA of the smart account (raw ECDSA, same key that signs orders). eth_sendUserOperationtohttps://mainnet-bundler.xo.market/.- Wait for the UserOperation receipt and check
success. A reverted UserOperation is still included in a bundle transaction that succeeds — do not treat the outer transaction hash as proof that the redeem ran.
0x0000000071727De22E5E9d8BAf0edAc6f37da032.
The paymaster allowlists
ConditionalTokens.redeemPositions, so redemption is gas-sponsored. The smart account does not need a native-token balance. Ask your XO contact for the mainnet paymaster address during onboarding.TypeScript (viem)
account is the same XO smart account you use as order.maker. How that object encodes execute / executeBatch is wallet-specific (Kernel ERC-7579 execute(bytes32,bytes) on accounts created at beta.xo.market). The inner call above is what must reach Conditional Tokens.
Several markets in one UserOperation
De-dupe by(collateralToken, conditionId) and batch one redeemPositions per condition. Passing [1, 2] redeems every outcome the smart account holds on that condition, so two /claimable rows for the same voided market become a single call.
Payout math
Shares and collateral are 6-decimal. A
/claimable balance of "50000000" is 50 shares.
On success, Conditional Tokens emits PayoutRedemption(redeemer, collateralToken, parentCollectionId, conditionId, indexSets, payout) and transfers payout XO USDC to the smart account.
Troubleshooting
Related
- Market lifecycle — when
/claimablestarts listing a position. - Smart accounts — owner vs smart account, and why on-chain CT calls go through the bundler.
GET /claimable— settled positions the orderbook knows about.