Skip to content

PolymarketAdapter

The PolymarketAdapter is the concrete implementation of the IMarketAdapter interface for the Polymarket CLOB venue. It satisfies the interface ABI but diverges from the generic state variable model due to Polymarket's Conditional Token Framework (CTF/ERC-1155) architecture.

Interface compatibility

The vault interacts with PolymarketAdapter exclusively through IMarketAdapter. All function selectors are identical. The differences below are internal to the adapter and invisible to the vault.


3a.1 Constructor

constructor(
    address _vault,        // LiquidityVault. Immutable. onlyVault modifier target.
    address _asset,        // USDC token. Immutable.
    address _exchange,     // CTF Exchange (CLOB) address. Immutable.
    address _ctf,          // Conditional Token Framework (ERC-1155) address. Immutable.
    bytes32 _conditionId,  // Polymarket condition identifier. Immutable.
    address _operatorAddr  // Operator address authorised to push price and stage orders.
)

Differs from generic spec (§10.3): The generic interface constructor takes (vault, asset, noToken, marketId). The PolymarketAdapter replaces noToken and marketId with exchange, ctf, conditionId, and operatorAddr — required by Polymarket's CTF/ERC-1155 architecture.

NO token ID derivation: Polymarket NO tokens are ERC-1155 positions, not simple ERC-20 tokens. The NO token ID is derived on-chain:

collectionId = ctf.getCollectionId(bytes32(0), conditionId, noIndexSet)
noTokenId    = ctf.getPositionId(asset, collectionId)

positionSize() returns IERC1155(ctf).balanceOf(address(this), noTokenId). Polymarket NO tokens are 1e18 precision, matching the spec's unit requirement.


3a.2 Price Feed — Operator Push Model

currentPrice() returns a stored value updated by the operator via updateCurrentPrice(uint256 price) — no on-chain price oracle is queried.

Why: Polymarket's CLOB has no on-chain price feed. The current best-bid must be read from the Polymarket API off-chain and pushed on-chain before each keeper run.

Price staleness risk

The operator must push a fresh price in the same block or transaction bundle as the keeper call to minimise staleness risk. If the operator pushes a stale price and the keeper processes withdrawals, the exit curve is computed against an inaccurate market NAV. This is the primary operational risk of this adapter.


3a.3 Buy and Sell — Signed Order Pre-Staging

Polymarket uses off-chain order matching. The vault cannot construct or sign a valid CLOB order on-chain. The flow is two-step:

Entry (buyNoShares):

  1. Operator constructs a signed counterparty order off-chain and calls setPendingBuyOrder(SignedOrder, expectedPrice) to stage it in the adapter.
  2. Vault calls buyNoShares(assets) which fills the pre-staged signed order atomically via the CTF Exchange.

Emergency exit (sellNoShares):

  1. Operator constructs a signed sell order off-chain and calls setPendingSellOrder(SignedOrder).
  2. Vault calls sellNoShares(shares) which caps at the actual ERC-1155 balance, fills the pre-staged signed order atomically, and returns (actualSharesSold, usdcReceived) computed via before/after balance diff.

Interface compliance: The staging step is adapter-internal and invisible to the vault. The vault still calls buyNoShares and sellNoShares with the standard IMarketAdapter signatures.


3a.4 Settlement Detection

isSettled() checks whether the CTF payoutDenominator for the condition is non-zero — which Polymarket sets when resolution is finalised after the UMA dispute window closes.

This satisfies the spec requirement that isSettled() returns false during any dispute window, because Polymarket does not set the denominator until resolution is complete and final.

The operator calls reportSettlement(uint256 finalPrice) to confirm finality. isSettled() returns the settled boolean set by that call, gated by the on-chain ctf.payoutDenominator(conditionId) > 0 check.


3a.5 Settlement Claim

claimSettlement() calls ctf.redeemPositions(asset, bytes32(0), conditionId, [2]) — the NO position index set. The CTF burns the NO ERC-1155 tokens and transfers the pro-rata USDC collateral to the adapter, which forwards it to the vault.

The [2] partition index corresponds to the NO outcome in a binary market (YES = 1, NO = 2).


Slippage Guards

Operation Max slippage Enforced in
buyNoShares 50 bps Compared against expectedPrice staged by operator
sellNoShares 200 bps Compared against currentPrice() at call time

Both match the spec requirements (§7.6 and §3.3).


Summary of Differences from Generic Spec

Area Generic spec (§3, §10.3) PolymarketAdapter
Constructor (vault, asset, noToken, marketId) (vault, asset, exchange, ctf, conditionId, operatorAddr)
currentPrice() Queried from venue on-chain Operator-pushed stored value
buyNoShares / sellNoShares Direct execution on call Two-step: operator stages order, vault fills
isSettled() Oracle flag CTF payoutDenominator > 0 + operator reportSettlement
claimSettlement() Simple USDC transfer ctf.redeemPositions burns ERC-1155, returns USDC
positionSize() ERC-20 balance ERC-1155 balance at derived noTokenId