Skip to main content
Positions are your exposure to specific market outcomes. quantity mirrors the outcome tokens your vault holds on-chain (trades, splits, merges and redemptions all move it); cost basis and P&L are computed from your trade, split and merge history.

Reading positions

Response:
The rows are under positions — the response is an object, not a bare array. Every stats metric carries { value, status }; status is unavailable when a metric could not be computed exactly, so branch on it rather than trusting a 0. nextCursor / hasMore page it; omit limit and cursor to get the full list in one call.

Field semantics

  • quantity — outcome shares your vault holds on-chain for this outcome (decimal string, up to 6 decimals, trailing zeros trimmed: "250", "250.5"). It is net of the in-kind BUY fee and is moved by splits, merges and redemptions as well as trades. When a market resolves YES, every YES share redeems for 1 USDC, so this number is also your maximum payout if the outcome wins.
  • avgCost — weighted average cost per share held (decimal USDC): the cash you paid divided by the shares you actually received. null, with accountingStatus: "unavailable", when the cost basis cannot be computed exactly.
  • currentPrice — current price of this outcome token (decimal string); null, with pricingStatus: "unavailable", when no price is available.
  • realizedPnl — P&L already booked: on each SELL, the proceeds minus the sell fee and the cost of the shares sold; once a resolved position has been redeemed (or has lost), also its payout minus its cost.
  • unrealizedPnl — markValue − costBasis at currentPrice; null when the price or the cost basis is unavailable.
  • feesPaidUsdc — lifetime trading fees paid on this (market, outcome) in USDC (decimal string, "0" when none). BUY fees are taken in shares and converted to USDC face value; SELL fees are USDC. It is already reflected in the other figures — a BUY fee shows up as a higher avgCost (fewer shares received for the same cash), and a SELL fee is deducted from realizedPnl — so don’t subtract it again; it is reported for display and fee reconciliation.
  • feesPaidShares — lifetime outcome-token fees taken on BUY legs (decimal string, "0" when none; same scale as quantity). The in-kind haircut: you matched N shares and received N − feesPaidShares on chain.
  • feesPaidSharesPrice — the price the BUY-leg token fee was valued at, so feesPaidUsdc can be split into its in-kind and cash halves:
    It is a share-weighted average across the position’s BUY legs, and it is not feesPaidUsdc / feesPaidShares — that identity breaks as soon as the position has a SELL-leg cash fee. null when no token fee was ever taken; feesPaidShares is then "0", so tokenFeeUsd is 0 and all of feesPaidUsdc is cash. Branch on the null rather than evaluating the formula through it — any wallet that has only ever sold has that shape.
On MINT fills (both sides buying, settled via splitPosition) these three fields are computed from the backend fee ledger at the matched price, which can differ from the amount actually taken on chain. Treat them as indicative rather than chain-exact for those fills; /api/me/fees carries the per-fill rows behind them.

Redeeming a resolved position

With auto-redeem on (the default), the platform redeems voided-market refunds and winnings of at least 1 USDC per market into your vault within a few minutes, and those positions turn redeemed without a call from you. Whatever is still redeemable — smaller winnings, or anything while auto-redeem is off — you claim yourself. Auto-redeem also waits while negRiskLinkagePending is true. The default open view lists neither kind: pass status=redeemable to list what is still to claim and status=redeemed for what has been claimed. Once a market resolves, six fields drive the claim flow — plus the negRiskLinkagePending fail-safe flag below. They are all on the /api/me/positions response; redeemable is the gate. Pick the redeem call from the data, not from the market’s name — but check negRiskLinkagePending first, because it is the one case where negRisk being null does not mean “binary”:
  • negRiskLinkagePending == true → no redeem call is correct yet. See the warning below.
  • else negRisk == null → the binary path (conditionId).
  • else → the neg-risk path (negRisk.marketId, negRisk.questionIndex).
negRiskLinkagePending: true — do not offer the claim.It means the market is neg-risk-eligible but its bundle id is not yet available, so negRisk is null and there is no correct call to make. Taking the binary Vault.redeemPositions branch anyway succeeds on-chain while burning 0 tokens for 0 USDC — the transaction confirms, the user pays gas, and nothing is redeemed. Render the claim as disabled or “preparing” and re-check on the next poll.

Split / Merge

Availability: split / merge endpoints are available via partner onboarding. The flow requires dual-signature calls to the user’s vault.

Split — USDC → full outcome set

amount is human-readable USDC. On a neg-risk market also send outcomeIndex (the outcome whose YES + NO pair to mint). The response carries signedOpId, the backend co-signature backendSig, and the exact payload the vault verifies: kind, collateralToken, conditionId, partition, amount (6-decimal units), salt and deadline. Sign that SplitPosition typed data with the vault owner’s key, then before deadline either post { signedOpId, ownerSig, backendSig } to POST /api/me/positions/op-submit (the platform broadcasts it and pays the gas) or call vault.splitPosition(kind, collateralToken, conditionId, partition, amount, salt, deadline, ownerSig, backendSig) yourself. Typed-data layout and domain: Vault contract reference. Result: 100 USDC → 100 YES + 100 NO (binary).

Merge — full outcome set → USDC

Symmetric: POST /api/vault/merge-signature with the same body, sign MergePositions, then submit the same way (or call vault.mergePositions(...)). Burns 1 YES + 1 NO, returns 1 USDC. Both operations are dual-sig, state-machine-driven, and audited.