# Welcome to OPINION

## **Our Vision**

OPINION democratizes access to global trading by eliminating cross-market frictions and providing a seamlessly integrated platform with unified liquidity.&#x20;

## **What We Are Building**

OPINION is building *the People’s Terminal for Global Economic Trading* — a high-performance prediction exchange that transforms economic insights into tradable markets.\
By combining **AI oracles**, **on-chain trading infrastructure**, and **DeFi composability**, OPINION enables anyone to act as a market economist — trading global macro signals without institutional barriers or expensive terminals.

At the heart of its architecture is the **Opinion Stack**, a four-layer system powering its mainnet:

* [**Opinion.Trade**](https://app.opinion.trade/macro) – The live prediction exchange where users can create, trade, and resolve real-world markets.
* **Opinion AI** – The first decentralized multi-agent AI oracle capable of resolving complex, unstructured data. Beyond resolution, Opinion AI also assists in the creation of **permissionless markets**, helping users generate rigorous, objective rules and verifying whether a topic meets resolvability standards. This ensures that open market creation can scale safely, transparently, and trustlessly.
* **Opinion Metapool** – Unified liquidity infrastructure ensuring deep cross-market liquidity and resolution trust.
* **Opinion Protocol** – A universal token standard enabling interoperability across prediction venues.

Through this stack, OPINION is standardizing economic risk as a new, transparent, and tradeable asset class — bridging the gap between traditional macro instruments and permissionless participation.

[Linktree](http://linktr.ee/Opinionlabs) | [X](https://x.com/opinionlabsxyz) | [Telegram](https://t.me/opinionlabs_channel) | [Launch App](https://app.opinion.trade/macro) |


# About OPINION

## **Abstract**

### **1.1 Vision**&#x20;

OPINION is building a series of trading infrastructures that enable direct trading of macroeconomic data, predictions, and news as standardized assets, leveraging proprietary on-chain infrastructure, AI Oracle, and trading tools to create new opportunities for retail users, institutions, and global decision makers in democratizing economic insights and risk management.

### **1.2 Problem and Solution**&#x20;

Macro trading remains largely inaccessible due to infrastructure limitations and expensive institutional tools. Both retail and institutional investors currently lack direct access to economic indicators like inflation and interest rates, instead relying on trading volatile proxies. OPINION creates a prediction exchange with integrated data dashboards and trading tools, enabling anyone to directly trade macro indicators and news outcomes while democratizing access to sophisticated financial instruments previously reserved for institutions.

### **1.3 The Proxy Trading Problem**&#x20;

Current financial infrastructure hasn't yet evolved to support direct trading of economic data itself. When you believe the Fed will cut rates, existing systems don't offer tools to trade that belief directly—market participants typically rely on proxies like gold, bonds, or crypto.

**Consider this real scenario:**&#x49;n Q3 2025, with 90% market consensus on a 25 bps Fed cut, you have two available options: position on BTC's next move from already-elevated levels around $110,000, or directly trade "Fed cuts 25 bps" at 89% chance for clean 11% returns. The latter would offer pure exposure to your actual economic belief without additional market noise.

This reliance on proxy instruments creates structural inefficiencies:

* **Complexity**: Every proxy reflects multiple unrelated factors, diluting intended macro exposure
* **Indirection**: Multiple layers of reasoning required to connect trades to desired outcomes
* **Noise**: Unrelated market forces interfere with clean economic signals

Even sophisticated institutions face these limitations. Hedge funds seeking CPI protection must construct complex swap structures through investment banks—expensive, illiquid, and lacking transparency. OPINION addresses this gap by enabling direct trading of economic indicators themselves, creating the first standardized marketplace for pure macro risk.

### **1.4 Dual-Side Value Creation**

**For Retail Traders:**

* Direct access to institutional-grade macro instruments previously limited to expensive professional data platforms
* Clean exposure to economic beliefs without proxy volatility
* AI-driven data visualization making complex economic indicators intuitive
* Opportunity to monetize economic insights and local knowledge

**For Institutions:**

* **Quantitative Funds**: Real-time probability distributions as trading signals, direct FOMC decision probability trading
* **Banks & Asset Managers**: Package prediction contracts into new investment products, offer clients precise macro hedging tools
* **Investment Banks**: Enhanced research capabilities through market-derived consensus data
* **Corporate Treasuries**: Efficient hedging against policy risks and economic uncertainties

### **1.5 Future Applications & Ecosystem Integration**

OPINION's prediction markets represent the evolution toward a new standardized asset class where economic risk itself becomes tradeable. As these markets mature, we envision unprecedented integration possibilities across the financial ecosystem.

**DeFi Composability**: Each prediction contract functions as a programmable financial primitive. DeFi protocols could automatically adjust risk parameters based on CPI prediction signals, while prediction tokens serve as collateral for lending, unlocking new capital efficiency models. Smart contracts could execute sophisticated macro hedging strategies across multiple protocols simultaneously.

**Institutional Innovation**: Quantitative funds could leverage real-time probability distributions as trading signals, while asset managers package prediction contracts into structured products offering pure macro exposure. Investment banks could enhance research capabilities by incorporating market-derived consensus data, creating more nuanced client advisory services.

**Real-World Integration**: Economic forecasting could evolve from centralized expert analysis to decentralized market intelligence, where thousands of participants—from professional analysts to industry practitioners—contribute insights backed by actual capital. This creates opportunities for policy hedging, earnings positioning, macro arbitrage, and entirely new categories of risk management products.

This infrastructure enables the transition from passive economic information consumption to active prediction participation, where collective intelligence and market forces converge to create more transparent and efficient economic forecasting systems.

### **1.6 The Future of Economic Intelligence**&#x20;

OPINION represents the fourth evolution in crypto: from Bitcoin's monetary infrastructure to DeFi's financial accessibility to RWA's asset democratization, and now to InfoFi's information democratization. We're building infrastructure where economic intelligence emerges from market mechanisms rather than institutional gatekeepers, creating a more transparent, efficient, and inclusive global economic system.

When anyone can directly trade CPI, interest rates, and employment data, we witness not just a new financial primitive, but the dawn of a truly democratic economic forecasting system—powered by collective intelligence and validated by market forces.


# Connect with Web3 wallet

## Choose a wallet

If you have any crypto wallets already installed, you will be automatically seeing the options.&#x20;

Click on the wallet option, the app will try to **Connect** your selected wallet to <https://app.opinion.trade>.

<figure><img src="/files/dFdADU56xKTFi0TOprGI" alt=""><figcaption></figcaption></figure>

#### Wallets We Support :

* Binance Wallet
* OKX Wallet
* MetaMask
* Backpack
* Coinbase Wallet

## Sign in

Once you approved to connect to the app. You will be asking to sign a message, approve to log in the app. &#x20;


# Connect with Social Account

## Connect your Google or X account to opinion.trade

<figure><img src="/files/xEFarkNWYZDgnTV8Pwcw" alt=""><figcaption></figcaption></figure>


# Trade on prediction market


# Understanding market prices

Prices reflect the probability of an event happening, based on current trading activity.

For example:

In this market **“Will the US FOMC keep the interest rate unchanged?”**, if **‘Yes’** shares are priced at **65c**, that suggests a **65%** chance that the Federal Reserve will **not change** the interest rate. Similarly, if the price of **‘Yes’** shares is **10c**, it indicates only a **10%** probability of no rate change.

You can also express your position through **‘No’** shares. For instance, if **‘No’** shares are priced at **80c**, that suggests an **80%** chance that the FOMC **will change** the interest rate.


# How to profit?

In the previous example, a trader who believes there is a higher than 65% chance that the **US FOMC will keep the interest rate unchanged** could place a limit order to buy **‘Yes’** shares at **65c** each. If the order is successfully filled and the FOMC indeed makes **no change**, those shares will settle at **$1**, resulting in a **35c profit per share**. This equates to a **53.85% return** (35c gain on a 65c cost). Conversely, traders holding **‘No’** shares would lose their investment as those shares would settle at **$0**.

Moreover, after your order is filled, you can buy or sell your shares at any time before the market resolves, you don’t need to wait until the final decision date to realize your gains. For instance, you could have purchased **‘Yes’** shares in a market like *“Will the US FOMC keep the interest rate unchanged?”* for **20c**, and later sold them for **70c** if the market’s expectations shifted—locking in your profit before the event concludes.


# Start trading

## Choosing Market

On [opinion.trade](https://app.opinion.trade/), finding a market is easy. Head to the market page to view all available markets. From there, you can refine your search using filters, sorting options, and search tools to target the events you're most interested in.

## Buying Shares

After selecting the market you want to trade, click on it to see all the relevant details.\
On the market page, you’ll find a graph, market rules, and current odds to guide your decision.\
To make your trade, follow these steps using the ‘Buy’ panel on the right:

1. Choose the outcome you wish to buy (generally Yes or No).
2. Enter your desired dollar amount.
3. Click Buy to confirm.

<figure><img src="/files/2ZjLWQuZKpemzrbK0IYE" alt=""><figcaption></figcaption></figure>

After your trade goes through, you'll receive a confirmation notification. Congratulations—you’ve made your first trade on opinion.trade!

Keep in mind, you’re free to sell your position at any point before the market closes, using the current odds.


# Understanding the order book

Understanding the Order Book is crucial for advanced trading. We’ve already discussed market orders, which are executed instantly at the current price. However, if you think the market price is too high, you can also place the limit order as the price you want.

## Limit Orders Explained

Limit orders are executed only when the market matches your chosen price.\
For instance, if you’re willing to buy “no” shares in “Will the US FOMC keep the interest rate unchanged?” for 90c, but the market price is 97.7c, you can place a limit order at 90c and wait for a seller to match your price.

💡Pro Tip: Limit orders can be partially filled as traders meet your price, so your order may not fill all at once.

## Steps to Place a Limit Order

1. In the ‘Buy’ menu, select ‘Limit’ as the order type.
2. Enter your desired price (or the price at which you want to sell).
3. Specify the number of shares you wish to trade at that price.
4. (Optional) Set an expiration date for your order, so it cancels if unfilled within that time frame.
5. Click ‘Confirm Buy for $xxx’.

## Viewing the Order Book

The order book shows all open buy and sell orders in a given market.\
For example, in the market “**US FOMC Interest Rate** **Decision**,” we are viewing the order book for Trump “Yes” shares.

* The Green Side displays the “bid,” which is the highest price buyers are willing to pay for “Yes” shares.
* The Red Side displays the “ask,” which is the lowest price sellers are willing to accept for “Yes” shares.

💡Notice the gap between the highest bid and the lowest ask price. This gap is called the spread.

<figure><img src="/files/7rrFc3aJgi6G9OWduVE7" alt=""><figcaption></figcaption></figure>


# Managing open orders

When you have an open order, it will appear in the **Open Orders**.

<figure><img src="/files/dfksl5mnAaoaLDc1QZMq" alt=""><figcaption></figcaption></figure>

In this example, I have an open order to buy $5 Yes shares at 50c. So far, none of the shares from my order have been filled.

To cancel the order, simply click the **× icon on the right** to cancel the order.

If you’d like to view only your active orders, navigate to the ‘Open Orders’ tab.

If you have open orders in multiple markets, it’s easier to manage and monitor them on the “My Portfolio” page.

<figure><img src="/files/lfu5zbWeoZwQv7E0CnnZ" alt=""><figcaption></figcaption></figure>


# Profit & Loss

## What is Profit & Loss (P\&L)?

P\&L (Profit and Loss) shows the potential gains or losses from your open positions. It's a fluctuating figure that adjusts as the current market price of the asset moves relative to your average purchase price. Essentially, it indicates whether your positions are trending toward a profit or a loss at any given moment.

A user's position on opinion.trade represents the total of all completed trades (whether increasing, reducing, or closing) over time. All trading lots, grouped by market, are consolidated into one position to determine the P\&L. The P\&L calculation in the table is based on the difference between the entry price of the position and the current market price (or oracle price, depending on the toggle), multiplied by the position's size. The P\&L can be viewed either in dollars or as a percentage.

<figure><img src="/files/sa0Owlfcqj1nMpBQOcej" alt=""><figcaption></figcaption></figure>

**Unrealised P\&L (uP\&L): r**epresents the potential gains or losses on open positions, based on the current market price compared to your entry price. This value fluctuates as the market changes.

The unrealized profit or loss of your current position, calculated as (Current Value − Total Buy-in Cost), or (Last Price − Avg. Buy-in Price) \* Quantity Held.

**Net worth** on this interface equals the market value of your current holdings plus your available balance

<figure><img src="/files/QbxIlgRAXh47jZdu0ClX" alt=""><figcaption></figcaption></figure>


# Fees

At Opinion, our goal is to build prediction markets that are liquid, sustainable, and built for accurate price discovery.

Trading fees are one of the tools that help us get there — not just a cost, but a mechanism that shapes healthy market behavior.

By aligning incentives, our model:

* **Rewards traders with conviction**, especially those who take positions with clarity.
* **Reduces noise**, keeping markets reliable.
* **Offers meaningful, growing discounts** to active participants over time.

In other words, fees on Opinion are designed to make your trading **smarter, fairer, and more rewarding — while helping the entire market stay healthy and efficient.**

> For a full side-by-side comparison of prediction-market fees across platforms, see our fee comparison guide.（<https://blog.opinion.trade/learn/polymarket-kalshi-opinion-fees）>

## Summary

Our schedule is designed to (i) reward liquidity provision, (ii) reflect uncertainty at the mid-price, (iii) allow targeted, programmatic discounts, and (iv) avoid small-ticket edge cases via minimums.

* Charging side: **Fees apply to the taker only**; **makers are not charged**. This holds regardless of buy/sell direction.
* **Fees range from 0% to 1%** — fees increase as market probability approaches 50%
* Minimums. **$5 minimum order** and **$0.25 minimum fee**. We implement a dynamic fallback so extremely small curve fees revert to $0.25.
* Higher fees apply during periods of maximum uncertainty, lower fees when outcomes are more determined
* Discounts stack together for up to **100% fee reduction.** ($0.25 minimum fee still applies)&#x20;
* All fees are denominated in the settlement asset (such as $USDT)
* **Referral.** Invitees receive an additional trading-fee discount; referrers receive incentives equivalent to 5% of the invitees paid fee (see [Referral Program](/incentive-plans/referral-program) for details)
* Gas policy: **We cover on-chain match/settle gas for trades**; low-frequency actions have small user-paid gas.

**Key Point: Only Takers Pay Fees**

Fees are charged only when your order executes immediately. What this means for you:

* **Limit orders (makers) usually pay zero fees**. Even when you sign a transaction with fee terms, you pay nothing if your orders rest on the order book
* Market orders (takers) are the only ones that pay fees when they execute instantly
* So if you provide liquidity to the market, you can trade completely **fee-free**

*Note：A maker adds liquidity by placing orders that wait in the book. A taker removes liquidity by executing against existing orders.*

## Technical Details

### How Our Fee Curve Operates

Our fee system incorporates market dynamics to reward strategic timing decisions.

The fee curve adjusts based on market probability at the time of trade execution.

The fee structure ensures pricing accurately reflects actual market conditions and operational costs. The mechanism operates as follows:

**All Trade Types**

* Trades at extreme probabilities (near 0% or 100%): Lower fees due to reduced uncertainty
* Trades at equilibrium (50% probability): Higher fees reflecting maximum uncertainty and peak activity

**Symmetric price curve.** The <mark style="color:$success;">`price × (1 − price)`</mark> shape raises fees near 0.5 (highest uncertainty/matching load) and tapers toward 0/1.

* The normalized shape below illustrates how <mark style="color:$success;">`price × (1 − price)`</mark> peaks at 0.5 and falls toward the edges.

<figure><img src="/files/P2SSt2fNnfVfEUmH3hnx" alt=""><figcaption></figcaption></figure>

**Topic-level base coefficient.** Each market (“topic”) sets a <mark style="color:$success;">`topic_rate`</mark> coefficient; consequently, the base fee rate at <mark style="color:$success;">`p=0.5`</mark> is <mark style="color:$success;">`topic_rate × 0.25`</mark>.

* The chart below shows an illustrative taker rate across prices with a sample <mark style="color:$success;">`topic_rate`</mark> and with stacked discounts applied (multiplicatively).

<figure><img src="/files/EHMmU2DGEMlCXlIhnEQN" alt=""><figcaption></figcaption></figure>

(Charts are illustrative; program values per market may differ.)

*Note:*

* *Notional means trade price × quantity in the settlement currency.*
* *Fees are assessed per matched fill; orders filled at multiple prices accrue fees linearly across fills.*

### Fee Formula

<mark style="color:$success;">`Effective fee rate = topic_rate × price × (1 − price) × (1 − user_discount) × (1 − transaction_discount) × (1 − user_referral_discount)`</mark>

<mark style="color:$success;">`Fee charged = max(notional × effective fee rate, min fee $0.25)`</mark>

**Discount Structure**

Multiple discount types multiply together:&#x20;

<mark style="color:$success;">`topic_rate`</mark>: selected markets may offer reduced or zero trading fees

<mark style="color:$success;">`user_discount`</mark>: see [VIP Program](/incentive-plans/vip-program) for details

<mark style="color:$success;">`transaction_discount`</mark>: limited-time promotional discounts during special campaigns&#x20;

<mark style="color:$success;">`user_referral_discount`</mark>: see [Referral Program](/incentive-plans/referral-program) for details

*Note:*&#x20;

* *<mark style="color:$success;">`topic_rate`</mark> is the curve coefficient; thus the base (pre-discount) fee rate at p=0.5 equals <mark style="color:$success;">`topic_rate × 0.25`</mark>*
* ***Notional** means <mark style="color:$success;">`trade price × quantity`</mark> in the settlement assets*
* *Fees are assessed **per matched fill**; orders filled at multiple prices accrue fees linearly across fills.*

### Gas Policy & Operational Safeguards

To make frequent trading smooth, we cover the on-chain gas for trade matches (buy/sell settlement). Other low-frequency actions have small, user-paid gas. We also set thresholds to avoid micro-burn from very small tickets: $5 minimum order, $0.25 minimum fee, $5 minimum withdrawal.  (Subject to change during abnormal network conditions; the UI will reflect the current schedule.)

## FAQ

Q: Why the $0.25 minimum fee?\
A: Two reasons: (1) it protects the platform when gas spikes, and (2) it avoids pathological edge cases when the price curve and discounts would otherwise yield a near-zero fee. We implement it as a dynamic floor rate: if curve\_fee < $0.25, fee becomes $0.25.&#x20;

Q: Are maker fees always zero?\
A: Our policy is taker pays, maker free to incentivize liquidity provision.

Q: Why is the fee higher near price 0.5?\
A: That’s where uncertainty—and matching load—is highest. The symmetric price × (1 − price) curve raises fees at the center and lowers them toward 0 or 1.&#x20;

Q: Do discounts stack?\
A: Yes—multiplicatively: user level × promo × referral. The $0.25 minimum still applies, so stacking can’t “pierce the floor.”

Q: Can I change my referral code later?\
A: No.

## **Ready to Trade with your Opinion?**

Opinion rewards the traders who think strategically. Free maker trading, stacking discounts up to 100%, and fee curves that favor conviction over speculation.

**Your next strategic trade awaits.**

➡️ Connect at [opinion.trade](https://opinion.trade)

*Trade Tomorrow Now*

<br>


# Data Analytics

Data Analytics

Dune dashboard is coming soon.


# Resolution

Resolution

The method of resolving each market is specified on its respective market page. At present, most markets are resolved through **Opinion AI**, which serves as the primary oracle mechanism.


# Dispute

Safeguard fair outcomes and get rewarded - empowered by $OPN

Opinion is built on the principle that **market outcomes must be accurate**. The dispute system gives every participant the power to challenge a resolution they believe is wrong — creating a transparent, community-driven layer of accountability.

By allowing anyone to dispute, Opinion ensures:

* **Accuracy** — incorrect resolutions can be corrected before payouts are finalized.
* **Fairness** — every trader has a voice.
* **Accountability** — disputors who are right are rewarded; frivolous disputes are discouraged through staking.

### Summary

* **Any user can dispute** a market resolution during the dispute window by staking $OPN tokens.
* **Two dispute categories**: *Wrong Result* (the outcome is incorrect) or *Too Early* (the event hasn't happened yet).
* You may also provide a **detailed written reason** — including reference URLs or evidence — for review.
* **One dispute per resolution.** The first user to file a dispute claims the slot. Once a dispute is filed, no further disputes can be submitted for the same resolution.
* **If your dispute is accepted**: you receive your stake back **plus** an equal reward in $OPN.
* **If your dispute is rejected**: your stake is forfeited.
* The required stake amount is shown on each market's dispute page.
* **Gas policy**: you pay your own gas for filing a dispute and claiming your reward.

### How It Works

#### Step 1 — A Market Resolves

When the proposed outcome is published and a **dispute window** opens - typically 2-hour long except some asset-price related markets. During this window, payouts are not yet finalized.

<figure><img src="/files/iXk3mLHum65Fe6myooZ7" alt=""><figcaption></figcaption></figure>

#### Step 2 — File a Dispute

If you believe the resolution is incorrect, you can file a dispute directly from the respective market page:

1. Select your dispute reason: **Incorrect outcome** or **Too early to resolve**.
2. Optionally, provide a detailed explanation with supporting evidence (e.g., links to news sources).
3. Stake the required amount of **$OPN** tokens. You will need to connect your wallet and have the required amount of $OPN ready. The exact amount is displayed on the market page.
4. Confirm the on-chain transaction.

<figure><img src="/files/L8GDH1km3X5x7CBhlmhg" alt=""><figcaption></figcaption></figure>

#### Step 3 — Review

Once a dispute is filed, a review process will take place.

<figure><img src="/files/YIsl54WEQvYTk7wk8lSY" alt=""><figcaption></figcaption></figure>

#### Step 4 — Outcome

| Scenario              | What Happens                                                            | Your Stake              |
| --------------------- | ----------------------------------------------------------------------- | ----------------------- |
| **No dispute filed**  | Resolution auto-finalizes after the dispute window expires              | N/A                     |
| **Dispute in review** | A dispute has been filed and is currently under review — result pending | Held                    |
| **Dispute rejected**  | The original result is reaffirmed — it stands                           | Forfeited               |
| **Dispute accepted**  | Original resolution is overturned — new result is applied               | Returned + equal reward |
| **Time out**          | Dispute succeeds automatically; market can be re-resolved later         | Returned + equal reward |

#### Step 5 — Claim Your Reward

If your dispute succeeded (accepted or timed out), you can claim your stake and reward from the market page. This is an on-chain transaction — you pay gas to claim.

<figure><img src="/files/DhTQErd5UHWJWxYsX3ZQ" alt=""><figcaption></figcaption></figure>

### Timelines

Each market has its own dispute window durations. These are displayed on the market page when a resolution is proposed.

* **Dispute window** — the period after a resolution is published during which you can file a dispute.
* **Review window** — the period after a dispute is filed for a review process. The dispute automatically succeeds if the review process times out.

### Stake & Rewards

**Stake token**: $OPN

### Stake Requirement

The required stake amount is fixed at the time the first resolution is proposed for a market, and is calculated as:

**max(1% of market open interest, 1,000 $OPN)**

The $OPN amount is determined using the Binance OPN/USDT exchange rate at the time the first resolution is proposed.

**Example (assuming OPN/USDT = 1):**

| Market Open Interest | 1% of OI   | Required Stake                       |
| -------------------- | ---------- | ------------------------------------ |
| 200,000 USDT         | 2,000 $OPN | 2,000 $OPN                           |
| 50,000 USDT          | 500 $OPN   | 1,000 $OPN *(minimum floor applies)* |

The exact stake amount is always displayed on the market's dispute page before you commit.

<figure><img src="/files/YcJtH41prPa1ACjTgOHd" alt=""><figcaption></figcaption></figure>

*Note: Opinion reserves the right to adjust the stake percentage based on market category.*

**Reward structure**:

* **Dispute accepted or timed out**: you receive your full stake back, plus an equal reward — **2x your original stake** in total.
* **Dispute rejected**: your entire stake is forfeited.

### FAQ

**Q: How do I know if I can dispute a market?** A: When a market has an active dispute window, the market page will show the dispute option along with the remaining time and required stake amount.

**Q: What $OPN tokens do I need?** A: You need $OPN tokens in your connected wallet (EOA). The exact amount required is shown on the market's dispute page.

**Q: What happens if no one disputes?** A: The resolution is automatically finalized after the dispute window expires, and payouts proceed as proposed.

**Q: Can I dispute more than once on the same market?** A: No. Each resolution allows only one dispute. Once a dispute is filed, no additional disputes can be made.

**Q: What's the difference between "Incorrect outcome" and "Too early to resolve"?** A: Choose **Incorrect outcome** if you believe the outcome is incorrect. Choose **Too early to resolve** if the event the market is based on hasn't actually occurred yet — for example, a match that was postponed.

**Q: Do I pay gas fees?** A: Yes. Dispute actions — filing a dispute and claiming your reward — require you to pay gas on the BNB Smart Chain.

### Think a Resolution is Wrong?

Opinion's dispute system ensures no outcome goes unchecked. If you spot an error, you have the power — and the incentive — to correct it.

**Protect the integrity of your markets.**

➡️ Trade at [opinion.trade](https://opinion.trade)

*Trade Tomorrow Now*


# Maker Rebates Program

## Overview

Opinion's Maker Rebates Program rewards market makers who provide liquidity by returning a portion of taker fees to the maker side of each trade. The program is **automatic** — no application or opt-in required. Every maker on every market is eligible.

{% hint style="info" %}
**Maker rebate = Taker fee × 50% (subject to change)**

For every filled maker order, 50% of the taker fee generated by that trade is returned to the maker as a rebate. The rebate ratio is subject to change.
{% endhint %}

## How It Works

When a taker fills a maker's limit order, the taker pays a trading fee. A fixed percentage of that fee is returned to the maker as a rebate. This creates a direct incentive for placing limit orders and providing liquidity across all markets.

**Key points:**

* **All markets qualify** — Every market on Opinion.Trade participates in the Maker Rebates Program.
* **Automatic enrollment** — If you place limit orders that get filled, you earn rebates. No sign-up needed.
* **Per-fill calculation** — Each individual on-chain trade generates its own rebate based on that specific fill's parameters.
* **Same-token payout** — Rebates are paid in the same collateral token used in the trade (e.g., USDT).

## Rebate Calculation

### Formula

$$
\Large \text{maker\_rebate} = \text{taker\_fee} \times k
$$

Where:

* **taker\_fee** — The trading fee paid by the taker on this fill. See [Fees](/trade-on-opinion.trade/fees) for how taker fees are calculated.
* **k** — The rebate ratio, currently **50%**.

Since taker fees follow the fee curve `taker_fee = volume × price × (1 - price) × fee_rate`, the full expansion is:

{% hint style="warning" %}
**Note on fee rate:** The fee rate (r) shown below is the **base rate parameter**, not the actual percentage you pay. The actual effective fee is reduced by the price curve factor `p × (1 − p)`, which maxes out at 0.25 when p = $0.5. For example, with a 4% base rate, the **maximum effective fee is only 1%** (at p = $0.5) of trade volume, and decreases toward 0% as the price approaches $0 or $1. See [Fees](/trade-on-opinion.trade/fees) for details.
{% endhint %}

$$
\Large \text{maker\_rebate} = V \times p \times (1 - p) \times r \times k
$$

Where:

<table><thead><tr><th width="107">Symbol</th><th width="492">Description</th><th>Example</th></tr></thead><tbody><tr><td>V</td><td>Taker's collateral volume</td><td>$1,000</td></tr><tr><td>p</td><td>Fill price (probability)</td><td>0.60</td></tr><tr><td>r</td><td>Base taker fee rate (per market, before curve adjustment)</td><td>4%</td></tr><tr><td>k</td><td>Rebate ratio</td><td>50%</td></tr></tbody></table>

### Worked Example

{% hint style="info" %}
**The current rebate-to-fee ratio is constant at 50%**, regardless of price, volume, or market. This makes rebate earnings predictable and easy to model. The rebate ratio is subject to change.
{% endhint %}

**Example 1: Trade at p = 0.60**

A taker buys $1,000 worth of "Yes" shares at a price of $0.60:

| Step                | Calculation               | Result    |
| ------------------- | ------------------------- | --------- |
| Taker volume        |                           | $1,000    |
| Fill price (p)      |                           | 0.60      |
| Fee curve factor    | p × (1 − p) = 0.60 × 0.40 | 0.24      |
| Base taker fee rate |                           | 4%        |
| **Taker fee**       | $1,000 × 0.24 × 0.04      | **$9.60** |
| Rebate ratio (k)    |                           | 50%       |
| **Maker rebate**    | $9.60 × 0.50              | **$4.80** |

The maker who provided liquidity on this trade earns **$4.80** in rebates.

**Example 2: Trade at p = 0.50 (maximum fee)**

At p = 0.50, the fee curve peaks — this is where makers earn the highest rebate per dollar of volume:

| Step             | Calculation           | Result     |
| ---------------- | --------------------- | ---------- |
| Taker volume     |                       | $10,000    |
| Fill price (p)   |                       | 0.50       |
| Fee curve factor | 0.50 × 0.50           | 0.25       |
| Taker fee        | $10,000 × 0.25 × 0.04 | $100.00    |
| **Maker rebate** | $100.00 × 0.50        | **$50.00** |

**Example 3: Trade at p = 0.95 (near certainty)**

At extreme prices, the fee curve compresses — fees and rebates are lower:

| Step             | Calculation             | Result    |
| ---------------- | ----------------------- | --------- |
| Taker volume     |                         | $10,000   |
| Fill price (p)   |                         | 0.95      |
| Fee curve factor | 0.95 × 0.05             | 0.0475    |
| Taker fee        | $10,000 × 0.0475 × 0.04 | $19.00    |
| **Maker rebate** | $19.00 × 0.50           | **$9.50** |

**Example 4: Small trade triggering minimum fee ($0.25)**

When the calculated taker fee falls below the **$0.25 minimum fee floor**, the taker is charged the minimum fee. However, the maker rebate is based on the **curve-derived fee**, not the minimum fee charged:

{% hint style="warning" %}
**Minimum fee and multiple fills:** A single taker order may be matched into multiple on-chain trades. The minimum fee is only charged on the **first** on-chain trade of that order. Maker rebates, however, are calculated **independently for each on-chain trade** based on that trade's actual volume and curve-derived fee — regardless of whether the minimum fee was applied. This means every maker involved in filling the order receives a rebate that accurately reflects their individual fill.
{% endhint %}

| Step                  | Calculation         | Result      |
| --------------------- | ------------------- | ----------- |
| Taker volume          |                     | $50         |
| Fill price (p)        |                     | 0.95        |
| Fee curve factor      | 0.95 × 0.05         | 0.0475      |
| **Curve-derived fee** | $50 × 0.0475 × 0.04 | **$0.095**  |
| **Maker rebate**      | $0.095 × 0.50       | **$0.0475** |

In this case, although the taker is charged the $0.25 minimum fee, the maker rebate is calculated on the **curve-derived fee** ($0.095), not the minimum fee charged.

**Example 5: Trade with taker referral discount (5%)**

When a taker has a referral discount, the taker pays a reduced fee. However, the maker rebate is always calculated on the **full curve-derived fee** (before any discounts), not the discounted fee the taker actually pays:

| Step                     | Calculation                     | Result    |
| ------------------------ | ------------------------------- | --------- |
| Taker volume             |                                 | $1,000    |
| Fill price (p)           |                                 | 0.60      |
| Fee curve factor         | 0.60 × 0.40                     | 0.24      |
| **Curve-derived fee**    | $1,000 × 0.24 × 0.04            | **$9.60** |
| Taker referral discount  |                                 | 5%        |
| Actual taker fee charged | $9.60 × (1 − 5%) = $9.60 × 0.95 | $9.12     |
| **Maker rebate**         | **$9.60** × 0.50                | **$4.80** |

{% hint style="info" %}
The maker rebate ($4.80) is the same as a trade without any referral discount — it is always based on the \*\*curve-derived fee\*\* ($9.60), not the discounted fee ($9.12) the taker actually pays. Referral discounts benefit the taker but do not reduce the maker's rebate.
{% endhint %}

### Multiple Fills from One Order

A single maker limit order can be filled by multiple takers in separate on-chain transactions. **Each fill is calculated independently:**

For example, a maker places a $5,000 limit order. It gets filled in three separate trades:

| Fill      | Taker Volume | Fill Price | Taker Fee  | Maker Rebate    |
| --------- | ------------ | ---------- | ---------- | --------------- |
| #1        | $2,000       | 0.55       | $19.80     | $9.90           |
| #2        | $1,500       | 0.58       | $14.62     | $7.31           |
| #3        | $1,500       | 0.60       | $14.40     | $7.20           |
| **Total** | **$5,000**   |            | **$48.82** | **$**&#x32;4.41 |

Each fill uses its own actual execution price and volume, ensuring rebates accurately reflect real market conditions at the time of each trade.

## Payout

* **Schedule**: Rebates are calculated and distributed daily.
* **Token**: Rebates are paid in the same collateral token as the original trade (e.g., USDT).
* **Minimum payout**: A minimum payout threshold of **$0.01** per user per day applies. If your total daily rebate is below $0.01, it will not be distributed for that day.
* **Destination**: Rebates are sent directly to your trading wallet.

## FAQ

<details>

<summary>Do I need to apply for the Maker Rebates Program?</summary>

No. The program is fully automatic. Any maker order that gets filled on any market will earn rebates.

</details>

<details>

<summary>Which markets are eligible?</summary>

All markets on Opinion.Trade participate in the Maker Rebates Program.

</details>

<details>

<summary>When do I receive my rebates?</summary>

Rebates are calculated and distributed daily.

</details>

<details>

<summary>What token are rebates paid in?</summary>

Rebates are paid in the same collateral token used in the trade. If you traded using USDT, your rebate is paid in USDT.

</details>

<details>

<summary>How can I maximize my rebate earnings?</summary>

* **Place more limit orders** — Only maker (limit) orders earn rebates, not market orders.
* **Provide liquidity at mid-range prices** — The fee curve `p × (1 − p)` peaks at p = 0.50, so trades near 50% generate the highest fees and rebates.
* **Trade on more markets** — All markets qualify, so diversifying your liquidity provision increases total rebate volume.

</details>

<details>

<summary>Is there a minimum trade size to earn rebates?</summary>

There is no minimum trade size for rebate eligibility. All filled maker orders earn rebates. However, a minimum daily payout threshold of **$0.01** applies — if your total daily rebate is below this amount, it will not be distributed for that day.

</details>

<details>

<summary>Can the rebate ratio change?</summary>

The rebate ratio (currently 50%) and other program parameters may be adjusted. Any changes will be announced in advance.

</details>


# Point System (PTS)

## Our Goal

With the OPINION Point System (PTS), your contributions shape the market and your future incentives:

* Develop your profitable prediction strategies
* Get incentives for quality insights
* Help create the gold standard for prediction data

Your points reflect how you and OPINION succeed together — by providing liquidity, sharpening price discovery, and making markets more efficient. As the platform grows, your contributions will directly influence your share of the future incentives.

Early movers capture the highest multipliers—don’t wait until points get harder to earn.

## How PTS Works

Connection to the Incentives\
Points serve as the primary metric for incentives. While there's no fixed conversion rate, your point possibly determines your proportional share of the incentive distribution directly.

## Program Structure

### Weekly Allocation

* A fixed pool of points distributed weekly—earlier participants typically earn more points per activity as the user base grows over time
* Leaderboards updated every week
* Designed to reward steady, consistent participation

### Scoring Method

* Internal metrics evaluate activity quality
* Public rankings and allocations provide transparency
* Ensures fairness while reducing system gaming

## How to Qualify & Earn

#### Minimum Requirement

* $200 in total trading volume to join the program&#x20;

#### Three Ways to Earn Points on Mainnet

* Limit Orders (Liquidity Provision)
  * The closer your order is to market odds, the larger the size, and the longer it stays open → the more points you earn\
    For example, if the market odds for “Fed cuts rates in December” are 0.20, a limit buy at 0.15 will earn fewer points than one at 0.19, because it contributes less to active price discovery.
  * Orders amount higher than $10 USDT are classified as liquidity provision, rewarding consistent depth
  * Providing liquidity with limit orders typically earns more points than market orders

<figure><img src="/files/gETpTM34HHg3p6T0lSC9" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ksLkB3vDF8aIPixaarmY" alt=""><figcaption></figcaption></figure>

* Trading Activity
  * Larger trade sizes earn proportionally more points
  * Designed to reward conviction and information contribution
* Position Holding&#x20;
  * Maintain conditional token positions over time to earn ongoing rewards
  * Points scale with the number of tokens held and the duration of holding
  * Different markets might have different multiplier

#### Higher Impact Activities

* Early Maker Liquidity
  * Establish strong, reliable order books
  * Higher multipliers, converging with taker rates over time
* Designated Markets
  * Specific markets will be designated by OPINION and given higher reward weights than regular markets
* Quality Trading
  * Activity that enhances market accuracy and price discovery. For example, strategic limit orders based on your research express informed conviction and help establish fair value

## Getting Started

To make the most of PTS:

1. Reach $200 in total trading volume per week
2. Provide liquidity, especially in early or new markets
3. Trade with conviction when you see opportunities
4. Stay consistent—learning and earning compound over time

👉 Trade, provide liquidity, stay consistent—and grow with OPINION.

👉 Begin your journey at opinion.trade

<br>

\ <br>

<br>


# Referral Program

Program

### Opinion Referral Program

#### 1. What is the Referral System

The **Referral System** is an incentive mechanism introduced by **Opinion** to reward community growth and participation.\
Any user can generate a unique referral code and invite link to onboard new users to trade on [**opinion.trade**](https://opinion.trade/), earning platform rewards in the process.

The referral program offers two types of incentives:

* **Fee Discounts:** Invited users enjoy trading fee discounts.
* **Potential Incentives Boost:** Both parties may receive additional points or other bonuses (subject to future announcements).

#### 2. How to Get Your Referral Code

1. Visit [**opinion.trade**](https://opinion.trade) and connect your wallet.
2. Click the **“Referral Code”** button on the top right to access the referral dashboard.
3. Once your **total trading volume reaches $1,000**, you can generate your unique referral code with one click.
4. Copy your referral code or link and share it with friends, communities, or social media.

#### 3. How to Bind a Referral Code

1. Click the **“Referral Code”** button at the top right of the homepage.
2. Enter a valid referral code in the input box.
3. Click **“Apply Code”** to complete the binding.

After binding, the invited user immediately enjoys the corresponding **fee discount** and **potential incentives boost**.

#### 4. Referral Rewards Structure

| **Role**     | **Reward**                                                    | **Description**                           |
| ------------ | ------------------------------------------------------------- | ----------------------------------------- |
| **Referrer** | Potential bonus points                                        | Potential bonus points                    |
| **Referee**  | Up to **10%** trading fee discount                            | Automatically applied upon code binding   |
| **Both**     | Potential incentives boost (additional points and/ or others) | Details to be announced in future updates |

**Note:** Reward rates may vary depending on user level or ongoing campaigns.

#### 5. Example

* User **A** reaches a total trading volume of **$1,000** on Opinion and receives referral code `xxxxxx`.
* User **A** shares the code with friend **B**.
* User **B** successfully binds A’s code and completes trades generating **$100 USDT** in total fees.

The system automatically settles as follows:

* **User B** receives up to **10% trading fee discount**.
* **Both users** may receive additional incentives rewards depending on ongoing platform activities.

#### 6. Additional Notes

* All discounts are **settled in real time**.
* There is **no upper limit** on total referral rewards — invite more users to earn more.
* Points campaigns and multiplier bonuses will be updated periodically.\
  Please refer to official announcements for the latest details.


# VIP Program

VIP Program

Coming Soon


# Opinion Builders Program

Welcome to the Opinion Ecosystem. [**Apply for Builder Key**](https://forms.gle/tqL84A5mMXjZ1xT86) to join us and start building today!

## What is an Opinion Builder?

A Builder is a researcher, router, or developer who creates tools, interfaces, and applications using the Opinion Developer Kit (Opinion Open API / Opinion CLOB SDK) to contribute to the Opinion ecosystem.

Router: In the context of Opinion, a “router” refers to a person, group, or organization that routes orders from their customers to Opinion.

Researchers: A “researcher” refers to a person, group, or organization that creates analytics-related applications/tools.

## Why should you join the Opinion Builders Program?

The Builders Program currently provides several key benefits:

1. Priority API Access
   1. API key with elevated rate limit upon request
2. Technical Resources
   1. Dedicated support from Opinion engineering team
   2. Opinion Open API for :&#x20;
      1. Real-time market data
      2. Real-time trade data
      3. Real-time position data
   3. Opinion CLOB SDK for:
      1. All features in Opinion Open API&#x20;
      2. Trade, position and balance management
      3. User onboarding features
      4. Gasless smart contract interaction
3. Rewards & Ecosystem Support
   1. Grants and rewards tied to real usage, liquidity, and impact
   2. Priority access to Opinion’s stack (API keys, infra access, and early feature rollouts)
   3. Technical support via direct collaboration with Opinion’s engineering & research team
   4. Marketing exposure across the Opinion ecosystem and partner networks
   5. Strategic and ecosystem support through investor and partner networks, media opportunitiest

## How to get started?

1. Apply for Access
   1. Submit your project details and use case via the [**application form**](https://forms.gle/9oBLs9wns6sJVm87A)
   2. Approved builders will be contacted with next steps, including the API key and Discord access.
   3. Request elevated limits if needed
2. Read the Documentation
   1. Find full technical guides and API references at  [docs.opinion.trade](https://docs.opinion.trade/developer-guide/)
3. Join the Community
   1. Join [Discord](https://discord.com/channels/1254615232496533545/1447424765739405493) for developer discussions and support
   2. Follow [@opinionlabsxyz](https://x.com/opinionlabsxyz) on X for updates


# Opinion Open API


# Overview

## Opinion OpenAPI

Welcome to the official documentation for the Opinion OpenAPI - a RESTful API for accessing OPINION Prediction Markets

> 📊 Public Data API: Public data endpoints do not require an API key. Authenticated endpoints require an API key. For trading operations (placing orders, managing positions), please use the Opinion CLOB SDK.
>
> To get authenticated access, create your own API key instantly with a wallet signature — see [Authentication](https://docs.opinion.trade/developer-guide/opinion-open-api/authentication). Builders can also request keys via this [short application form](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII).
>
> *API Key can be used for Opinion OpenAPI, Opinion Websocket, and Opinion CLOB SDK*

### What is Opinion OpenAPI?

The Opinion OpenAPI provides a simple HTTP interface for accessing prediction market data from Opinion Labs' infrastructure. It enables developers to:

* **Query market data** - Access real-time market information, metadata, and trading volumes
* **Monitor prices** - Get latest trade prices and historical price data
* **Analyze orderbooks** - Retrieve order book depth for any market token
* **Discover quote tokens** - List available trading currencies and their configurations

### Key Features

#### Simple Integration

* **RESTful** - Standard HTTP/JSON API
* **OpenAPI 3.0** - Full specification with Swagger/Redoc support
* **Language Agnostic** - Use with any programming language
* **No Dependencies** - Just HTTP requests

#### &#x20;Performance Optimized

* **Low Latency** - Optimized for real-time data access
* **Rate Limited** - 15 requests/second per API key
* **Paginated** - Efficient handling of large datasets

#### &#x20;Secure Access

* **API Key Authentication** - Simple header-based auth
* **HTTPS Only** - All traffic encrypted
* **Production Ready** - Battle-tested infrastructure

#### &#x20;Blockchain Support

| Chain             | Chain ID | Status |
| ----------------- | -------- | ------ |
| BNB Chain Mainnet | 56       | ✅ Live |

### Use Cases

#### Market Analytics Dashboard

Aggregate and display market data for research or monitoring applications.

```bash
# Get all active markets sorted by 24h volume
curl -X GET "https://openapi.opinion.trade/openapi/market?status=activated&sortBy=5&limit=20"
```

```json
{
  "code": 0,
  "msg": "success",
  "result": {
    "total": 150,
    "list": [
      {
        "marketId": 123,
        "marketTitle": "Will BTC reach $100k by end of 2025?",
        "status": 2,
        "statusEnum": "Activated",
        "yesTokenId": "0x1234...5678",
        "noTokenId": "0x8765...4321",
        "volume": "1500000.00",
        "volume24h": "125000.00"
      }
    ]
  }
}
```

#### Price Monitoring Bot

Track real-time prices for specific outcome tokens.

```bash
# Get latest price for a token
curl -X GET "https://openapi.opinion.trade/openapi/token/latest-price?token_id=0x1234...5678"
```

```json
{
  "code": 0,
  "msg": "success", 
  "result": {
    "tokenId": "0x1234...5678",
    "price": "0.65",
    "side": "BUY",
    "size": "1000.00",
    "timestamp": 1733312400000
  }
}
```

#### Orderbook Analysis

Analyze market depth for trading insights.

```bash
# Get orderbook for a token
curl -X GET "https://openapi.opinion.trade/openapi/token/orderbook?token_id=0x1234...5678"
```

```json
{
  "code": 0,
  "msg": "success",
  "result": {
    "market": "0xabc...def",
    "tokenId": "0x1234...5678",
    "timestamp": 1733312400000,
    "bids": [
      {"price": "0.64", "size": "5000.00"},
      {"price": "0.63", "size": "12000.00"}
    ],
    "asks": [
      {"price": "0.66", "size": "3000.00"},
      {"price": "0.67", "size": "8000.00"}
    ]
  }
}
```

#### Historical Price Charts

Build price charts with historical data.

```bash
# Get daily price history for the last 30 days
curl -X GET "https://openapi.opinion.trade/openapi/token/price-history?token_id=0x1234...5678&interval=1d"
```

```json
{
  "code": 0,
  "msg": "success",
  "result": {
    "history": [
      {"t": 1733184000, "p": "0.58"},
      {"t": 1733270400, "p": "0.62"},
      {"t": 1733356800, "p": "0.65"}
    ]
  }
}
```

### API Endpoints Overview

| Endpoint               | Method | Description                    |
| ---------------------- | ------ | ------------------------------ |
| `/market`              | GET    | List all markets with filters  |
| `/market/{marketId}`   | GET    | Get market details by ID       |
| `/token/latest-price`  | GET    | Get latest trade price         |
| `/token/orderbook`     | GET    | Get order book depth           |
| `/token/price-history` | GET    | Get historical prices          |
| `/quoteToken`          | GET    | List quote tokens (currencies) |

### Authentication

Public data endpoints listed on this page do not require an API key. All other OpenAPI endpoints require an apikey header. An invalid apikey returns 401 Unauthorized and does not fall back to anonymous access.

```bash
curl -X GET "https://openapi.opinion.trade/openapi/market"
```

> 📧 **Get an API Key**: Please kindly fill out this [short application form ](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII).

### Rate Limiting

| Limit                  | Value                               |
| ---------------------- | ----------------------------------- |
| Public requests        | 5 requests/second per IP            |
| Authenticated requests | 15 requests/second per API consumer |

If you exceed rate limits, you'll receive a 429 Too Many Requests response. Respect the Retry-After response header when present.

### Response Format

All responses follow a consistent JSON structure:

```json
{
  "code": 0,         // 0 = success, non-zero = error
  "msg": "success",  // Human-readable message
  "result": { ... }  // Response data (varies by endpoint)
}
```

#### Error Codes

| Code | Description                               |
| ---- | ----------------------------------------- |
| 0    | Success                                   |
| 400  | Bad Request - Invalid parameters          |
| 401  | Unauthorized - Invalid or missing API key |
| 404  | Not Found - Resource doesn't exist        |
| 429  | Too Many Requests - Rate limit exceeded   |
| 500  | Internal Server Error                     |

### Quick Links

| Resource   | Link                                                                 |
| ---------- | -------------------------------------------------------------------- |
| Python SDK | [Opinion CLOB SDK](https://github.com/opinion-labs/opinion-clob-sdk) |

### SDK vs OpenAPI

| Feature             | OpenAPI (This API) | CLOB SDK |
| ------------------- | ------------------ | -------- |
| Market Data         | ✅                  | ✅        |
| Orderbook           | ✅                  | ✅        |
| Price History       | ✅                  | ✅        |
| Place Orders        | ❌                  | ✅        |
| Cancel Orders       | ❌                  | ✅        |
| Manage Positions    | ❌                  | ✅        |
| On-chain Operations | ❌                  | ✅        |
| Language            | Any (HTTP)         | Python   |

**Recommendation**:

* Use **OPINION** **OpenAPI** for read-only data access, dashboards, and analytics
* Use **OPINION** **CLOB SDK** for trading, order management, and blockchain interactions

***

Ready to get started? Check the OpenAPI Specification for detailed endpoint documentation.


# Authentication

Public data endpoints do not require an API key. Authenticated endpoints require providing your API key in the `apikey` HTTP request header. The same key works for Opinion OpenAPI, Opinion Websocket, and Opinion CLOB SDK.

#### Getting an API key

You can create your own API key instantly by signing an EIP-712 message with your wallet — no application form or approval needed.

Prerequisites:

* Your wallet must be a registered Opinion account with a trading wallet enabled — connect the wallet on [opinion.trade](https://opinion.trade) and complete onboarding first.
* Each wallet holds one active API key at a time.

The three Auth endpoints (`POST` / `GET` / `DELETE` `/auth/api-key`) are authenticated by wallet signature only — no API key required. Every request carries three headers: `OPINION_ADDRESS` (the signing wallet address), `OPINION_SIGNATURE` (EIP-712 signature) and `OPINION_TIMESTAMP` (Unix timestamp in seconds, must match the signed `timestamp` field).

#### Signing the request

Sign the following EIP-712 typed data with your wallet's private key:

```json
{
  "types": {
    "EIP712Domain": [
      { "name": "name", "type": "string" },
      { "name": "version", "type": "string" },
      { "name": "chainId", "type": "uint256" }
    ],
    "OpinionApiKeyAuth": [
      { "name": "walletAddress", "type": "address" },
      { "name": "action", "type": "string" },
      { "name": "timestamp", "type": "string" }
    ]
  },
  "primaryType": "OpinionApiKeyAuth",
  "domain": {
    "name": "Opinion OpenAPI",
    "version": "1",
    "chainId": 56
  },
  "message": {
    "walletAddress": "0xYourWalletAddress",
    "action": "create",
    "timestamp": "1753690000"
  }
}
```

* `action` must match the endpoint: `create` for POST, `get` for GET, `delete` for DELETE. A signature for one action cannot be reused for another.
* Signatures expire after 5 minutes. `create` and `delete` signatures are single-use — sign a fresh message for each attempt. `get` signatures can be retried within the window.

#### Example

```typescript
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY);
const timestamp = Math.floor(Date.now() / 1000).toString();

const signature = await wallet.signTypedData(
  { name: "Opinion OpenAPI", version: "1", chainId: 56 },
  {
    OpinionApiKeyAuth: [
      { name: "walletAddress", type: "address" },
      { name: "action", type: "string" },
      { name: "timestamp", type: "string" },
    ],
  },
  { walletAddress: wallet.address, action: "create", timestamp }
);

const res = await fetch("https://openapi.opinion.trade/openapi/auth/api-key", {
  method: "POST",
  headers: {
    OPINION_ADDRESS: wallet.address,
    OPINION_SIGNATURE: signature,
    OPINION_TIMESTAMP: timestamp,
  },
});
console.log(await res.json());
// { "errno": 0, "errmsg": "", "result": { "apiKey": "…", "walletAddress": "0x…" } }
```

#### Response codes

Unlike the data endpoints, the Auth endpoints return HTTP `200` with the outcome in an `errno` / `errmsg` / `result` envelope — always check `errno`:

| errno   | Meaning                                                                        |
| ------- | ------------------------------------------------------------------------------ |
| `0`     | Success                                                                        |
| `11004` | Self-service key issuance is temporarily disabled                              |
| `11005` | Wallet is not a registered Opinion account — connect it on opinion.trade first |
| `11009` | API key already exists — use GET to retrieve it                                |
| `11010` | No API key found for this wallet                                               |
| `11011` | Invalid signature                                                              |
| `11012` | Signature expired                                                              |
| `11013` | Signature already used — sign a fresh message                                  |

#### Key activation timing

* A newly created key becomes active at the gateway within about **15 seconds**. If your first requests return `401`, simply retry — no backoff needed.
* After `DELETE`, the old key stops working within about 10 seconds.
* To rotate a key, call `DELETE` then `POST`, and plan for up to \~15 seconds during which neither key is usable.

If you lose your key, call `GET` at any time to retrieve it. If your key is compromised, `DELETE` it and create a new one.


# Rate Limiting

The API imposes a rate limit of 15 requests per second. If you require a higher rate limit or have questions regarding rate limit policies, please contact <nik@opinionlabs.xyz> for professional assistance.


# Pagination

Most list endpoints support pagination with `page` and `limit` parameters. Maximum limit per request is 20 items.


# Market

Market listing and details

## Get market list

> Get a paginated list of all markets (categorical and binary)

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Market","description":"Market listing and details"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"MarketListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of markets"},"list":{"type":"array","items":{"$ref":"#/components/schemas/MarketData"}}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/market":{"get":{"tags":["Market"],"summary":"Get market list","description":"Get a paginated list of all markets (categorical and binary)","operationId":"getMarketList","parameters":[{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}},{"name":"status","in":"query","description":"Market status filter","schema":{"type":"string","enum":["activated","resolved"]}},{"name":"marketType","in":"query","description":"Market type filter: 0=Binary, 1=Categorical, 2=All","schema":{"type":"integer","default":0,"enum":[0,1,2]}},{"name":"sortBy","in":"query","description":"Sort order: 1=new, 2=ending soon, 3=volume desc, 4=volume asc, 5=volume24h desc, 6=volume24h asc, 7=volume7d desc, 8=volume7d asc","schema":{"type":"integer","enum":[1,2,3,4,5,6,7,8]}},{"name":"chainId","in":"query","description":"Chain ID filter","schema":{"type":"string"}},{"name":"labelId","in":"query","description":"Filter markets by label id. 0 or omitted returns all markets.\nUse GET /label to discover available label ids; prefer labelId\nover labelName because operators may rename labels.\n","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/MarketListResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get binary market detail

> Get detailed information about a specific binary market

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Market","description":"Market listing and details"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"MarketDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MarketData"}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/market/{marketId}":{"get":{"tags":["Market"],"summary":"Get binary market detail","description":"Get detailed information about a specific binary market","operationId":"getBinaryMarketDetail","parameters":[{"name":"marketId","in":"path","required":true,"description":"Market ID","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/MarketDetailResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get categorical market detail

> Get detailed information about a specific categorical market including child markets

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Market","description":"Market listing and details"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"MarketDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MarketData"}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/market/categorical/{marketId}":{"get":{"tags":["Market"],"summary":"Get categorical market detail","description":"Get detailed information about a specific categorical market including child markets","operationId":"getCategoricalMarketDetail","parameters":[{"name":"marketId","in":"path","required":true,"description":"Market ID","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/MarketDetailResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get market detail by slug

> Get detailed information about a specific market by slug (supports binary and categorical market routing)

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Market","description":"Market listing and details"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"MarketDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MarketData"}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/market/slug/{slug}":{"get":{"tags":["Market"],"summary":"Get market detail by slug","description":"Get detailed information about a specific market by slug (supports binary and categorical market routing)","operationId":"getMarketDetailBySlug","parameters":[{"name":"slug","in":"path","required":true,"description":"Market slug","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/MarketDetailResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get Label List

> Get list of labels (categories). Use the returned labelId values with\
> /market?labelId=\<id> to filter markets. labelName is human-readable and\
> may be renamed by operators — integrators should treat labelId as the\
> stable key.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Market","description":"Market listing and details"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"LabelListResp":{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/LabelData"}}}},"LabelData":{"type":"object","properties":{"labelId":{"type":"integer","format":"int64","description":"Stable label id (use this for filtering)"},"labelName":{"type":"string","description":"Display label name (may be renamed by operators)"},"imageUrl":{"type":"string","description":"Desktop label image URL"},"imageMobileUrl":{"type":"string","description":"Mobile label image URL"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/label":{"get":{"tags":["Market"],"summary":"Get Label List","description":"Get list of labels (categories). Use the returned labelId values with\n/market?labelId=<id> to filter markets. labelName is human-readable and\nmay be renamed by operators — integrators should treat labelId as the\nstable key.\n","operationId":"getLabelList","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/LabelListResp"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Token

Token price and orderbook operations

## Get latest price

> Get the latest trade price and details for a specific token

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Token","description":"Token price and orderbook operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"LatestPriceResponse":{"type":"object","properties":{"tokenId":{"type":"string","description":"Token ID"},"price":{"type":"string","description":"Latest trade price"},"side":{"type":"string","description":"Last trade side (BUY/SELL)"},"size":{"type":"string","description":"Last trade size"},"timestamp":{"type":"integer","format":"int64","description":"Trade timestamp in milliseconds"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/token/latest-price":{"get":{"tags":["Token"],"summary":"Get latest price","description":"Get the latest trade price and details for a specific token","operationId":"getLatestPrice","parameters":[{"name":"token_id","in":"query","required":true,"description":"Token ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/LatestPriceResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get orderbook

> Get the orderbook (market depth) for a specific token

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Token","description":"Token price and orderbook operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"OrderbookResponse":{"type":"object","properties":{"market":{"type":"string","description":"Condition ID"},"tokenId":{"type":"string","description":"Token ID"},"timestamp":{"type":"integer","format":"int64","description":"Timestamp in milliseconds"},"bids":{"type":"array","items":{"$ref":"#/components/schemas/OrderbookLevel"},"description":"Buy orders, sorted by price descending"},"asks":{"type":"array","items":{"$ref":"#/components/schemas/OrderbookLevel"},"description":"Sell orders, sorted by price ascending"}}},"OrderbookLevel":{"type":"object","properties":{"price":{"type":"string","description":"Price level"},"size":{"type":"string","description":"Total size at this price level"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/token/orderbook":{"get":{"tags":["Token"],"summary":"Get orderbook","description":"Get the orderbook (market depth) for a specific token","operationId":"getOrderbook","parameters":[{"name":"token_id","in":"query","required":true,"description":"Token ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/OrderbookResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get price history

> Get historical price data for a specific token (Polymarket-compatible format)

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Token","description":"Token price and orderbook operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"PriceHistoryResponse":{"type":"object","properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/PricePoint"},"description":"Historical price data points"}}},"PricePoint":{"type":"object","properties":{"t":{"type":"integer","format":"int64","description":"UTC timestamp in seconds"},"p":{"type":"string","description":"Price"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/token/price-history":{"get":{"tags":["Token"],"summary":"Get price history","description":"Get historical price data for a specific token (Polymarket-compatible format)","operationId":"getPriceHistory","parameters":[{"name":"token_id","in":"query","required":true,"description":"Token ID","schema":{"type":"string"}},{"name":"interval","in":"query","description":"Price data interval: 1m, 1h, 1d, 1w, max","schema":{"type":"string","default":"1d","enum":["1m","1h","1d","1w","max"]}},{"name":"start_at","in":"query","description":"Start timestamp in Unix seconds","schema":{"type":"integer","format":"int64"}},{"name":"end_at","in":"query","description":"End timestamp in Unix seconds","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/PriceHistoryResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Quote Token

Quote token (currency) operations

## Get quote token list

> Retrieve a list of quote tokens (trading pair quote currencies)

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"QuoteToken","description":"Quote token (currency) operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"QuoteTokenListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of quote tokens"},"list":{"type":"array","items":{"$ref":"#/components/schemas/QuoteTokenData"}}}},"QuoteTokenData":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Quote token ID"},"quoteTokenName":{"type":"string","description":"Quote token name"},"quoteTokenAddress":{"type":"string","description":"Quote token contract address"},"ctfExchangeAddress":{"type":"string","description":"CTF Exchange contract address"},"decimal":{"type":"integer","description":"Token decimals"},"symbol":{"type":"string","description":"Token symbol"},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/quoteToken":{"get":{"tags":["QuoteToken"],"summary":"Get quote token list","description":"Retrieve a list of quote tokens (trading pair quote currencies)","operationId":"getQuoteTokenList","parameters":[{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","description":"Number of items per page","schema":{"type":"integer","default":10}},{"name":"quoteTokenName","in":"query","description":"Filter by quote token name","schema":{"type":"string"}},{"name":"chainId","in":"query","description":"Filter by chain ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/QuoteTokenListResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Position

User position (portfolio) operations

## Get user positions

> Get positions (portfolio) of a specific user by wallet address. Results are sorted by position size (descending).

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Position","description":"User position (portfolio) operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"PositionsResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of positions"},"list":{"type":"array","items":{"$ref":"#/components/schemas/PositionData"}}}},"PositionData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"marketStatus":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted"},"marketStatusEnum":{"type":"string","description":"Human-readable market status"},"marketCutoffAt":{"type":"integer","format":"int64","description":"Market cutoff timestamp"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"sharesOwned":{"type":"string","description":"Number of shares owned"},"sharesFrozen":{"type":"string","description":"Number of shares frozen (in pending orders)"},"unrealizedPnl":{"type":"string","description":"Unrealized profit/loss"},"unrealizedPnlPercent":{"type":"string","description":"Unrealized profit/loss percentage"},"dailyPnlChange":{"type":"string","description":"Daily PnL change"},"dailyPnlChangePercent":{"type":"string","description":"Daily PnL change percentage"},"conditionId":{"type":"string","description":"Condition ID"},"tokenId":{"type":"string","description":"Token ID"},"currentValueInQuoteToken":{"type":"string","description":"Current value in quote token (shares × current price)"},"avgEntryPrice":{"type":"string","description":"Average entry price"},"claimStatus":{"type":"integer","description":"Claim status: 0=CanNotClaim, 1=WaitClaim, 2=Claiming, 3=ClaimFailed, 4=Claimed"},"claimStatusEnum":{"type":"string","description":"Human-readable claim status","enum":["CanNotClaim","WaitClaim","Claiming","ClaimFailed","Claimed"]},"quoteToken":{"type":"string","description":"Quote token address"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/positions/user/{walletAddress}":{"get":{"tags":["Position"],"summary":"Get user positions","description":"Get positions (portfolio) of a specific user by wallet address. Results are sorted by position size (descending).","operationId":"getUserPositions","parameters":[{"name":"walletAddress","in":"path","required":true,"description":"Target user's wallet address","schema":{"type":"string"}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}},{"name":"marketId","in":"query","description":"Market ID filter","schema":{"type":"integer","format":"int64"}},{"name":"chainId","in":"query","description":"Chain ID filter","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/PositionsResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Trade

User trade history operations

## Get user trades

> Get trades of a specific user by wallet address. Only returns filled (successful) trades. Results are sorted by creation time (descending).

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Trade","description":"User trade history operations"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"UserTradeListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of trades"},"list":{"type":"array","items":{"$ref":"#/components/schemas/UserTradeData"}}}},"UserTradeData":{"type":"object","description":"Trade data for querying other users' trades (orderNo and tradeNo are hidden for privacy)","properties":{"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"string","description":"Trade side (BUY/SELL)"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Trade price"},"shares":{"type":"string","description":"Number of shares traded"},"amount":{"type":"string","description":"Trade amount in quote token"},"fee":{"type":"string","description":"Fee amount (human-readable format)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"quoteTokenUsdPrice":{"type":"string","description":"USD price of quote token"},"usdAmount":{"type":"string","description":"Total USD value of this trade"},"status":{"type":"integer","description":"Trade status: 1=Pending, 2=Filled, 3=Canceled, 4=Expired, 5=Failed"},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Filled","Canceled","Expired","Failed"]},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/trade/user/{walletAddress}":{"get":{"tags":["Trade"],"summary":"Get user trades","description":"Get trades of a specific user by wallet address. Only returns filled (successful) trades. Results are sorted by creation time (descending).","operationId":"getUserTrades","parameters":[{"name":"walletAddress","in":"path","required":true,"description":"Target user's wallet address","schema":{"type":"string"}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}},{"name":"marketId","in":"query","description":"Market ID filter","schema":{"type":"integer","format":"int64"}},{"name":"chainId","in":"query","description":"Chain ID filter","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/UserTradeListResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Order

User order list and order detail (authenticated). `OrderData` includes optional `postOnly` for limit orders.

## Get orders

> Get the authenticated user's orders with optional filters. Supports pagination and status filter (numeric or comma-separated, e.g. "1,2,3"). Status 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed.

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Order","description":"User order list and order detail (authenticated). `OrderData` includes optional `postOnly` for limit orders."}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"OrderListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of orders"},"list":{"type":"array","items":{"$ref":"#/components/schemas/OrderData"},"description":"List of orders"}}},"OrderData":{"type":"object","description":"Order record (list item or detail; detail includes trades)","properties":{"orderId":{"type":"string","description":"Order ID (transaction number)"},"transNo":{"type":"string","description":"Deprecated; use orderId instead"},"status":{"type":"integer","description":"Order status: 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","enum":[1,2,3,4,5]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Finished","Canceled","Expired","Failed"]},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"integer","description":"Side: 1=buy, 2=sell","enum":[1,2]},"sideEnum":{"type":"string","description":"Human-readable side","enum":["Buy","Sell"]},"tradingMethod":{"type":"integer","description":"Trading method: 1=market, 2=limit"},"tradingMethodEnum":{"type":"string","description":"Human-readable trading method","enum":["Market","Limit"]},"tradingUnit":{"type":"integer","description":"Trading unit: 1=quote, 2=shares. Indicates which side was user-specified when the order was placed.","enum":[1,2]},"tradingUnitEnum":{"type":"string","description":"Human-readable trading unit. `quote` means the user specified quote-token amount; `shares` means the user specified shares quantity.","enum":["quote","shares"]},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=yes, 2=no","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Price per share (for limit orders)"},"orderShares":{"type":"string","description":"Total shares in order (base token). Always expressed in shares regardless of tradingUnit."},"orderAmount":{"type":"string","description":"Total amount in order (quote token). Always expressed in quote-token amount regardless of tradingUnit."},"filledShares":{"type":"string","description":"Filled shares (base token)"},"filledAmount":{"type":"string","description":"Filled amount (quote token)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp (milliseconds)"},"expiresAt":{"type":"integer","format":"int64","description":"Expiration timestamp (milliseconds)"},"postOnly":{"type":"boolean","description":"Optional. Post-only mode for limit orders (default false). When true on place-order, the order must rest as maker; if it would cross the spread it is cancelled (comment: post-only order would cross the spread). Market orders with postOnly=true are rejected with code 10610."},"trades":{"type":"array","items":{"$ref":"#/components/schemas/OrderTradeData"},"description":"Related trades (only present in order detail response)"}}},"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/order":{"get":{"tags":["Order"],"summary":"Get orders","description":"Get the authenticated user's orders with optional filters. Supports pagination and status filter (numeric or comma-separated, e.g. \"1,2,3\"). Status 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed.","operationId":"getOrderList","parameters":[{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}},{"name":"marketId","in":"query","description":"Market ID filter","schema":{"type":"integer","format":"int64"}},{"name":"chainId","in":"query","description":"Chain ID filter","schema":{"type":"string"}},{"name":"status","in":"query","description":"Order status filter: single value (1-5) or comma-separated (e.g. 1,2,3). 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/OrderListResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```

## Get order detail

> Get order detail by order ID for the authenticated user, including related trades.

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Order","description":"User order list and order detail (authenticated). `OrderData` includes optional `postOnly` for limit orders."}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"apikey","description":"API key for authentication"}},"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}},"OrderDetailResponse":{"type":"object","description":"Order detail response (includes orderData with trades)","properties":{"orderData":{"$ref":"#/components/schemas/OrderData"}}},"OrderData":{"type":"object","description":"Order record (list item or detail; detail includes trades)","properties":{"orderId":{"type":"string","description":"Order ID (transaction number)"},"transNo":{"type":"string","description":"Deprecated; use orderId instead"},"status":{"type":"integer","description":"Order status: 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","enum":[1,2,3,4,5]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Finished","Canceled","Expired","Failed"]},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"integer","description":"Side: 1=buy, 2=sell","enum":[1,2]},"sideEnum":{"type":"string","description":"Human-readable side","enum":["Buy","Sell"]},"tradingMethod":{"type":"integer","description":"Trading method: 1=market, 2=limit"},"tradingMethodEnum":{"type":"string","description":"Human-readable trading method","enum":["Market","Limit"]},"tradingUnit":{"type":"integer","description":"Trading unit: 1=quote, 2=shares. Indicates which side was user-specified when the order was placed.","enum":[1,2]},"tradingUnitEnum":{"type":"string","description":"Human-readable trading unit. `quote` means the user specified quote-token amount; `shares` means the user specified shares quantity.","enum":["quote","shares"]},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=yes, 2=no","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Price per share (for limit orders)"},"orderShares":{"type":"string","description":"Total shares in order (base token). Always expressed in shares regardless of tradingUnit."},"orderAmount":{"type":"string","description":"Total amount in order (quote token). Always expressed in quote-token amount regardless of tradingUnit."},"filledShares":{"type":"string","description":"Filled shares (base token)"},"filledAmount":{"type":"string","description":"Filled amount (quote token)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp (milliseconds)"},"expiresAt":{"type":"integer","format":"int64","description":"Expiration timestamp (milliseconds)"},"postOnly":{"type":"boolean","description":"Optional. Post-only mode for limit orders (default false). When true on place-order, the order must rest as maker; if it would cross the spread it is cancelled (comment: post-only order would cross the spread). Market orders with postOnly=true are rejected with code 10610."},"trades":{"type":"array","items":{"$ref":"#/components/schemas/OrderTradeData"},"description":"Related trades (only present in order detail response)"}}},"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}},"responses":{"BadRequestError":{"description":"Bad request - invalid parameters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"code":{},"msg":{}}}]}}}}}},"paths":{"/order/{orderId}":{"get":{"tags":["Order"],"summary":"Get order detail","description":"Get order detail by order ID for the authenticated user, including related trades.","operationId":"getOrderDetail","parameters":[{"name":"orderId","in":"path","required":true,"description":"Order ID (transaction number)","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/OrderDetailResponse"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequestError"}}}}}}
```


# Resolution

Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.

The three GET endpoints are public (no API key). Responses are cached server-side for \~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.

Markets in the resolution flow are in one of three phases:

* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`
* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`
* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)

Fields default to `""` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.

#### Error codes

Returned with HTTP 200 in the envelope:

* `10003` — invalid parameters (`errmsg` says which)
* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used
* `11020` — no resolution record for this market
* `11021` — no dispute filed for this question
* `11022` — signer is not the on-chain disputor
* `11023` — rationale frozen (already finalized)
* `11024` — rationale too long (max 2000 bytes)

## Get resolution market list

> Markets in the resolution flow, viewed from the resolution side.\
> \
> Fixed ordering: \`proposed\` rows by \`disputeDeadline\` ascending, then \`disputed\` rows by \`arbitrationDeadline\` ascending, then \`finalized\` rows by \`finalizedAt\` descending.\
> \
> \### Self-indexing with events\
> \
> If you index on-chain events directly, subscribe on the \`disputeResolver\` addresses returned by the API. Event signatures:\
> \
> \`\`\`text\
> ResolutionReceived(bytes32 indexed questionId, uint256\[2] payouts, uint256 disputeDeadline)\
> RewardDeposited(bytes32 indexed questionId, address indexed stakeToken, uint256 amount)\
> DisputeFiled(bytes32 indexed questionId, address indexed disputor, uint256 stakeAmount, uint8 reason, uint256 reVoteDeadline)\
> DisputeResolved(bytes32 indexed questionId, bool overturned, uint256\[2] finalPayouts)\
> DisputeTimedOut(bytes32 indexed questionId)\
> ResolutionFinalized(bytes32 indexed questionId, uint256\[2] payouts)\
> StakeClaimed(bytes32 indexed questionId, address indexed recipient, uint256 amount)\
> \`\`\`\
> \
> A market enters the feed as \`proposed\` when \`RewardDeposited\` lands (that transaction fixes the dispute window); \`ResolutionReceived\` is a slightly earlier step carrying the proposed result, and anchors \`proposedAt\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/markets":{"get":{"tags":["Resolution"],"summary":"Get resolution market list","description":"Markets in the resolution flow, viewed from the resolution side.\n\nFixed ordering: `proposed` rows by `disputeDeadline` ascending, then `disputed` rows by `arbitrationDeadline` ascending, then `finalized` rows by `finalizedAt` descending.\n\n### Self-indexing with events\n\nIf you index on-chain events directly, subscribe on the `disputeResolver` addresses returned by the API. Event signatures:\n\n```text\nResolutionReceived(bytes32 indexed questionId, uint256[2] payouts, uint256 disputeDeadline)\nRewardDeposited(bytes32 indexed questionId, address indexed stakeToken, uint256 amount)\nDisputeFiled(bytes32 indexed questionId, address indexed disputor, uint256 stakeAmount, uint8 reason, uint256 reVoteDeadline)\nDisputeResolved(bytes32 indexed questionId, bool overturned, uint256[2] finalPayouts)\nDisputeTimedOut(bytes32 indexed questionId)\nResolutionFinalized(bytes32 indexed questionId, uint256[2] payouts)\nStakeClaimed(bytes32 indexed questionId, address indexed recipient, uint256 amount)\n```\n\nA market enters the feed as `proposed` when `RewardDeposited` lands (that transaction fixes the dispute window); `ResolutionReceived` is a slightly earlier step carrying the proposed result, and anchors `proposedAt`.\n","operationId":"getResolutionMarketList","parameters":[{"name":"phase","in":"query","description":"Filter by phase","schema":{"type":"string","enum":["proposed","disputed","finalized"]}},{"name":"labelId","in":"query","description":"Filter by category label id — same ids as `GET /label`","schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionMarketsResponse"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionMarketsResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of markets for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionMarketRow"}}}},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}}}}}
````

## Get resolution market detail

> Full resolution view of one market: all list-row fields plus the rules text, the tx-anchored timeline, and the \`dispute\` object carrying everything needed to send dispute/claim transactions.\
> \
> Returns \`errno 11020\` when the market has no resolution record (nothing proposed yet, categorical parent id — children resolve independently — or a market predating the feed).\
> \
> \### Disputing on-chain\
> \
> Filing and claiming are direct contract calls with the parameters from the \`dispute\` object.\
> \
> \*\*Always call \`isDisputable(questionId)\` on-chain immediately before sending a dispute transaction.\*\* API responses are cached \~10 s; \`isDisputable\` is the on-chain truth for deadline boundaries and the one-dispute-per-resolution slot. Skipping it risks a reverted transaction.\
> \
> \`\`\`javascript\
> const DR\_ABI = \[\
> &#x20; "function dispute(bytes32 questionId, uint8 reason) external",\
> &#x20; "function claim(bytes32 questionId) external",\
> &#x20; "function isDisputable(bytes32 questionId) external view returns (bool)",\
> ];\
> const ERC20\_ABI = \["function approve(address spender, uint256 amount) returns (bool)"];\
> \
> const dr  = new ethers.Contract(dispute.disputeResolver, DR\_ABI, wallet);\
> const opn = new ethers.Contract(dispute.stakeToken, ERC20\_ABI, wallet);\
> \
> if (!(await dr.isDisputable(questionId))) throw new Error("not disputable");\
> \
> const stake = ethers.parseUnits(dispute.requiredStake, dispute.stakeTokenDecimals);\
> await (await opn.approve(dispute.disputeResolver, stake)).wait();\
> await (await dr.dispute(questionId, 0 /\* 0 = WrongResult, 1 = TooEarly \*/)).wait();\
> \
> // after finalization, when claimStatus = "claimable":\
> await (await dr.claim(questionId)).wait();\
> \`\`\`<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/market/{marketId}":{"get":{"tags":["Resolution"],"summary":"Get resolution market detail","description":"Full resolution view of one market: all list-row fields plus the rules text, the tx-anchored timeline, and the `dispute` object carrying everything needed to send dispute/claim transactions.\n\nReturns `errno 11020` when the market has no resolution record (nothing proposed yet, categorical parent id — children resolve independently — or a market predating the feed).\n\n### Disputing on-chain\n\nFiling and claiming are direct contract calls with the parameters from the `dispute` object.\n\n**Always call `isDisputable(questionId)` on-chain immediately before sending a dispute transaction.** API responses are cached ~10 s; `isDisputable` is the on-chain truth for deadline boundaries and the one-dispute-per-resolution slot. Skipping it risks a reverted transaction.\n\n```javascript\nconst DR_ABI = [\n  \"function dispute(bytes32 questionId, uint8 reason) external\",\n  \"function claim(bytes32 questionId) external\",\n  \"function isDisputable(bytes32 questionId) external view returns (bool)\",\n];\nconst ERC20_ABI = [\"function approve(address spender, uint256 amount) returns (bool)\"];\n\nconst dr  = new ethers.Contract(dispute.disputeResolver, DR_ABI, wallet);\nconst opn = new ethers.Contract(dispute.stakeToken, ERC20_ABI, wallet);\n\nif (!(await dr.isDisputable(questionId))) throw new Error(\"not disputable\");\n\nconst stake = ethers.parseUnits(dispute.requiredStake, dispute.stakeTokenDecimals);\nawait (await opn.approve(dispute.disputeResolver, stake)).wait();\nawait (await dr.dispute(questionId, 0 /* 0 = WrongResult, 1 = TooEarly */)).wait();\n\n// after finalization, when claimStatus = \"claimable\":\nawait (await dr.claim(questionId)).wait();\n```\n","operationId":"getResolutionMarketDetail","parameters":[{"name":"marketId","in":"path","required":true,"description":"Numeric market id, or a 0x-prefixed question id","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionMarketDetail"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionMarketDetail":{"allOf":[{"$ref":"#/components/schemas/ResolutionMarketRow"},{"type":"object","properties":{"rules":{"type":"string","description":"This market's resolution rules — the criteria the outcome is judged against"},"parentRules":{"type":"string","nullable":true,"description":"Parent event's rules for categorical child markets (read both when evaluating a proposal); null for binary markets"},"dispute":{"$ref":"#/components/schemas/ResolutionDisputeParams"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionTimelineStage"},"description":"Fixed three stages (`proposed` / `disputed` / `finalized`), each anchored to its on-chain transaction; stages not reached are null. Records indexed before tx tracking was introduced may show null `at` / `txHash` for stages that did occur"}}}]},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}},"ResolutionDisputeParams":{"type":"object","nullable":true,"description":"Everything needed to send dispute/claim transactions, plus the state of the current dispute. Null for price-oracle records (nothing to dispute).","properties":{"chainId":{"type":"string","description":"Chain id"},"disputeResolver":{"type":"string","description":"Contract to call `dispute` / `claim` / `isDisputable` on for this market — always take the address from here"},"requiredStake":{"type":"string","nullable":true,"description":"Stake required to dispute (display units)"},"stakeToken":{"type":"string","description":"ERC-20 address of the stake token — `approve` it to `disputeResolver` before disputing"},"stakeTokenSymbol":{"type":"string"},"stakeTokenDecimals":{"type":"integer","description":"Stake token decimals — use with `requiredStake` to build the raw approve amount"},"disputor":{"type":"string","nullable":true,"description":"Wallet that filed the dispute; null if none"},"reason":{"type":"integer","nullable":true,"description":"Dispute reason as filed: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason (`WrongResult` / `TooEarly`); `\"\"` if never disputed"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set once finalized"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake — `not_applicable` (no dispute, dispute rejected, or not yet finalized) / `claimable` (dispute won, unclaimed) / `claimed`"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of finalization"},"claimTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the stake claim"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `disputeOutcome`, not the deadline"}}},"ResolutionTimelineStage":{"type":"object","description":"One tx-anchored stage of the resolution timeline","properties":{"stage":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Stage name, always in this order"},"at":{"type":"integer","format":"int64","nullable":true,"description":"Unix time of the stage's transaction; null if not reached"},"txHash":{"type":"string","nullable":true,"description":"Transaction hash anchoring the stage; null if not reached"}}}}}}
````

## Get dispute list

> Disputes across all markets, with reason, rationale, outcome and claim state. Ordered by \`disputedAt\` descending (newest first).\
> \
> \`proposedPayouts\` / \`proposedTokenId\` are the original proposal that was disputed; \`resultTokenId\` / \`finalizeTxHash\` stay empty while review is in progress.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/disputes":{"get":{"tags":["Resolution"],"summary":"Get dispute list","description":"Disputes across all markets, with reason, rationale, outcome and claim state. Ordered by `disputedAt` descending (newest first).\n\n`proposedPayouts` / `proposedTokenId` are the original proposal that was disputed; `resultTokenId` / `finalizeTxHash` stay empty while review is in progress.\n","operationId":"getResolutionDisputes","parameters":[{"name":"disputor","in":"query","description":"Filter by disputor wallet address","schema":{"type":"string"}},{"name":"outcome","in":"query","description":"Filter by outcome (`pending` = review in progress)","schema":{"type":"string","enum":["pending","upheld","overturned","timeout"]}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionDisputesResponse"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionDisputesResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of disputes for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionDisputeRow"}}}},"ResolutionDisputeRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"disputor":{"type":"string","description":"Wallet that filed this dispute"},"reason":{"type":"integer","description":"Dispute reason: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"stake":{"type":"string","description":"Amount actually staked for this dispute (display units)"},"stakeTokenSymbol":{"type":"string"},"proposedPayouts":{"type":"array","items":{"type":"string"},"description":"The original proposal that was disputed, as the payout vector `[yes, no]`"},"proposedTokenId":{"type":"string","description":"Proposed winning token id of the disputed proposal (`\"draw\"` for a tie)"},"resultTokenId":{"type":"string","description":"Final outcome; `\"\"` while review is in progress"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Null while review is in progress"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","description":"Transaction hash of finalization; `\"\"` while review is in progress"},"claimTxHash":{"type":"string","description":"Transaction hash of the stake claim; `\"\"` if not claimed"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline of this dispute"}}}}}}
```

## Submit dispute rationale

> After your dispute transaction confirms, attach a public statement. It is shown with the dispute in the endpoints above. Authenticated by an EIP-712 signature from the disputing wallet — no API key or account needed; the server verifies the signer against the on-chain disputor, so you can submit as soon as the dispute transaction confirms. Verification is ECDSA recovery, so the disputing wallet must be an EOA — disputes filed from a contract wallet (e.g. a Safe) cannot attach a rationale.\
> \
> Accepted while the dispute is under review; resubmit to overwrite; frozen after finalization. Signatures expire after 5 minutes and are single-use.\
> \
> Sign the following typed data — \`walletAddress\` lowercase, \`questionId\` exactly as in the URL, \`rationale\` exactly the string sent in the body. The server strips leading/trailing whitespace before verifying, so sign and send the rationale without surrounding whitespace. Maximum 2000 bytes (UTF-8):\
> \
> \`\`\`javascript\
> const domain = { name: "Opinion OpenAPI", version: "1", chainId: 56 };\
> const types = {\
> &#x20; OpinionDisputeRationale: \[\
> &#x20;   { name: "walletAddress", type: "address" },\
> &#x20;   { name: "questionId",    type: "string"  },\
> &#x20;   { name: "rationale",     type: "string"  },\
> &#x20;   { name: "timestamp",     type: "uint256" },\
> &#x20; ],\
> };\
> const timestamp = Math.floor(Date.now() / 1000).toString();\
> const message = {\
> &#x20; walletAddress: wallet.address.toLowerCase(),\
> &#x20; questionId,\
> &#x20; rationale: "Final score was 3-2 per MLB.com; the proposal resolves the wrong side.",\
> &#x20; timestamp,\
> };\
> const signature = await wallet.signTypedData(domain, types, message);\
> \`\`\`\
> \
> Send \`signature\` in \`OPINION\_SIGNATURE\`, the wallet address in \`OPINION\_ADDRESS\` and the signed timestamp in \`OPINION\_TIMESTAMP\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/dispute/{questionId}/rationale":{"post":{"tags":["Resolution"],"summary":"Submit dispute rationale","description":"After your dispute transaction confirms, attach a public statement. It is shown with the dispute in the endpoints above. Authenticated by an EIP-712 signature from the disputing wallet — no API key or account needed; the server verifies the signer against the on-chain disputor, so you can submit as soon as the dispute transaction confirms. Verification is ECDSA recovery, so the disputing wallet must be an EOA — disputes filed from a contract wallet (e.g. a Safe) cannot attach a rationale.\n\nAccepted while the dispute is under review; resubmit to overwrite; frozen after finalization. Signatures expire after 5 minutes and are single-use.\n\nSign the following typed data — `walletAddress` lowercase, `questionId` exactly as in the URL, `rationale` exactly the string sent in the body. The server strips leading/trailing whitespace before verifying, so sign and send the rationale without surrounding whitespace. Maximum 2000 bytes (UTF-8):\n\n```javascript\nconst domain = { name: \"Opinion OpenAPI\", version: \"1\", chainId: 56 };\nconst types = {\n  OpinionDisputeRationale: [\n    { name: \"walletAddress\", type: \"address\" },\n    { name: \"questionId\",    type: \"string\"  },\n    { name: \"rationale\",     type: \"string\"  },\n    { name: \"timestamp\",     type: \"uint256\" },\n  ],\n};\nconst timestamp = Math.floor(Date.now() / 1000).toString();\nconst message = {\n  walletAddress: wallet.address.toLowerCase(),\n  questionId,\n  rationale: \"Final score was 3-2 per MLB.com; the proposal resolves the wrong side.\",\n  timestamp,\n};\nconst signature = await wallet.signTypedData(domain, types, message);\n```\n\nSend `signature` in `OPINION_SIGNATURE`, the wallet address in `OPINION_ADDRESS` and the signed timestamp in `OPINION_TIMESTAMP`.\n","operationId":"submitDisputeRationale","parameters":[{"name":"questionId","in":"path","required":true,"description":"0x-prefixed question id, exactly as returned by the API","schema":{"type":"string"}},{"$ref":"#/components/parameters/RationaleAddressHeader"},{"$ref":"#/components/parameters/RationaleSignatureHeader"},{"$ref":"#/components/parameters/RationaleTimestampHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeRationaleRequest"}}}},"responses":{"200":{"description":"Successful response (check `errno` in the body)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/DisputeRationaleResult"}}}]}}}}}}}},"components":{"parameters":{"RationaleAddressHeader":{"name":"OPINION_ADDRESS","in":"header","required":true,"description":"Disputor wallet address (must equal the on-chain disputor)","schema":{"type":"string"}},"RationaleSignatureHeader":{"name":"OPINION_SIGNATURE","in":"header","required":true,"description":"EIP-712 signature over the `OpinionDisputeRationale` typed data (see the endpoint description)","schema":{"type":"string"}},"RationaleTimestampHeader":{"name":"OPINION_TIMESTAMP","in":"header","required":true,"description":"Unix timestamp in seconds; must equal the signed `timestamp` field. Valid for 5 minutes.","schema":{"type":"string"}}},"schemas":{"DisputeRationaleRequest":{"type":"object","required":["rationale"],"properties":{"rationale":{"type":"string","description":"Public statement explaining the dispute — must equal the signed `rationale`; at most 2000 bytes (UTF-8). Surrounding whitespace is stripped server-side"}}},"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"DisputeRationaleResult":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}
````


# Auth

Self-service API key management (create / retrieve / delete), authenticated by an EIP-712 wallet signature instead of an API key. See the Authentication section for the typed-data structure, examples, error codes and activation timing.

## Get API key

> Retrieve the wallet's current API key. Authenticated by an EIP-712 wallet signature with \`action: get\` (see the Authentication section).\
> \
> Returns HTTP 200 with the outcome in \`errno\`: \`0\` success, \`11010\` no API key found, \`11011\` / \`11012\` invalid / expired signature. \`get\` signatures can be retried within the 5-minute window.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Auth","description":"Self-service API key management (create / retrieve / delete), authenticated by an EIP-712 wallet signature instead of an API key. See the Authentication section for the typed-data structure, examples, error codes and activation timing."}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/auth/api-key":{"get":{"tags":["Auth"],"summary":"Get API key","description":"Retrieve the wallet's current API key. Authenticated by an EIP-712 wallet signature with `action: get` (see the Authentication section).\n\nReturns HTTP 200 with the outcome in `errno`: `0` success, `11010` no API key found, `11011` / `11012` invalid / expired signature. `get` signatures can be retried within the 5-minute window.\n","operationId":"getApiKey","parameters":[{"$ref":"#/components/parameters/OpinionAddressHeader"},{"$ref":"#/components/parameters/OpinionSignatureHeader"},{"$ref":"#/components/parameters/OpinionTimestampHeader"}],"responses":{"200":{"description":"Successful response (check `errno` in the body)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AuthAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ApiKeyCredential"}}}]}}}}}}}},"components":{"parameters":{"OpinionAddressHeader":{"name":"OPINION_ADDRESS","in":"header","required":true,"description":"Signing wallet address","schema":{"type":"string"}},"OpinionSignatureHeader":{"name":"OPINION_SIGNATURE","in":"header","required":true,"description":"EIP-712 signature over the `OpinionApiKeyAuth` typed data (see the Authentication section)","schema":{"type":"string"}},"OpinionTimestampHeader":{"name":"OPINION_TIMESTAMP","in":"header","required":true,"description":"Unix timestamp in seconds; must equal the signed `timestamp` field. Valid for 5 minutes.","schema":{"type":"string"}}},"schemas":{"AuthAPIBaseResponse":{"type":"object","description":"Envelope used by the Auth endpoints. Unlike the data endpoints, the outcome is reported in `errno` / `errmsg`.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success; see the Authentication section for the full list)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ApiKeyCredential":{"type":"object","properties":{"apiKey":{"type":"string","description":"The API key for the `apikey` request header"},"walletAddress":{"type":"string","description":"Wallet address the key belongs to"}}}}}}
```

## Create API key

> Create the wallet's API key. Authenticated by an EIP-712 wallet signature with \`action: create\` (see the Authentication section).\
> \
> Returns HTTP 200 with the outcome in \`errno\`: \`0\` success, \`11004\` issuance disabled, \`11005\` wallet is not a registered Opinion account, \`11009\` key already exists, \`11011\` / \`11012\` / \`11013\` invalid / expired / already-used signature. A newly created key becomes active at the gateway within about 15 seconds — retry on \`401\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Auth","description":"Self-service API key management (create / retrieve / delete), authenticated by an EIP-712 wallet signature instead of an API key. See the Authentication section for the typed-data structure, examples, error codes and activation timing."}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/auth/api-key":{"post":{"tags":["Auth"],"summary":"Create API key","description":"Create the wallet's API key. Authenticated by an EIP-712 wallet signature with `action: create` (see the Authentication section).\n\nReturns HTTP 200 with the outcome in `errno`: `0` success, `11004` issuance disabled, `11005` wallet is not a registered Opinion account, `11009` key already exists, `11011` / `11012` / `11013` invalid / expired / already-used signature. A newly created key becomes active at the gateway within about 15 seconds — retry on `401`.\n","operationId":"createApiKey","parameters":[{"$ref":"#/components/parameters/OpinionAddressHeader"},{"$ref":"#/components/parameters/OpinionSignatureHeader"},{"$ref":"#/components/parameters/OpinionTimestampHeader"}],"responses":{"200":{"description":"Successful response (check `errno` in the body)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AuthAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ApiKeyCredential"}}}]}}}}}}}},"components":{"parameters":{"OpinionAddressHeader":{"name":"OPINION_ADDRESS","in":"header","required":true,"description":"Signing wallet address","schema":{"type":"string"}},"OpinionSignatureHeader":{"name":"OPINION_SIGNATURE","in":"header","required":true,"description":"EIP-712 signature over the `OpinionApiKeyAuth` typed data (see the Authentication section)","schema":{"type":"string"}},"OpinionTimestampHeader":{"name":"OPINION_TIMESTAMP","in":"header","required":true,"description":"Unix timestamp in seconds; must equal the signed `timestamp` field. Valid for 5 minutes.","schema":{"type":"string"}}},"schemas":{"AuthAPIBaseResponse":{"type":"object","description":"Envelope used by the Auth endpoints. Unlike the data endpoints, the outcome is reported in `errno` / `errmsg`.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success; see the Authentication section for the full list)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ApiKeyCredential":{"type":"object","properties":{"apiKey":{"type":"string","description":"The API key for the `apikey` request header"},"walletAddress":{"type":"string","description":"Wallet address the key belongs to"}}}}}}
```

## Delete API key

> Delete (revoke) the wallet's current API key. Authenticated by an EIP-712 wallet signature with \`action: delete\` (see the Authentication section). Deleting when no key exists is treated as success, so it is safe to retry.\
> \
> The revoked key stops working at the gateway within about 10 seconds.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Auth","description":"Self-service API key management (create / retrieve / delete), authenticated by an EIP-712 wallet signature instead of an API key. See the Authentication section for the typed-data structure, examples, error codes and activation timing."}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/auth/api-key":{"delete":{"tags":["Auth"],"summary":"Delete API key","description":"Delete (revoke) the wallet's current API key. Authenticated by an EIP-712 wallet signature with `action: delete` (see the Authentication section). Deleting when no key exists is treated as success, so it is safe to retry.\n\nThe revoked key stops working at the gateway within about 10 seconds.\n","operationId":"deleteApiKey","parameters":[{"$ref":"#/components/parameters/OpinionAddressHeader"},{"$ref":"#/components/parameters/OpinionSignatureHeader"},{"$ref":"#/components/parameters/OpinionTimestampHeader"}],"responses":{"200":{"description":"Successful response (check `errno` in the body)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AuthAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/DeleteApiKeyResult"}}}]}}}}}}}},"components":{"parameters":{"OpinionAddressHeader":{"name":"OPINION_ADDRESS","in":"header","required":true,"description":"Signing wallet address","schema":{"type":"string"}},"OpinionSignatureHeader":{"name":"OPINION_SIGNATURE","in":"header","required":true,"description":"EIP-712 signature over the `OpinionApiKeyAuth` typed data (see the Authentication section)","schema":{"type":"string"}},"OpinionTimestampHeader":{"name":"OPINION_TIMESTAMP","in":"header","required":true,"description":"Unix timestamp in seconds; must equal the signed `timestamp` field. Valid for 5 minutes.","schema":{"type":"string"}}},"schemas":{"AuthAPIBaseResponse":{"type":"object","description":"Envelope used by the Auth endpoints. Unlike the data endpoints, the outcome is reported in `errno` / `errmsg`.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success; see the Authentication section for the full list)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"DeleteApiKeyResult":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}}
```


# Models

## The APIBaseResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"APIBaseResponse":{"type":"object","properties":{"code":{"type":"integer","description":"Response code (0 for success)"},"msg":{"type":"string","description":"Response message"},"result":{"type":"object","description":"Response data"}}}}}}
```

## The AuthAPIBaseResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"AuthAPIBaseResponse":{"type":"object","description":"Envelope used by the Auth endpoints. Unlike the data endpoints, the outcome is reported in `errno` / `errmsg`.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success; see the Authentication section for the full list)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}}}}}
```

## The ApiKeyCredential object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ApiKeyCredential":{"type":"object","properties":{"apiKey":{"type":"string","description":"The API key for the `apikey` request header"},"walletAddress":{"type":"string","description":"Wallet address the key belongs to"}}}}}}
```

## The DeleteApiKeyResult object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"DeleteApiKeyResult":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}}
```

## The QuoteTokenBalance object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"QuoteTokenBalance":{"type":"object","properties":{"quoteToken":{"type":"string","description":"Quote token address"},"tokenDecimals":{"type":"integer","description":"Token decimals"},"totalBalance":{"type":"string","description":"Total balance (formatted with decimals)"},"availableBalance":{"type":"string","description":"Available balance (formatted with decimals)"},"frozenBalance":{"type":"string","description":"Frozen balance (formatted with decimals)"}}}}}}
```

## The MarketListResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"MarketListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of markets"},"list":{"type":"array","items":{"$ref":"#/components/schemas/MarketData"}}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}}}}
```

## The MarketDetailResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"MarketDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MarketData"}}},"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}}}}
```

## The CollectionMarketDataOpenAPI object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}}}}
```

## The CollectionDataOpenAPI object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}}}}
```

## The MarketData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"MarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"status":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted","enum":[1,2,3,4,5,6]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Created","Activated","Resolving","Resolved","Failed","Deleted"]},"marketType":{"type":"integer","description":"Market type: 0=Binary, 1=Categorical","enum":[0,1]},"childMarkets":{"type":"array","items":{"$ref":"#/components/schemas/ChildMarketData"},"description":"Child markets (for categorical markets)"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"rules":{"type":"string","description":"Market rules"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"conditionId":{"type":"string","description":"Condition ID"},"resultTokenId":{"type":"string","description":"Result token ID (after resolution)"},"volume":{"type":"string","description":"Total trading volume"},"volume24h":{"type":"string","description":"24-hour trading volume"},"volume7d":{"type":"string","description":"7-day trading volume"},"quoteToken":{"type":"string","description":"Quote token address"},"chainId":{"type":"string","description":"Chain ID"},"questionId":{"type":"string","description":"Question ID"},"incentiveFactor":{"type":"object","description":"Incentive factor (masked as empty object)"},"collection":{"$ref":"#/components/schemas/CollectionDataOpenAPI","description":"Collection market detail if any"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"},"cutoffAt":{"type":"integer","format":"int64","description":"Cutoff timestamp"},"resolvedAt":{"type":"integer","format":"int64","description":"Resolution timestamp"},"labels":{"type":"array","items":{"type":"string"},"description":"Category label names for this market. Aligned by index with labelIds\n(labels[i] corresponds to labelIds[i]). A market may have multiple labels.\n"},"labelIds":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Category label ids for this market. Aligned by index with labels.\nUse these for stable filtering via GET /market?labelId=<id> — names\nmay be renamed by operators.\n"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}},"CollectionDataOpenAPI":{"type":"object","description":"Collection market data (for recurring/time-series markets)","properties":{"title":{"type":"string","description":"Collection title"},"symbol":{"type":"string","description":"Collection symbol"},"frequency":{"type":"string","description":"Collection frequency (e.g., daily, weekly)"},"current":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI","description":"Current period market data"},"next":{"type":"array","items":{"$ref":"#/components/schemas/CollectionMarketDataOpenAPI"},"description":"Upcoming period markets"}}},"CollectionMarketDataOpenAPI":{"type":"object","description":"Collection market period data","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"period":{"type":"string","description":"Period identifier"},"startTime":{"type":"integer","format":"int64","description":"Period start timestamp"},"endTime":{"type":"integer","format":"int64","description":"Period end timestamp"},"startPrice":{"type":"string","description":"Starting price for this period"},"endPrice":{"type":"string","description":"Ending price for this period"}}}}}}
```

## The ChildMarketData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ChildMarketData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"yesLabel":{"type":"string"},"noLabel":{"type":"string"},"rules":{"type":"string"},"yesTokenId":{"type":"string"},"noTokenId":{"type":"string"},"conditionId":{"type":"string"},"resultTokenId":{"type":"string"},"volume":{"type":"string"},"quoteToken":{"type":"string"},"chainId":{"type":"string"},"questionId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"cutoffAt":{"type":"integer","format":"int64"},"resolvedAt":{"type":"integer","format":"int64"},"resolution":{"$ref":"#/components/schemas/MarketResolutionSummary"}}},"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}}}}}
```

## The LabelData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"LabelData":{"type":"object","properties":{"labelId":{"type":"integer","format":"int64","description":"Stable label id (use this for filtering)"},"labelName":{"type":"string","description":"Display label name (may be renamed by operators)"},"imageUrl":{"type":"string","description":"Desktop label image URL"},"imageMobileUrl":{"type":"string","description":"Mobile label image URL"}}}}}}
```

## The LabelListResp object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"LabelListResp":{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/LabelData"}}}},"LabelData":{"type":"object","properties":{"labelId":{"type":"integer","format":"int64","description":"Stable label id (use this for filtering)"},"labelName":{"type":"string","description":"Display label name (may be renamed by operators)"},"imageUrl":{"type":"string","description":"Desktop label image URL"},"imageMobileUrl":{"type":"string","description":"Mobile label image URL"}}}}}}
```

## The LatestPriceResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"LatestPriceResponse":{"type":"object","properties":{"tokenId":{"type":"string","description":"Token ID"},"price":{"type":"string","description":"Latest trade price"},"side":{"type":"string","description":"Last trade side (BUY/SELL)"},"size":{"type":"string","description":"Last trade size"},"timestamp":{"type":"integer","format":"int64","description":"Trade timestamp in milliseconds"}}}}}}
```

## The OrderbookResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderbookResponse":{"type":"object","properties":{"market":{"type":"string","description":"Condition ID"},"tokenId":{"type":"string","description":"Token ID"},"timestamp":{"type":"integer","format":"int64","description":"Timestamp in milliseconds"},"bids":{"type":"array","items":{"$ref":"#/components/schemas/OrderbookLevel"},"description":"Buy orders, sorted by price descending"},"asks":{"type":"array","items":{"$ref":"#/components/schemas/OrderbookLevel"},"description":"Sell orders, sorted by price ascending"}}},"OrderbookLevel":{"type":"object","properties":{"price":{"type":"string","description":"Price level"},"size":{"type":"string","description":"Total size at this price level"}}}}}}
```

## The OrderbookLevel object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderbookLevel":{"type":"object","properties":{"price":{"type":"string","description":"Price level"},"size":{"type":"string","description":"Total size at this price level"}}}}}}
```

## The PriceHistoryResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"PriceHistoryResponse":{"type":"object","properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/PricePoint"},"description":"Historical price data points"}}},"PricePoint":{"type":"object","properties":{"t":{"type":"integer","format":"int64","description":"UTC timestamp in seconds"},"p":{"type":"string","description":"Price"}}}}}}
```

## The PricePoint object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"PricePoint":{"type":"object","properties":{"t":{"type":"integer","format":"int64","description":"UTC timestamp in seconds"},"p":{"type":"string","description":"Price"}}}}}}
```

## The QuoteTokenListResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"QuoteTokenListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of quote tokens"},"list":{"type":"array","items":{"$ref":"#/components/schemas/QuoteTokenData"}}}},"QuoteTokenData":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Quote token ID"},"quoteTokenName":{"type":"string","description":"Quote token name"},"quoteTokenAddress":{"type":"string","description":"Quote token contract address"},"ctfExchangeAddress":{"type":"string","description":"CTF Exchange contract address"},"decimal":{"type":"integer","description":"Token decimals"},"symbol":{"type":"string","description":"Token symbol"},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}}}}
```

## The QuoteTokenData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"QuoteTokenData":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Quote token ID"},"quoteTokenName":{"type":"string","description":"Quote token name"},"quoteTokenAddress":{"type":"string","description":"Quote token contract address"},"ctfExchangeAddress":{"type":"string","description":"CTF Exchange contract address"},"decimal":{"type":"integer","description":"Token decimals"},"symbol":{"type":"string","description":"Token symbol"},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}}}}
```

## The PositionsResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"PositionsResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of positions"},"list":{"type":"array","items":{"$ref":"#/components/schemas/PositionData"}}}},"PositionData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"marketStatus":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted"},"marketStatusEnum":{"type":"string","description":"Human-readable market status"},"marketCutoffAt":{"type":"integer","format":"int64","description":"Market cutoff timestamp"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"sharesOwned":{"type":"string","description":"Number of shares owned"},"sharesFrozen":{"type":"string","description":"Number of shares frozen (in pending orders)"},"unrealizedPnl":{"type":"string","description":"Unrealized profit/loss"},"unrealizedPnlPercent":{"type":"string","description":"Unrealized profit/loss percentage"},"dailyPnlChange":{"type":"string","description":"Daily PnL change"},"dailyPnlChangePercent":{"type":"string","description":"Daily PnL change percentage"},"conditionId":{"type":"string","description":"Condition ID"},"tokenId":{"type":"string","description":"Token ID"},"currentValueInQuoteToken":{"type":"string","description":"Current value in quote token (shares × current price)"},"avgEntryPrice":{"type":"string","description":"Average entry price"},"claimStatus":{"type":"integer","description":"Claim status: 0=CanNotClaim, 1=WaitClaim, 2=Claiming, 3=ClaimFailed, 4=Claimed"},"claimStatusEnum":{"type":"string","description":"Human-readable claim status","enum":["CanNotClaim","WaitClaim","Claiming","ClaimFailed","Claimed"]},"quoteToken":{"type":"string","description":"Quote token address"}}}}}}
```

## The PositionData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"PositionData":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"marketStatus":{"type":"integer","description":"Market status: 1=Created, 2=Activated, 3=Resolving, 4=Resolved, 5=Failed, 6=Deleted"},"marketStatusEnum":{"type":"string","description":"Human-readable market status"},"marketCutoffAt":{"type":"integer","format":"int64","description":"Market cutoff timestamp"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"sharesOwned":{"type":"string","description":"Number of shares owned"},"sharesFrozen":{"type":"string","description":"Number of shares frozen (in pending orders)"},"unrealizedPnl":{"type":"string","description":"Unrealized profit/loss"},"unrealizedPnlPercent":{"type":"string","description":"Unrealized profit/loss percentage"},"dailyPnlChange":{"type":"string","description":"Daily PnL change"},"dailyPnlChangePercent":{"type":"string","description":"Daily PnL change percentage"},"conditionId":{"type":"string","description":"Condition ID"},"tokenId":{"type":"string","description":"Token ID"},"currentValueInQuoteToken":{"type":"string","description":"Current value in quote token (shares × current price)"},"avgEntryPrice":{"type":"string","description":"Average entry price"},"claimStatus":{"type":"integer","description":"Claim status: 0=CanNotClaim, 1=WaitClaim, 2=Claiming, 3=ClaimFailed, 4=Claimed"},"claimStatusEnum":{"type":"string","description":"Human-readable claim status","enum":["CanNotClaim","WaitClaim","Claiming","ClaimFailed","Claimed"]},"quoteToken":{"type":"string","description":"Quote token address"}}}}}}
```

## The UserTradeListResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"UserTradeListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of trades"},"list":{"type":"array","items":{"$ref":"#/components/schemas/UserTradeData"}}}},"UserTradeData":{"type":"object","description":"Trade data for querying other users' trades (orderNo and tradeNo are hidden for privacy)","properties":{"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"string","description":"Trade side (BUY/SELL)"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Trade price"},"shares":{"type":"string","description":"Number of shares traded"},"amount":{"type":"string","description":"Trade amount in quote token"},"fee":{"type":"string","description":"Fee amount (human-readable format)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"quoteTokenUsdPrice":{"type":"string","description":"USD price of quote token"},"usdAmount":{"type":"string","description":"Total USD value of this trade"},"status":{"type":"integer","description":"Trade status: 1=Pending, 2=Filled, 3=Canceled, 4=Expired, 5=Failed"},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Filled","Canceled","Expired","Failed"]},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}}}}
```

## The UserTradeData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"UserTradeData":{"type":"object","description":"Trade data for querying other users' trades (orderNo and tradeNo are hidden for privacy)","properties":{"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID (for categorical markets)"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"string","description":"Trade side (BUY/SELL)"},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=Yes, 2=No","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Trade price"},"shares":{"type":"string","description":"Number of shares traded"},"amount":{"type":"string","description":"Trade amount in quote token"},"fee":{"type":"string","description":"Fee amount (human-readable format)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"quoteTokenUsdPrice":{"type":"string","description":"USD price of quote token"},"usdAmount":{"type":"string","description":"Total USD value of this trade"},"status":{"type":"integer","description":"Trade status: 1=Pending, 2=Filled, 3=Canceled, 4=Expired, 5=Failed"},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Filled","Canceled","Expired","Failed"]},"chainId":{"type":"string","description":"Chain ID"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp"}}}}}}
```

## The OrderListResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderListResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of orders"},"list":{"type":"array","items":{"$ref":"#/components/schemas/OrderData"},"description":"List of orders"}}},"OrderData":{"type":"object","description":"Order record (list item or detail; detail includes trades)","properties":{"orderId":{"type":"string","description":"Order ID (transaction number)"},"transNo":{"type":"string","description":"Deprecated; use orderId instead"},"status":{"type":"integer","description":"Order status: 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","enum":[1,2,3,4,5]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Finished","Canceled","Expired","Failed"]},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"integer","description":"Side: 1=buy, 2=sell","enum":[1,2]},"sideEnum":{"type":"string","description":"Human-readable side","enum":["Buy","Sell"]},"tradingMethod":{"type":"integer","description":"Trading method: 1=market, 2=limit"},"tradingMethodEnum":{"type":"string","description":"Human-readable trading method","enum":["Market","Limit"]},"tradingUnit":{"type":"integer","description":"Trading unit: 1=quote, 2=shares. Indicates which side was user-specified when the order was placed.","enum":[1,2]},"tradingUnitEnum":{"type":"string","description":"Human-readable trading unit. `quote` means the user specified quote-token amount; `shares` means the user specified shares quantity.","enum":["quote","shares"]},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=yes, 2=no","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Price per share (for limit orders)"},"orderShares":{"type":"string","description":"Total shares in order (base token). Always expressed in shares regardless of tradingUnit."},"orderAmount":{"type":"string","description":"Total amount in order (quote token). Always expressed in quote-token amount regardless of tradingUnit."},"filledShares":{"type":"string","description":"Filled shares (base token)"},"filledAmount":{"type":"string","description":"Filled amount (quote token)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp (milliseconds)"},"expiresAt":{"type":"integer","format":"int64","description":"Expiration timestamp (milliseconds)"},"postOnly":{"type":"boolean","description":"Optional. Post-only mode for limit orders (default false). When true on place-order, the order must rest as maker; if it would cross the spread it is cancelled (comment: post-only order would cross the spread). Market orders with postOnly=true are rejected with code 10610."},"trades":{"type":"array","items":{"$ref":"#/components/schemas/OrderTradeData"},"description":"Related trades (only present in order detail response)"}}},"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}}}}
```

## The OrderData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderData":{"type":"object","description":"Order record (list item or detail; detail includes trades)","properties":{"orderId":{"type":"string","description":"Order ID (transaction number)"},"transNo":{"type":"string","description":"Deprecated; use orderId instead"},"status":{"type":"integer","description":"Order status: 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","enum":[1,2,3,4,5]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Finished","Canceled","Expired","Failed"]},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"integer","description":"Side: 1=buy, 2=sell","enum":[1,2]},"sideEnum":{"type":"string","description":"Human-readable side","enum":["Buy","Sell"]},"tradingMethod":{"type":"integer","description":"Trading method: 1=market, 2=limit"},"tradingMethodEnum":{"type":"string","description":"Human-readable trading method","enum":["Market","Limit"]},"tradingUnit":{"type":"integer","description":"Trading unit: 1=quote, 2=shares. Indicates which side was user-specified when the order was placed.","enum":[1,2]},"tradingUnitEnum":{"type":"string","description":"Human-readable trading unit. `quote` means the user specified quote-token amount; `shares` means the user specified shares quantity.","enum":["quote","shares"]},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=yes, 2=no","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Price per share (for limit orders)"},"orderShares":{"type":"string","description":"Total shares in order (base token). Always expressed in shares regardless of tradingUnit."},"orderAmount":{"type":"string","description":"Total amount in order (quote token). Always expressed in quote-token amount regardless of tradingUnit."},"filledShares":{"type":"string","description":"Filled shares (base token)"},"filledAmount":{"type":"string","description":"Filled amount (quote token)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp (milliseconds)"},"expiresAt":{"type":"integer","format":"int64","description":"Expiration timestamp (milliseconds)"},"postOnly":{"type":"boolean","description":"Optional. Post-only mode for limit orders (default false). When true on place-order, the order must rest as maker; if it would cross the spread it is cancelled (comment: post-only order would cross the spread). Market orders with postOnly=true are rejected with code 10610."},"trades":{"type":"array","items":{"$ref":"#/components/schemas/OrderTradeData"},"description":"Related trades (only present in order detail response)"}}},"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}}}}
```

## The OrderDetailResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderDetailResponse":{"type":"object","description":"Order detail response (includes orderData with trades)","properties":{"orderData":{"$ref":"#/components/schemas/OrderData"}}},"OrderData":{"type":"object","description":"Order record (list item or detail; detail includes trades)","properties":{"orderId":{"type":"string","description":"Order ID (transaction number)"},"transNo":{"type":"string","description":"Deprecated; use orderId instead"},"status":{"type":"integer","description":"Order status: 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed","enum":[1,2,3,4,5]},"statusEnum":{"type":"string","description":"Human-readable status","enum":["Pending","Finished","Canceled","Expired","Failed"]},"marketId":{"type":"integer","format":"int64","description":"Market ID"},"marketTitle":{"type":"string","description":"Market title"},"rootMarketId":{"type":"integer","format":"int64","description":"Root market ID"},"rootMarketTitle":{"type":"string","description":"Root market title"},"side":{"type":"integer","description":"Side: 1=buy, 2=sell","enum":[1,2]},"sideEnum":{"type":"string","description":"Human-readable side","enum":["Buy","Sell"]},"tradingMethod":{"type":"integer","description":"Trading method: 1=market, 2=limit"},"tradingMethodEnum":{"type":"string","description":"Human-readable trading method","enum":["Market","Limit"]},"tradingUnit":{"type":"integer","description":"Trading unit: 1=quote, 2=shares. Indicates which side was user-specified when the order was placed.","enum":[1,2]},"tradingUnitEnum":{"type":"string","description":"Human-readable trading unit. `quote` means the user specified quote-token amount; `shares` means the user specified shares quantity.","enum":["quote","shares"]},"outcome":{"type":"string","description":"Outcome label"},"outcomeSide":{"type":"integer","description":"Outcome side: 1=yes, 2=no","enum":[1,2]},"outcomeSideEnum":{"type":"string","description":"Human-readable outcome side","enum":[true,false]},"price":{"type":"string","description":"Price per share (for limit orders)"},"orderShares":{"type":"string","description":"Total shares in order (base token). Always expressed in shares regardless of tradingUnit."},"orderAmount":{"type":"string","description":"Total amount in order (quote token). Always expressed in quote-token amount regardless of tradingUnit."},"filledShares":{"type":"string","description":"Filled shares (base token)"},"filledAmount":{"type":"string","description":"Filled amount (quote token)"},"profit":{"type":"string","description":"Profit/loss"},"quoteToken":{"type":"string","description":"Quote token address"},"createdAt":{"type":"integer","format":"int64","description":"Creation timestamp (milliseconds)"},"expiresAt":{"type":"integer","format":"int64","description":"Expiration timestamp (milliseconds)"},"postOnly":{"type":"boolean","description":"Optional. Post-only mode for limit orders (default false). When true on place-order, the order must rest as maker; if it would cross the spread it is cancelled (comment: post-only order would cross the spread). Market orders with postOnly=true are rejected with code 10610."},"trades":{"type":"array","items":{"$ref":"#/components/schemas/OrderTradeData"},"description":"Related trades (only present in order detail response)"}}},"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}}}}
```

## The OrderTradeData object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"OrderTradeData":{"type":"object","description":"Trade record within an order detail","properties":{"orderNo":{"type":"string","description":"Order number"},"tradeNo":{"type":"string","description":"Trade number"},"txHash":{"type":"string","description":"Transaction hash"},"marketId":{"type":"integer","format":"int64"},"marketTitle":{"type":"string"},"rootMarketId":{"type":"integer","format":"int64"},"rootMarketTitle":{"type":"string"},"side":{"type":"string"},"outcome":{"type":"string"},"outcomeSide":{"type":"integer","enum":[1,2]},"outcomeSideEnum":{"type":"string","enum":[true,false]},"price":{"type":"string"},"shares":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"integer","format":"int64","description":"Fee in wei"},"feeFormatted":{"type":"string","description":"Human-readable fee"},"profit":{"type":"string"},"quoteToken":{"type":"string"},"quoteTokenUsdPrice":{"type":"string"},"usdAmount":{"type":"string"},"status":{"type":"integer"},"statusEnum":{"type":"string"},"chainId":{"type":"string"},"createdAt":{"type":"integer","format":"int64"}}}}}}
```

## The ResolutionAPIBaseResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}}}}}
```

## The ResolutionMarketRow object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}}}}}
```

## The ResolutionMarketsResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionMarketsResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of markets for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionMarketRow"}}}},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}}}}}
```

## The ResolutionDisputeParams object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionDisputeParams":{"type":"object","nullable":true,"description":"Everything needed to send dispute/claim transactions, plus the state of the current dispute. Null for price-oracle records (nothing to dispute).","properties":{"chainId":{"type":"string","description":"Chain id"},"disputeResolver":{"type":"string","description":"Contract to call `dispute` / `claim` / `isDisputable` on for this market — always take the address from here"},"requiredStake":{"type":"string","nullable":true,"description":"Stake required to dispute (display units)"},"stakeToken":{"type":"string","description":"ERC-20 address of the stake token — `approve` it to `disputeResolver` before disputing"},"stakeTokenSymbol":{"type":"string"},"stakeTokenDecimals":{"type":"integer","description":"Stake token decimals — use with `requiredStake` to build the raw approve amount"},"disputor":{"type":"string","nullable":true,"description":"Wallet that filed the dispute; null if none"},"reason":{"type":"integer","nullable":true,"description":"Dispute reason as filed: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason (`WrongResult` / `TooEarly`); `\"\"` if never disputed"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set once finalized"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake — `not_applicable` (no dispute, dispute rejected, or not yet finalized) / `claimable` (dispute won, unclaimed) / `claimed`"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of finalization"},"claimTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the stake claim"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `disputeOutcome`, not the deadline"}}}}}}
```

## The ResolutionTimelineStage object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionTimelineStage":{"type":"object","description":"One tx-anchored stage of the resolution timeline","properties":{"stage":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Stage name, always in this order"},"at":{"type":"integer","format":"int64","nullable":true,"description":"Unix time of the stage's transaction; null if not reached"},"txHash":{"type":"string","nullable":true,"description":"Transaction hash anchoring the stage; null if not reached"}}}}}}
```

## The ResolutionMarketDetail object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionMarketDetail":{"allOf":[{"$ref":"#/components/schemas/ResolutionMarketRow"},{"type":"object","properties":{"rules":{"type":"string","description":"This market's resolution rules — the criteria the outcome is judged against"},"parentRules":{"type":"string","nullable":true,"description":"Parent event's rules for categorical child markets (read both when evaluating a proposal); null for binary markets"},"dispute":{"$ref":"#/components/schemas/ResolutionDisputeParams"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionTimelineStage"},"description":"Fixed three stages (`proposed` / `disputed` / `finalized`), each anchored to its on-chain transaction; stages not reached are null. Records indexed before tx tracking was introduced may show null `at` / `txHash` for stages that did occur"}}}]},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}},"ResolutionDisputeParams":{"type":"object","nullable":true,"description":"Everything needed to send dispute/claim transactions, plus the state of the current dispute. Null for price-oracle records (nothing to dispute).","properties":{"chainId":{"type":"string","description":"Chain id"},"disputeResolver":{"type":"string","description":"Contract to call `dispute` / `claim` / `isDisputable` on for this market — always take the address from here"},"requiredStake":{"type":"string","nullable":true,"description":"Stake required to dispute (display units)"},"stakeToken":{"type":"string","description":"ERC-20 address of the stake token — `approve` it to `disputeResolver` before disputing"},"stakeTokenSymbol":{"type":"string"},"stakeTokenDecimals":{"type":"integer","description":"Stake token decimals — use with `requiredStake` to build the raw approve amount"},"disputor":{"type":"string","nullable":true,"description":"Wallet that filed the dispute; null if none"},"reason":{"type":"integer","nullable":true,"description":"Dispute reason as filed: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason (`WrongResult` / `TooEarly`); `\"\"` if never disputed"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set once finalized"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake — `not_applicable` (no dispute, dispute rejected, or not yet finalized) / `claimable` (dispute won, unclaimed) / `claimed`"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of finalization"},"claimTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the stake claim"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `disputeOutcome`, not the deadline"}}},"ResolutionTimelineStage":{"type":"object","description":"One tx-anchored stage of the resolution timeline","properties":{"stage":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Stage name, always in this order"},"at":{"type":"integer","format":"int64","nullable":true,"description":"Unix time of the stage's transaction; null if not reached"},"txHash":{"type":"string","nullable":true,"description":"Transaction hash anchoring the stage; null if not reached"}}}}}}
```

## The ResolutionDisputeRow object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionDisputeRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"disputor":{"type":"string","description":"Wallet that filed this dispute"},"reason":{"type":"integer","description":"Dispute reason: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"stake":{"type":"string","description":"Amount actually staked for this dispute (display units)"},"stakeTokenSymbol":{"type":"string"},"proposedPayouts":{"type":"array","items":{"type":"string"},"description":"The original proposal that was disputed, as the payout vector `[yes, no]`"},"proposedTokenId":{"type":"string","description":"Proposed winning token id of the disputed proposal (`\"draw\"` for a tie)"},"resultTokenId":{"type":"string","description":"Final outcome; `\"\"` while review is in progress"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Null while review is in progress"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","description":"Transaction hash of finalization; `\"\"` while review is in progress"},"claimTxHash":{"type":"string","description":"Transaction hash of the stake claim; `\"\"` if not claimed"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline of this dispute"}}}}}}
```

## The ResolutionDisputesResponse object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"ResolutionDisputesResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of disputes for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionDisputeRow"}}}},"ResolutionDisputeRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"disputor":{"type":"string","description":"Wallet that filed this dispute"},"reason":{"type":"integer","description":"Dispute reason: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"stake":{"type":"string","description":"Amount actually staked for this dispute (display units)"},"stakeTokenSymbol":{"type":"string"},"proposedPayouts":{"type":"array","items":{"type":"string"},"description":"The original proposal that was disputed, as the payout vector `[yes, no]`"},"proposedTokenId":{"type":"string","description":"Proposed winning token id of the disputed proposal (`\"draw\"` for a tie)"},"resultTokenId":{"type":"string","description":"Final outcome; `\"\"` while review is in progress"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Null while review is in progress"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","description":"Transaction hash of finalization; `\"\"` while review is in progress"},"claimTxHash":{"type":"string","description":"Transaction hash of the stake claim; `\"\"` if not claimed"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline of this dispute"}}}}}}
```

## The DisputeRationaleRequest object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"DisputeRationaleRequest":{"type":"object","required":["rationale"],"properties":{"rationale":{"type":"string","description":"Public statement explaining the dispute — must equal the signed `rationale`; at most 2000 bytes (UTF-8). Surrounding whitespace is stripped server-side"}}}}}}
```

## The DisputeRationaleResult object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"DisputeRationaleResult":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}
```

## The MarketResolutionSummary object

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"components":{"schemas":{"MarketResolutionSummary":{"type":"object","description":"Resolution lifecycle snapshot. Omitted while no resolution proposal is visible for the market (and for categorical parents). Full lifecycle detail is on the Resolution endpoints.","properties":{"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Current resolution phase"},"proposedTokenId":{"type":"string","description":"Token ID the proposal resolves as the winner (\"draw\" for a tie proposal)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Unix seconds; end of the dispute window for the proposal. Null when not applicable (e.g. auto-resolved markets)"}}}}}}
```


# Opinion Websocket


# Overview

## Opinion Websocket

Welcome to the official documentation for the Opinion WebSocket API — a real-time, event-driven interface for accessing live market data in OPINION Prediction Markets.

> 📊 **Public Websocket Data API**: This Websocket API provides live, streaming read-only access to market data, orderbooks, and price information. For trading operations (placing orders, managing positions), please use the [Opinion CLOB SDK](https://github.com/opinion-labs/opinion-clob-sdk).&#x20;
>
> To request API access, Please kindly fill out this [short application form ](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII).&#x20;
>
> *API Key can be used for Opinion OpenAPI, Opinion Websocket, and Opinion CLOB SDK*

### What is Opinion Websocket?

The Opinion WebSocket API provides a persistent connection that pushes updates in real time from Opinion prediction market. Unlike RESTful polling, WebSockets ensure developers receive data instantly when changes occur, reducing latency and network overhead - ideal for live dashboards, trading engines, and analytics:

* **Subscribe to market price streams** — Receive ticks and trade updates as they happen
* **Monitor orderbook changes** — Get bid/ask book deltas in near real-time
* **Receive market events** — Market activation, resolution, or status transitions
* **Track aggregated metrics** — Volume or event summaries streamed live

### Key Features

**Real-Time Streaming**

* Persistent WebSocket connection for low-latency updates
* Event-driven feeds eliminate the need for repeated polling
* Efficient delivery of high-frequency data

**Subscription Model**

* Subscribe to specific markets, tokens, or event types
* Receive targeted updates to minimize noise and bandwidth

**Secure & Compatible**

* API Key authentication on initial handshake
* TLS/SSL encrypted transport
* Works across languages and platforms with standard WebSocket clients

### Use Cases

**Live Market Dashboards**

Build interactive UIs that update instantly with trades, prices, and orderbook moves.

**Automated Trading Clients**

Feed real-time market data into trading algorithms, bots, or signal engines.

**Analytics & Alerting**

Trigger custom alerts when price thresholds, spread changes, or market events occur.

### How It Works

1. **Connect to the WebSocket endpoint** with your API Key.
2. **Authenticate and subscribe** to one or more topics (markets, tokens, events).
3. **Receive streaming messages** as soon as updates occur.
4. **Parse and act** on events in your application.


# Quickstart

## Authentication

To use Opinion Websocket, establish a `wss` connection to `wss://ws.opinion.trade` with your `apikey` as query parameters:

```
wss://ws.opinion.trade?apikey={API_KEY}
```

## Maintain connection

To maintain connection, send a HEARTBEAT message (e.g. every 30 seconds) to keep it open.

```
{"action":"HEARTBEAT"}
```

## Subscribe

To subscribe a channel, send a `SUBSCRIBE` message, for example:

```
For Binary market:
{"action":"SUBSCRIBE","channel":"{CHANNEL}","marketId":{MARKET_ID}}
For Categorical market:
{"action":"SUBSCRIBE","channel":"{CHANNEL}","rootMarketId":{ROOT_MARKET_ID}}
```

The exact requied fields (e.g. `marketId` or `rootMarketId`) depend on the channel to be subscribed.

To unsubscribe a channel, send an UNSUBSCRIBE message with the same parameters:

```
For Binary market:
{"action":"UNSUBSCRIBE","channel":"{CHANNEL}","marketId":{MARKET_ID}}
For Categorical market:
{"action":"UNSUBSCRIBE","channel":"{CHANNEL}","rootMarketId":{ROOT_MARKET_ID}}
```


# User Channels

## Order Update

### Subscribe

Message will be sent once your order in this market has an update (new/cancel/match/confirm).

{% hint style="warning" %}
Please note that the matched trade does not guarantee successful execution on-chain.

And the final on-chain amount/share may vary if fee applied. For the accurate on-chain amount/share, please subscribe Trade Executed channel.
{% endhint %}

<table><thead><tr><th width="150">Field</th><th width="200">Value</th><th>Description</th></tr></thead><tbody><tr><td>channel</td><td>trade.order.update</td><td>Channel of user order update </td></tr><tr><td>marketId</td><td>{MARKET_ID}</td><td>MarketId of subscribed binary market</td></tr><tr><td>rootMarketId</td><td>{ROOT_MARKET_ID}</td><td>MarketId of subscribed categorical market</td></tr></tbody></table>

{% code title="Example" %}

```
For Binary market:
{"action":"SUBSCRIBE","channel":"trade.order.update","marketId":1274}
For Categorical market:
{"action":"SUBSCRIBE","channel":"trade.order.update","rootMarketId":61}
```

{% endcode %}

### Structure

<table><thead><tr><th width="170">Name</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>orderUpdateType</td><td>string</td><td>orderNew | orderFill | orderCancel | orderConfirm</td></tr><tr><td>marketId</td><td>number</td><td>market id</td></tr><tr><td>rootMarketId</td><td>number</td><td>root market id if belongs to a categorical market</td></tr><tr><td>orderId</td><td>string</td><td>order id</td></tr><tr><td>side</td><td>number</td><td>1 - buy, 2 - sell</td></tr><tr><td>outcomeSide</td><td>number</td><td>1 - yes, 2 - no</td></tr><tr><td>price</td><td>string</td><td>price</td></tr><tr><td>shares</td><td>string</td><td>amount of conditional token (e.g. "Yes","No")</td></tr><tr><td>amount</td><td>string</td><td>amount of quote token</td></tr><tr><td>status</td><td>number</td><td>1 - pending, 2 - finished, 3 - canceled, 4 - expired, 5 - failed</td></tr><tr><td>tradingMethod</td><td>number</td><td>1 - market order, 2 - limit order</td></tr><tr><td>quoteToken</td><td>string</td><td>contract address of quote token</td></tr><tr><td>createdAt</td><td>number</td><td>create unix timestamp</td></tr><tr><td>expiresAt</td><td>number</td><td>expire unix timestamp</td></tr><tr><td>chainId</td><td>string</td><td>chain id</td></tr><tr><td>filledShares</td><td>string</td><td>filled in shares, update after order confirmed on chain</td></tr><tr><td>filledAmount</td><td>string</td><td>filled in amount, update after order confirmed on chain</td></tr></tbody></table>

{% code title="Sample message" expandable="true" %}

```json
{
  "orderUpdateType": "orderConfirm",
  "marketId": 2770,
  "rootMarketId": 122,
  "orderId": "a11ee07e-e22f-11f0-9714-0a58a9feac02",
  "side": 1,
  "outcomeSide": 1,
  "price": "0.150000000000000000",
  "shares": "66.66",
  "amount": "9.999000000000000000",
  "status": 1,
  "tradingMethod": 2,
  "quoteToken": "0x55d398326f99059fF775485246999027B3197955",
  "createdAt": 1766735464,
  "expiresAt": 0,
  "chainId": "56",
  "filledShares": "10.000000000000000000",
  "filledAmount": "1.500000000000000000",
  "msgType": "trade.order.update"
}
```

{% endcode %}

## Trade Executed

### Subscribe

Message will be sent once your trade (matched order) has been confirmed on-chain, or a split/merge has been executed on-chain. Same order can have multiple fills that ends up multiple trades.

<table><thead><tr><th width="150">Field</th><th width="200">Value</th><th>Description</th></tr></thead><tbody><tr><td>channel</td><td>trade.record.new</td><td>Channel of user trade notice</td></tr><tr><td>marketId</td><td>{MARKET_ID}</td><td>MarketId of subscribed binary market</td></tr><tr><td>rootMarketId</td><td>{ROOT_MARKET_ID}</td><td>MarketId of subscribed categorical market</td></tr></tbody></table>

{% code title="Example" %}

```
For Binary market:
{"action":"SUBSCRIBE","channel":"trade.record.new","marketId":1274}
For Categorical market:
{"action":"SUBSCRIBE","channel":"trade.record.new","rootMarketId":61}
```

{% endcode %}

### Structure

<table><thead><tr><th width="185">Name</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>orderId</td><td>string</td><td>order id, same order can have multiple fills that ends up multiple trades</td></tr><tr><td>txHash</td><td>string</td><td>transaction hash on-chain, each trade has a unique txHash</td></tr><tr><td>marketId</td><td>number</td><td>market id</td></tr><tr><td>rootMarketId</td><td>number</td><td>root market id if belongs to a categorical market</td></tr><tr><td>side</td><td>string</td><td>Buy | Sell | Split | Merge</td></tr><tr><td>outcomeSide</td><td>number</td><td>1 - yes, 2 - no</td></tr><tr><td>price</td><td>string</td><td>price</td></tr><tr><td>shares</td><td>string</td><td>amount of conditional token (e.g. "Yes","No")</td></tr><tr><td>amount</td><td>string</td><td>amount of quote token</td></tr><tr><td>profit</td><td>string</td><td>realized profit in usd value, applicable for sell/merge</td></tr><tr><td>status</td><td>number</td><td>2 - finished, 3 - canceled, 5 - failed, 6 - onchain failed</td></tr><tr><td>quoteToken</td><td>string</td><td>contract address of quote token</td></tr><tr><td>quoteTokenUsdPrice</td><td>string</td><td>USD price of quote token at the moment</td></tr><tr><td>usdAmount</td><td>string</td><td>order value in USD value</td></tr><tr><td>fee</td><td>string</td><td>fee applied to this trade</td></tr><tr><td>chainId</td><td>string</td><td>chain id</td></tr><tr><td>createdAt</td><td>number</td><td>create unix timestamp</td></tr><tr><td>tradeNo</td><td>string</td><td>trade id for reference</td></tr></tbody></table>

{% code title="Sample message" expandable="true" %}

```json
{
  "orderId": "3c7af25f-e21f-11f0-9714-0a58a9feac02",
  "tradeNo": "e1403840-e22f-11f0-83af-0a58a9feac02",
  "marketId": 2770,
  "rootMarketId": 122,
  "txHash": "0x272c8d9b8f90f50564173cf624c0ac5a371978b72bcd12604b26312a27e24195",
  "side": "Buy",
  "outcomeSide": 2,
  "price": "0.100000000000000000",
  "shares": "9.44444",
  "amount": "0.944444",
  "profit": "0.000000000000000000",
  "status": 2,
  "quoteToken": "0x55d398326f99059fF775485246999027B3197955",
  "quoteTokenUsdPrice": "1.000000000000000000",
  "usdAmount": "1000000.000000000000000000",
  "fee": "0.000000000000000000",
  "chainId": "56",
  "createdAt": 1766735571,
  "msgType": "trade.record.new"
}
```

{% endcode %}


# Market Channels

## Orderbook Change

### Subscribe

Message will be sent once the orderbook has any change (new/cancel/match order).

<table><thead><tr><th width="150">Field</th><th width="200">Value</th><th>Description</th></tr></thead><tbody><tr><td>channel</td><td>market.depth.diff</td><td>Channel of orderbook change</td></tr><tr><td>marketId</td><td>{MARKET_ID}</td><td>MarketId of subscribed market</td></tr></tbody></table>

{% hint style="info" %}
Orderbook Change applied to a single binary market only.

For categorical market, you should subscribe each `market_id` individually.
{% endhint %}

{% code title="Example" %}

```
{"action":"SUBSCRIBE","channel":"market.depth.diff","marketId":1274}
```

{% endcode %}

### Structure

<table><thead><tr><th width="180">Name</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>marketId</td><td>number</td><td>market id</td></tr><tr><td>rootMarketId</td><td>number</td><td>root market id if belongs to a categorical market</td></tr><tr><td>tokenId</td><td>string</td><td>token id of updated conditional token</td></tr><tr><td>outcomeSide</td><td>number</td><td>1 - yes, 2 - no</td></tr><tr><td>side</td><td>string</td><td>bids | asks</td></tr><tr><td>price</td><td>string</td><td>price</td></tr><tr><td>size</td><td>string</td><td>shares of conditional tokens</td></tr></tbody></table>

<pre class="language-json" data-title="Sample message" data-expandable="true"><code class="lang-json">{
<strong>    "marketId": 2764, 
</strong>    "tokenId": "19120407572139442221452465677574895365338028945317996490376653704877573103648", 
    "outcomeSide": 1, 
    "side": "bids", 
    "price": "0.2", 
    "size": "50", 
    "msgType": "market.depth.diff"
}
</code></pre>

## Market Price Change

Message will be sent once the latest match price has changed.

### Subscribe

<table><thead><tr><th width="150">Field</th><th width="200">Value</th><th>Description</th></tr></thead><tbody><tr><td>channel</td><td>market.last.price</td><td>Channel of market price change </td></tr><tr><td>marketId</td><td>{MARKET_ID}</td><td>MarketId of subscribed binary market</td></tr><tr><td>rootMarketId</td><td>{ROOT_MARKET_ID}</td><td>MarketId of subscribed categorical market</td></tr></tbody></table>

{% hint style="info" %}
If `rootMarketId` is defined, `marketId` will be omitted.

Subscribing root market will receive messages of all of its sub-markets.
{% endhint %}

<pre data-title="Example"><code><strong>For Binary market:
</strong>{"action":"SUBSCRIBE","channel":"market.last.price","marketId":1274}
For Categorical market:
{"action":"SUBSCRIBE","channel":"market.last.price","rootMarketId":61}
</code></pre>

### Structure

<table><thead><tr><th width="180">Name</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>marketId</td><td>number</td><td>market id</td></tr><tr><td>rootMarketId</td><td>number</td><td>root market id if belongs to a categorical market</td></tr><tr><td>tokenId</td><td>string</td><td>token id of updated conditional token</td></tr><tr><td>price</td><td>string</td><td>price</td></tr><tr><td>outcomeSide</td><td>number</td><td>1 - yes, 2 - no</td></tr></tbody></table>

{% code title="Sample message" expandable="true" %}

```json
{
    "tokenId": "19120407572139442221452465677574895365338028945317996490376653704877573103648", 
    "outcomeSide": 1, 
    "price": "0.85", 
    "marketId": 2764, 
    "msgType": "market.last.price"
}
```

{% endcode %}

## Market Last Trade

### Subscribe

Message will be sent once a trade matched in this market.

{% hint style="warning" %}
Please note that the matched trade does not guarantee successful execution on-chain.

And the final on-chain amount/share may vary if fee applied.
{% endhint %}

<table><thead><tr><th width="150">Field</th><th width="200">Value</th><th>Description</th></tr></thead><tbody><tr><td>channel</td><td>market.last.trade</td><td>Channel of market last trade </td></tr><tr><td>marketId</td><td>{MARKET_ID}</td><td>MarketId of subscribed binary market</td></tr><tr><td>rootMarketId</td><td>{ROOT_MARKET_ID}</td><td>MarketId of subscribed categorical market</td></tr></tbody></table>

{% hint style="info" %}
If `rootMarketId` is defined, `marketId` will be omitted.

Subscribing root market will receive messages of all of its sub-markets.
{% endhint %}

{% code title="Example" %}

```
For Binary market:
{"action":"SUBSCRIBE","channel":"market.last.trade","marketId":1274}
For Categorical market:
{"action":"SUBSCRIBE","channel":"market.last.trade","rootMarketId":61}
```

{% endcode %}

### Structure

<table><thead><tr><th width="180">Name</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>marketId</td><td>number</td><td>market id</td></tr><tr><td>rootMarketId</td><td>number</td><td>root market id if belongs to a categorical market</td></tr><tr><td>tokenId</td><td>string</td><td>token id of updated conditional token</td></tr><tr><td>side</td><td>string</td><td>Buy | Sell | Split | Merge</td></tr><tr><td>outcomeSide</td><td>number</td><td>1 - yes, 2 - no</td></tr><tr><td>price</td><td>string</td><td>price</td></tr><tr><td>shares</td><td>string</td><td>amount of conditional token</td></tr><tr><td>amount</td><td>string</td><td>amount of quote token</td></tr></tbody></table>

{% code title="Sample message" expandable="true" %}

```json
{
    "tokenId": "19120407572139442221452465677574895365338028945317996490376653704877573103648", 
    "side": "Buy", 
    "outcomeSide": 1, 
    "price": "0.85", 
    "shares": "10", 
    "amount": "8.5", 
    "marketId": 2764, 
    "msgType": "market.last.trade"
}
```

{% endcode %}


# Opinion CLOB Python SDK


# Overview

## Opinion CLOB SDK

Welcome to the official documentation for the **Opinion CLOB Python SDK** - a Python library for interacting with Opinion Labs' prediction markets via the Central Limit Order Book (CLOB) API.

> 🔬 **Technical Preview**: Version 0.4.1 features BNB Chain support. While fully functional and tested, we recommend thorough testing before production use.
>
> To request SDK/API access, Please kindly fill out this [short application form ](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII).&#x20;
>
> *API Key can be used for Opinion OpenAPI, Opinion Websocket, and Opinion CLOB SDK*

### What is Opinion CLOB SDK?

The Opinion CLOB SDK provides a Python interface for building applications on top of Opinion prediction market infrastructure. It enables developers to:

* **Query market data** - Access real-time market information, prices, and orderbooks
* **Execute trades** - Place market and limit orders with EIP712 signing
* **Manage positions** - Track balances, positions, and trading history
* **Interact with smart contracts** - Split, merge, and redeem tokens on BNB Chain blockchain

### Key Features

#### Production-Ready

* **Type-safe** - Full type hints and Pythonic naming conventions
* **Well-tested** - test suite with 95%+ coverage
* **Reliable** - Built on industry-standard libraries (Web3.py, eth-account)
* **Documented** - Extensive documentation with examples

#### Performance Optimized

* **Smart caching** - Configurable TTL for market data and quote tokens
* **Batch operations** - Place or cancel multiple orders efficiently
* **Gas optimization** - Minimal on-chain transactions

#### Secure by Design

* **EIP712 signing** - Industry-standard typed data signatures
* **Multi-sig support** - Gnosis Safe integration for institutional users
* **Private key safety** - Keys never leave your environment

#### Blockchain Support

* **BNB Chain Mainnet** (Chain ID: 56)

### Use Cases

#### Trading Applications

Build automated trading bots, market-making applications, or custom trading interfaces.

```python
from opinion_clob_sdk import Client
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide

client = Client(host='https://proxy.opinion.trade:8443', apikey='your_key', ...)

# Place a limit order
order = PlaceOrderDataInput(
    marketId=123,
    tokenId='token_yes',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.55',
    makerAmountInQuoteToken=100
)
result = client.place_order(order)
```

#### Market Analytics

Aggregate and analyze market data for research or monitoring dashboards.

```python
# Get all active markets
markets = client.get_markets(status=TopicStatusFilter.ACTIVATED, limit=100)

# Analyze orderbook depth
orderbook = client.get_orderbook(token_id='token_123')
print(f"Best bid: {orderbook.bids[0]['price']}")
print(f"Best ask: {orderbook.asks[0]['price']}")
```

#### Portfolio Management

Track positions and balances across multiple markets.

```python
# Get user positions
positions = client.get_my_positions(limit=50)

# Get balances
balances = client.get_my_balances()

# Get trade history
trades = client.get_my_trades(market_id=123)
```

### Architecture

The Opinion CLOB SDK is built with a modular architecture:

```
┌─────────────────────────────────────────────┐
│          Application Layer                  │
│         (Your Python Code)                  │
└──────────────┬──────────────────────────────┘
               │
┌──────────────▼──────────────────────────────┐
│         Opinion CLOB SDK                    │
│  ┌──────────────┐   ┌─────────────────┐     │
│  │ Client API   │   │ Contract Caller │     │
│  │ (REST)       │   │ (Blockchain)    │     │
│  └──────┬───────┘   └──────────┬──────┘     │
└─────────┼──────────────────────┼────────────┘
          │                      │
┌─────────▼──────────┐  ┌───────-▼───────────┐
│  Opinion API       │  │     Blockchain     │
│  (CLOB Exchange)   │  │  (Smart Contracts) │
└────────────────────┘  └────────────────────┘
```

### Quick Links

* [📦 Installation Guide](/developer-guide/opinion-clob-python-sdk/getting-started/installation)
* [⚡ Quick Start](/developer-guide/opinion-clob-python-sdk/getting-started/quick-start)
* [🧠 Core Concepts](/developer-guide/opinion-clob-python-sdk/core-concepts)
* [📚 API Reference](/developer-guide/opinion-clob-python-sdk/api-references)
* [❓ FAQ](/developer-guide/opinion-clob-python-sdk/support/faq)

***

Ready to get started? Head to the Installation Guide to begin building with Opinion CLOB SDK!


# Getting Started


# Installation

This guide will help you install the Opinion CLOB SDK and its dependencies.

<https://pypi.org/project/opinion-clob-sdk/>

### Requirements

#### Python Version

* **Python 3.9.10 or higher**&#x20;

Check your Python version:

```bash
python --version  # or python3 --version
```

#### System Requirements

* **Operating Systems**: Linux, macOS, Windows
* **Network**: Internet connection for API access and blockchain RPC
* **Optional**: Git (for development installation)

### Installation Methods

#### Install from PyPI (Recommended)

The simplest way to install the Opinion CLOB SDK is via pip:

```bash
pip install opinion_clob_sdk
```

This will install the latest stable version and all required dependencies.

### Dependencies

The SDK automatically installs the following dependencies:

### Verify Installation

After installation, verify it works:

```python
import opinion_clob_sdk

# Check version
print(opinion_clob_sdk.__version__)  # Should print: 0.1.0 or higher

# Import main classes
from opinion_clob_sdk import Client
from opinion_clob_sdk.model import TopicType, TopicStatus

print("✓ Opinion CLOB SDK installed successfully!")
```

Or run from command line:

```bash
python -c "import opinion_clob_sdk; print('✓ Installed:', opinion_clob_sdk.__version__)"
```

### Virtual Environment (Recommended)

It's best practice to use a virtual environment:

#### Using venv (Built-in)

```bash
# Create virtual environment
python3 -m venv venv

# Activate it
source venv/bin/activate  # macOS/Linux
# or
venv\Scripts\activate     # Windows

# Install SDK
pip install opinion_clob_sdk

# When done, deactivate
deactivate
```

#### Using conda

```bash
# Create environment
conda create -n opinion python=3.11

# Activate it
conda activate opinion

# Install SDK
pip install opinion_clob_sdk

# When done, deactivate
conda deactivate
```

### Upgrading

To upgrade to the latest version:

```bash
pip install --upgrade opinion_clob_sdk
```

To upgrade all dependencies as well:

```bash
pip install --upgrade --force-reinstall opinion_clob_sdk
```

### Uninstalling

To remove the SDK:

```bash
pip uninstall opinion_clob_sdk
```

### Next Steps

Once installed, proceed to:

1. [Quick Start Guide - Build your first application](/developer-guide/opinion-clob-python-sdk/getting-started/quick-start)
2. [Configuration - Set up API keys and credentials](/developer-guide/opinion-clob-python-sdk/getting-started/configuration)
3. [API Reference - Explore available methods](/developer-guide/opinion-clob-python-sdk/api-references)

***

**Having issues?** Check the Troubleshooting Guide or FAQ.


# Quick Start

Get up and running with the Opinion CLOB SDK in minutes. This guide will walk you through your first integration.

### Prerequisites

Before starting, ensure you have:

1. **Python 3.9.10+** installed
2. **Opinion CLOB SDK** installed (Installation Guide)
3. **API credentials** from Opinion Labs:
   * API Key
   * Private Key (for signing orders)
   * Multi-sig wallet address (create on <https://app.opinion.trade>)
   * RPC URL (BNB Chain mainnet)

> **Need credentials?** Fill out this [short application form](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII) to get your API key.

### 5-Minute Quickstart

#### Step 1: Set Up Environment

Create a `.env` file in your project directory:

```bash
# .env file
API_KEY=your_api_key_here
RPC_URL=https://bsc-dataseed.binance.org
PRIVATE_KEY=0x1234567890abcdef...
MULTI_SIG_ADDRESS=0xYourWalletAddress...
HOST=https://proxy.opinion.trade:8443
CHAIN_ID=56
CONDITIONAL_TOKEN_ADDR=0xAD1a38cEc043e70E83a3eC30443dB285ED10D774
MULTISEND_ADDR=0x998739BFdAAdde7C933B942a68053933098f9EDa
```

#### Step 2: Initialize the Client

Create a new Python file (`my_first_app.py`):

```python
import os
from dotenv import load_dotenv
from opinion_clob_sdk import Client

# Load environment variables
load_dotenv()

# Initialize client
client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey=os.getenv('API_KEY'),
    chain_id=56,  # BNB Chain mainnet
    rpc_url=os.getenv('RPC_URL'),
    private_key=os.getenv('PRIVATE_KEY'),
    multi_sig_addr=os.getenv('MULTI_SIG_ADDRESS'),
    conditional_tokens_addr=os.getenv('CONDITIONAL_TOKEN_ADDR'),
    multisend_addr=os.getenv('0x998739BFdAAdde7C933B942a68053933098f9EDa')
)

print("✓ Client initialized successfully!")
```

#### Step 3: Fetch Market Data

Add market data fetching:

```python
from opinion_clob_sdk.model import TopicStatusFilter

# Get all active markets
markets_response = client.get_markets(
    status=TopicStatusFilter.ACTIVATED,
    page=1,
    limit=10
)

# Parse the response
if markets_response.errno == 0:
    markets = markets_response.result.list
    print(f"\n✓ Found {len(markets)} active markets:")

    for market in markets[:3]:  # Show first 3
        print(f"  - Market #{market.market_id}: {market.market_title}")
        print(f"    Status: {market.status}")
        print()
else:
    print(f"Error: {markets_response.errmsg}")
```

#### Step 4: Get Market Details

```python
# Get details for a specific market
market_id = markets[0].topic_id  # Use first market from above

market_detail = client.get_market(market_id)
if market_detail.errno == 0:
    market = market_detail.result.data
    print(f"\n✓ Market Details for #{market_id}:")
    print(f"  Title: {market.market_title}")
    print(f"  Question ID: {market.question_id}")
    print(f"  Quote Token: {market.quote_token}")
    print(f"  Chain ID: {market.chain_id}")
```

#### Step 5: Check Orderbook

```python
# Assuming the market has a token (get from market.options for binary markets)
# For this example, we'll use a placeholder token_id
token_id = "your_token_id_here"  # Replace with actual token ID

try:
    orderbook = client.get_orderbook(token_id)
    if orderbook.errno == 0:
        book = orderbook.result.data
        print(f"\n✓ Orderbook for token {token_id}:")
        print(f"  Best Bid: {book.bids[0] if book.bids else 'No bids'}")
        print(f"  Best Ask: {book.asks[0] if book.asks else 'No asks'}")
except Exception as e:
    print(f"  (Skip if token_id not set: {e})")
```

#### Complete Example

Here's the complete `my_first_app.py`:

```python
import os
from dotenv import load_dotenv
from opinion_clob_sdk import Client
from opinion_clob_sdk.model import TopicStatusFilter

# Load environment variables
load_dotenv()

def main():
    # Initialize client
    client = Client(
        host='https://proxy.opinion.trade:8443',
        apikey=os.getenv('API_KEY'),
        chain_id=56,
        rpc_url=os.getenv('RPC_URL'),
        private_key=os.getenv('PRIVATE_KEY'),
        multi_sig_addr=os.getenv('MULTI_SIG_ADDRESS')
    )
    print("✓ Client initialized successfully!")

    # Get active markets
    markets_response = sdk.get_markets(
        status=TopicStatusFilter.ACTIVATED,
        limit=5
    )

    if markets_response.errno == 0:
        markets = markets_response.result.list
        print(f"\n✓ Found {len(markets)} active markets\n")

        # Display markets
        for i, market in enumerate(markets, 1):
            print(f"{i}. {market.market_title}")
            print(f"   Market ID: {market.market_id}")
            print()

        # Get details for first market
        if markets:
            first_market = markets[0]
            detail = sdk.get_categorical_market(market_id=first_market.market_id)

            if detail.errno == 0:
                m = detail.result.data
                print(f"✓ Details for '{m.market_title}':")
                print(f"  Status: {m.status}")
                print(f"  Question ID: {m.question_id}")
                print(f"  Quote Token: {m.quote_token}")
     else:
        print(f"Error fetching markets: {markets_response.errmsg}")

if __name__ == '__main__':
    main()
```

#### Run Your App

```bash
# Install python-dotenv if not already installed
pip install python-dotenv

# Run the script
python my_first_app.py
```

**Expected Output:**

```
✓ Client initialized successfully!

✓ Found 5 active markets

1. Will Bitcoin reach $100k by end of 2025?
   Market ID: 1

2. Will AI surpass human intelligence by 2030?
   Market ID: 2

...

✓ Details for 'Will Bitcoin reach $100k by end of 2025?':
  Status: 2
  Condition ID: 0xabc123...
  Quote Token: 0xdef456...
```

### Next Steps

Now that you've fetched market data, explore more advanced features:

#### Trading

Learn how to place orders:

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

# Enable trading (required once before placing orders)
client.enable_trading()

# Place a buy order of "No" token
order_data = PlaceOrderDataInput(
    marketId=813,
    tokenId='33095770954068818933468604332582424490740136703838404213332258128147961949614',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.55',
    makerAmountInQuoteToken=10  # 10 USDT
)

result = client.place_order(order_data)
print(f"Order placed: {result}")
```

See Placing Orders for detailed examples.

#### Position Management

Track your positions:

```python
# Get balances
balances = client.get_my_balances()

# Get positions
positions = client.get_my_positions(limit=20)

# Get trade history
trades = client.get_my_trades(market_id=813)
```

See Managing Positions for more.

#### Smart Contract Operations

Interact with blockchain:

```python
# Split USDT into outcome tokens
tx_hash, receipt, event = client.split(
    market_id=813,
    amount=1000000000000000000  # 1 USDT (18 decimals for USDT)
)

# Merge outcome tokens back to USDT
tx_hash, receipt, event = client.merge(
    market_id=813,
    amount=1000000000000000000
)

# Redeem winnings after market resolves
tx_hash, receipt, event = client.redeem(market_id=813)
```

See Contract Operations for details.

### Common Patterns

#### Error Handling

Always check response status:

```python
response = client.get_markets()

if response.errno == 0:
    # Success
    markets = response.result.list
else:
    # Error
    print(f"Error {response.errno}: {response.errmsg}")
```

#### Using Try-Except

```python
from opinion_clob_sdk import InvalidParamError, OpenApiError

try:
    market = client.get_market(market_id=123)
except InvalidParamError as e:
    print(f"Invalid parameter: {e}")
except OpenApiError as e:
    print(f"API error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")
```

#### Pagination

For large datasets:

```python
page = 1
all_markets = []

while True:
    response = client.get_markets(page=page, limit=100)
    if response.errno != 0:
        break

    markets = response.result.list
    all_markets.extend(markets)

    # Check if more pages exist
    if len(markets) < 100:
        break

    page += 1

print(f"Total markets: {len(all_markets)}")
```

### Configuration Tips

#### Cache Settings

Optimize performance with caching:

```python
client = Client(
    # ... other params ...
    market_cache_ttl=300,        # Cache markets for 5 minutes
    quote_tokens_cache_ttl=3600, # Cache quote tokens for 1 hour
    enable_trading_check_interval=3600  # Check trading status hourly
)
```

Set to `0` to disable caching:

```python
client = Client(
    # ... other params ...
    market_cache_ttl=0  # Disable market caching
)
```

#### Chain Selection

For production deployment, ensure you're using the correct configuration:

```python
client = Client(
    host='https://proxy.opinion.trade:8443',
    chain_id=56,  # BNB Chain mainnet
    rpc_url='https://bsc-dataseed.binance.org',  # BNB Chain RPC
    # ... other params ...
)
```

### Resources

* [**API Reference**](/developer-guide/opinion-clob-python-sdk/api-references): All Supported Methods
* [**Configuration Guide**](/developer-guide/opinion-clob-python-sdk/getting-started/configuration): Configuration
* [**Core Concepts**](/developer-guide/opinion-clob-python-sdk/core-concepts): Architecture
* [**Troubleshooting**](/developer-guide/opinion-clob-python-sdk/support/troubleshooting): Common Issues

***

**Ready to build?** Explore the API Reference to see all available methods!


# Configuration

This guide covers how to configure the Opinion CLOB SDK for different environments and use cases.

### Client Configuration

The `Client` class accepts multiple configuration parameters during initialization:

```python
from opinion_clob_sdk import Client

client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey='your_api_key',
    chain_id=56,
    rpc_url='your_rpc_url',
    private_key='0x...',
    multi_sig_addr='0x...',
    conditional_tokens_addr='0xAD1a38cEc043e70E83a3eC30443dB285ED10D774',
    multisend_addr='0x998739BFdAAdde7C933B942a68053933098f9EDa',
    enable_trading_check_interval=3600,
    quote_tokens_cache_ttl=3600,
    market_cache_ttl=300
)
```

### Required Parameters

#### host

**Type**: `str` **Description**: Opinion API host URL **Default**: No default (required)

```python
# Production
host='https://proxy.opinion.trade:8443'
```

#### apikey

**Type**: `str` **Description**: API authentication key provided by Opinion Labs **Default**: No default (required)

**How to obtain**: fill out  this [short application form](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII)

```python
apikey='________'
```

> ⚠️ **Security**: Store API keys in environment variables, never in source code.

#### chain\_id

**Type**: `int` **Description**: Blockchain network chain ID **Supported values**:

* `56` - BNB Chain Mainnet (production)

```python
# Mainnet
chain_id=56
```

#### rpc\_url

**Type**: `str` **Description**: Blockchain RPC endpoint URL **Default**: No default (required)

**Common providers**:

* **BNB Chain Mainnet**: `https://bsc-dataseed.binance.org`
* **BNB Chain (Nodereal)**: [`https://bsc.nodereal.io`](https://bsc.nodereal.io)

```python
# Public RPC (rate limited)
rpc_url='https://bsc-dataseed.binance.org'

# Private RPC (recommended for production)
rpc_url='https://bsc.nodereal.io'
```

#### private\_key

**Type**: `str` (HexStr) **Description**: Private key for signing orders and transactions **Format**: 64-character hex string (with or without `0x` prefix)

```python
private_key='0x1234567890abcdef...'  # With 0x prefix
# or
private_key='1234567890abcdef...'    # Without 0x prefix
```

> ⚠️ **Critical Security**:
>
> * Never commit private keys to version control
> * Use environment variables or secure key management systems
> * Ensure the associated address has BNB for gas fees
> * This is the **signer** address, may differ from multi\_sig\_addr

#### multi\_sig\_addr

**Type**: `str` **Description**: Multi-signature wallet address (your assets/portfolio wallet) **Format**: Ethereum address (checksummed or lowercase)

```python
multi_sig_addr='0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
```

**Relationship to private\_key**:

* `private_key` → **Signer address** (signs orders/transactions)
* `multi_sig_addr` → **Assets address** (holds funds/positions)
* Can be the same address or different (e.g., hot wallet signs for cold wallet)

**Where to find**:

* Check your Opinion platform "My Profile" section
* Or use the wallet address where you hold USDT/positions

### Optional Parameters

#### conditional\_tokens\_addr

**Type**: `ChecksumAddress` (str) **Description**: ConditionalTokens contract address **Default**: `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` (BNB Chain mainnet)

```python
# Default for BNB Chain - no need to specify
client = Client(chain_id=56, ...)

# Custom deployment
conditional_tokens_addr='0xYourConditionalTokensContract...'
```

**When to set**: Only if using a custom deployment

#### multisend\_addr

**Type**: `ChecksumAddress` (str) **Description**: Gnosis Safe MultiSend contract address **Default**: `0x998739BFdAAdde7C933B942a68053933098f9EDa` (BNB Chain mainnet)

```python
# Default for BNB Chain - no need to specify
client = Client(chain_id=56, ...)

# Custom deployment
multisend_addr='0xYourMultiSendContract...'
```

**When to set**: Only if using a custom Gnosis Safe deployment

#### enable\_trading\_check\_interval

**Type**: `int` **Description**: Cache duration (in seconds) for trading approval checks **Default**: `3600` (1 hour) **Range**: `0` to `∞`

```python
# Default: check approval status every hour
enable_trading_check_interval=3600

# Check every time (no caching)
enable_trading_check_interval=0

# Check daily
enable_trading_check_interval=86400
```

**Impact**:

* Higher values → Fewer RPC calls → Faster performance
* `0` → Always check → Slower but always current
* Recommended: `3600` (approvals rarely change)

#### quote\_tokens\_cache\_ttl

**Type**: `int` **Description**: Cache duration (in seconds) for quote token data **Default**: `3600` (1 hour) **Range**: `0` to `∞`

```python
# Default: cache for 1 hour
quote_tokens_cache_ttl=3600

# No caching (always fresh)
quote_tokens_cache_ttl=0

# Cache for 6 hours
quote_tokens_cache_ttl=21600
```

**Impact**:

* Quote tokens rarely change
* Higher values improve performance
* Recommended: `3600` or higher

#### market\_cache\_ttl

**Type**: `int` **Description**: Cache duration (in seconds) for market data **Default**: `300` (5 minutes) **Range**: `0` to `∞`

```python
# Default: cache for 5 minutes
market_cache_ttl=300

# No caching (always fresh)
market_cache_ttl=0

# Cache for 1 hour
market_cache_ttl=3600
```

**Impact**:

* Markets change frequently (prices, status)
* Lower values → More current data
* Recommended: `300` for balance of performance and freshness

### Environment Variables

#### Using .env Files

Create a `.env` file in your project root:

```bash
# .env
API_KEY=opn_prod_abc123xyz789
RPC_URL=____
PRIVATE_KEY=0x1234567890abcdef...
MULTI_SIG_ADDRESS=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
CHAIN_ID=56
```

Load in your Python code:

```python
import os
from dotenv import load_dotenv
from opinion_clob_sdk import Client

# Load .env file
load_dotenv()

# Use environment variables
client = Client(
    host='https://api.opinion.trade',
    apikey=os.getenv('API_KEY'),
    chain_id=int(os.getenv('CHAIN_ID', 56)),
    rpc_url=os.getenv('RPC_URL'),
    private_key=os.getenv('PRIVATE_KEY'),
    multi_sig_addr=os.getenv('MULTI_SIG_ADDRESS')
)
```

#### Using System Environment Variables

Set in shell:

```bash
# Linux/macOS
export API_KEY="opn_prod_abc123xyz789"
export RPC_URL=___
export PRIVATE_KEY="0x..."
export MULTI_SIG_ADDRESS="0x..."

# Windows (Command Prompt)
set API_KEY=opn_prod_abc123xyz789
set RPC_URL=___

# Windows (PowerShell)
$env:API_KEY="opn_prod_abc123xyz789"
$env:RPC_URL=___
```

Then access in Python:

```python
import os
client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey=os.environ['API_KEY'],  # Raises error if not set
    # ... or ...
    apikey=os.getenv('API_KEY', 'default_value'),  # Returns default if not set
    # ...
)
```

### Configuration Patterns

#### Multi-Environment Setup

Manage different environments (dev, staging, prod):

```python
import os
from opinion_clob_sdk import Client

ENVIRONMENTS = {
    'production': {
        'host': 'https://proxy.opinion.trade:8443',
        'chain_id': 56,  # BNB Chain Mainnet
        'rpc_url': 'https://bsc-dataseed.binance.org'
    }
}

def create_client(env='production'):
    config = ENVIRONMENTS[env]

    return Client(
        host=config['host'],
        apikey=os.getenv(f'{env.upper()}_API_KEY'),
        chain_id=config['chain_id'],
        rpc_url=config['rpc_url'],
        private_key=os.getenv(f'{env.upper()}_PRIVATE_KEY'),
        multi_sig_addr=os.getenv(f'{env.upper()}_MULTI_SIG_ADDRESS')
    )

# Usage
dev_client = create_client('development')
prod_client = create_client('production')
```

#### Configuration Class

Organize configuration in a class:

```python
from dataclasses import dataclass
import os
from opinion_clob_sdk import Client

@dataclass
class OpinionConfig:
    api_key: str
    rpc_url: str
    private_key: str
    multi_sig_address: str
    chain_id: int = 56
    host: str = 'https://proxy.opinion.trade:8443'
    market_cache_ttl: int = 300

    @classmethod
    def from_env(cls):
        """Load configuration from environment variables"""
        return cls(
            api_key=os.environ['API_KEY'],
            rpc_url=os.environ['RPC_URL'],
            private_key=os.environ['PRIVATE_KEY'],
            multi_sig_address=os.environ['MULTI_SIG_ADDRESS'],
            chain_id=int(os.getenv('CHAIN_ID', 56))
        )

    def create_client(self):
        """Create Opinion Client from this configuration"""
        return Client(
            host=self.host,
            apikey=self.api_key,
            chain_id=self.chain_id,
            rpc_url=self.rpc_url,
            private_key=self.private_key,
            multi_sig_addr=self.multi_sig_address,
            market_cache_ttl=self.market_cache_ttl
        )

# Usage
config = OpinionConfig.from_env()
client = config.create_client()
```

#### Read-Only Client

For applications that only read data (no trading):

```python
# Minimal configuration for read-only access
client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey=os.getenv('API_KEY'),
    chain_id=56,
    rpc_url='',           # Empty if not doing contract operations
    private_key='0x00',   # Dummy key if not placing orders
    multi_sig_addr='0x0000000000000000000000000000000000000000'
)

# Can use all GET methods
markets = client.get_markets()
market = client.get_market(123)
orderbook = client.get_orderbook('token_123')

# Cannot use trading or contract methods
# client.place_order(...)  # Would fail
# client.split(...)        # Would fail
```

### Performance Tuning

#### High-Frequency Trading

For trading bots with frequent API calls:

```python
client = Client(
    # ... required params ...
    market_cache_ttl=60,           # 1-minute cache for faster updates
    quote_tokens_cache_ttl=3600,   # 1-hour cache (rarely changes)
    enable_trading_check_interval=7200  # 2-hour cache (already approved)
)
```

#### Analytics/Research

For data analysis with less frequent updates:

```python
client = Client(
    # ... required params ...
    market_cache_ttl=1800,         # 30-minute cache
    quote_tokens_cache_ttl=86400,  # 24-hour cache
    enable_trading_check_interval=0  # Not trading
)
```

#### Real-Time Monitoring

For dashboards requiring fresh data:

```python
client = Client(
    # ... required params ...
    market_cache_ttl=0,            # No caching
    quote_tokens_cache_ttl=0,      # No caching
    enable_trading_check_interval=0
)
```

###

### Smart Contract Addresses

#### BNB Chain Mainnet (Chain ID: 56)

The following smart contract addresses are used by the Opinion CLOB SDK on BNB Chain mainnet:

| Contract              | Address                                      | Description                                            |
| --------------------- | -------------------------------------------- | ------------------------------------------------------ |
| **ConditionalTokens** | `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` | ERC1155 conditional tokens contract for outcome tokens |
| **MultiSend**         | `0x998739BFdAAdde7C933B942a68053933098f9EDa` | Gnosis Safe MultiSend contract for batch transactions  |

These addresses are automatically used by the SDK when you specify `chain_id=56`. You only need to provide custom addresses if you're using a custom deployment.

**Verification:**

* ConditionalTokens: [View on BscScan](https://bscscan.com/address/0xAD1a38cEc043e70E83a3eC30443dB285ED10D774)
* MultiSend: [View on BscScan](https://bscscan.com/address/0x998739BFdAAdde7C933B942a68053933098f9EDa)

### Next Steps

* [**API Reference**](/developer-guide/opinion-clob-python-sdk/api-references): All Supported Methods
* [**Configuration Guide**](/developer-guide/opinion-clob-python-sdk/getting-started/configuration): Configuration
* [**Core Concepts**](/developer-guide/opinion-clob-python-sdk/core-concepts): Architecture
* [**Troubleshooting**](/developer-guide/opinion-clob-python-sdk/support/troubleshooting): Common Issues


# Core Concepts


# Architecture

### System Architecture

The Opinion CLOB SDK implements a hybrid architecture that integrates off-chain order matching with on-chain settlement. This Python client provides programmatic access to the Opinion prediction market infrastructure deployed on BNB Chain.

#### High-Level Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                    Your Application                              │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                  opinion_clob_sdk.Client                         │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  API Layer (opinion_api)                                 │   │
│  │  - Market data queries                                   │   │
│  │  - Order submission                                      │   │
│  │  - Position tracking                                     │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  Chain Layer (Web3)                                      │   │
│  │  - Smart contract interactions                           │   │
│  │  - Token operations (split/merge/redeem)                 │   │
│  │  - Transaction signing                                   │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  Order Utils (EIP712)                                    │   │
│  │  - Order building                                        │   │
│  │  - Cryptographic signing                                 │   │
│  │  - Gnosis Safe integration                               │   │
│  └─────────────────────────────────────────────────────────┘   │
└───────────┬──────────────────────────────────┬─────────────────┘
            │                                  │
            ▼                                  ▼
┌─────────────────────────┐      ┌──────────────────────────────┐
│  Opinion CLOB API       │      │  BNB Chain (BSC)             │
│  - Order matching       │      │  - ConditionalTokens         │
│  - Market data          │      │  - USDT (Collateral)         │
│  - User positions       │      │  - Gnosis Safe               │
└─────────────────────────┘      └──────────────────────────────┘
```

### Core Components

#### 1. Client (`sdk.py`)

The `Client` class serves as the primary interface, orchestrating interactions across API and blockchain layers.

**Responsibilities:**

* API connection management and authentication
* Method exposure for market data, trading, and contract operations
* Market data and quote token caching with configurable TTL
* Coordination between HTTP requests and Web3 transactions

**Key Configuration:**

```python
client = Client(
    host='https://proxy.opinion.trade:8443',  # API endpoint
    apikey='your_api_key',                     # Authentication
    chain_id=56,                                # BNB Chain
    rpc_url='https://bsc-dataseed.binance.org',
    private_key='0x...',                        # For signing
    multi_sig_addr='0x...'                      # Assets wallet
)
```

#### 2. API Layer (`opinion_api`)

Auto-generated OpenAPI client managing HTTP communication with the Opinion CLOB backend.

**Capabilities:**

* RESTful API invocation with type-safe request/response models
* Request serialization and response deserialization
* HTTP status code handling and error propagation
* Bearer token authentication (API key-based)

**Endpoints Covered:**

* Market discovery and details
* Orderbook snapshots
* Price data and candles
* Order placement and cancellation
* Position and balance queries
* Trade history

#### 3. Chain Layer (`chain/contract_caller.py`)

Web3 integration layer enabling direct blockchain interactions via the `web3.py` library.

**Smart Contracts Integrated:**

| Contract              | Address                                      | Purpose                                                         |
| --------------------- | -------------------------------------------- | --------------------------------------------------------------- |
| **ConditionalTokens** | `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` | Core prediction market contract for splitting/merging positions |
| **MultiSend**         | `0x998739BFdAAdde7C933B942a68053933098f9EDa` | Gnosis Safe batch transaction helper                            |
| **USDT**              | Native BNB Chain USDT                        | Collateral token for all markets                                |

**Operations:**

* `split()` - Convert USDT → outcome tokens (YES/NO)
* `merge()` - Convert outcome tokens → USDT
* `redeem()` - Claim winnings from resolved markets
* `enable_trading()` - Approve token allowances

#### 4. Order Utils (`chain/py_order_utils/`)

Order construction and signing module implementing EIP712 typed structured data signatures.

**Components:**

* **OrderBuilder** - Constructs valid order objects with all required fields
* **Signer** - Signs orders using private key (EOA) or Gnosis Safe
* **Model Classes** - Type-safe order representations

**EIP712 Signing Process:**

```
Order Data → EIP712 Hash → ECDSA Signature → Signed Order → API
```

#### 5. Gnosis Safe Integration (`chain/safe/`)

Support for multi-signature wallets using Gnosis Safe v1.3.0 contracts.

**Key Concepts:**

* **Signer Address** - The private key that signs orders (can be a Safe owner)
* **Multi-Sig Address** - The Safe contract holding your funds
* **Signature Type 2** - Indicates Gnosis Safe signature scheme

### Data Flow Patterns

#### Pattern 1: Market Data Query (Gas-Free)

```
Application
    ↓ call get_markets()
Client
    ↓ HTTP GET /markets
Opinion API
    ↓ response (JSON)
Client (caches result, TTL-based)
    ↓ return typed object
Application
```

**Characteristics:**

* Pure API interaction, no blockchain transaction
* Zero gas cost
* Response latency: 50-150ms (cached: <1ms)
* Configurable TTL-based caching

#### Pattern 2: Order Placement (Gas-Free via CLOB)

```
Application
    ↓ place_order(order_data)
Client
    ↓ construct order
OrderBuilder
    ↓ generate EIP712 hash
Signer
    ↓ ECDSA signature (secp256k1)
Client
    ↓ HTTP POST /orders + signature
Opinion API
    ↓ verify signature (ecrecover)
    ↓ match against orderbook
    ↓ settle on-chain (backend batch)
Client
    ↓ return order confirmation (trans_no)
Application
```

**Characteristics:**

* Gas abstraction: backend covers settlement costs
* Cryptographic proof of authorization via ECDSA signature
* Order matching latency: 200-500ms
* On-chain settlement batched asynchronously

#### Pattern 3: Smart Contract Operation (Gas Required)

```
Application
    ↓ split(market_id, amount)
Client
    ↓ construct transaction data
Web3 Provider
    ↓ gas estimation
    ↓ transaction signing (ECDSA)
    ↓ broadcast to BNB Chain RPC
BNB Chain
    ↓ transaction inclusion (block creation)
    ↓ ConditionalTokens.splitPosition() execution
    ↓ event emission (logs)
Client
    ↓ transaction receipt polling
    ↓ return transaction hash
Application
```

**Characteristics:**

* Gas payment required (BNB native token)
* Direct smart contract invocation
* Block confirmation time: \~3 seconds (BSC)
* Finality: \~10 blocks (\~15 seconds recommended)
* State changes immutable post-confirmation

### Authentication & Security

#### Blockchain Signing

Two-key system for enhanced security:

1. **Private Key (Signer)**
   * Signs orders and transactions
   * Can be a hot wallet for automated trading
   * Never leaves your application
2. **Multi-Sig Address (Funder)**
   * Holds your USDT and outcome tokens
   * Gnosis Safe v1.3.0 create via GnosisSafeProxyFactory(0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2) on BNB Chain
   * Requires approval for token operations

**Example Configuration:**

```python
# EOA (externally owned account) setup
private_key = "0x..."        # Signs orders
multi_sig_addr = "0x..."     # Same as signer (EOA holds funds)

# Gnosis Safe setup
private_key = "0x..."        # Safe owner key (signs orders)
multi_sig_addr = "0x..."     # Safe contract address (holds funds)
```

### Precision and Number Handling

#### Token Decimals

All tokens use **18 decimal places** (Wei standard):

```python
1 USDT = 1_000_000_000_000_000_000 wei
0.5 YES = 500_000_000_000_000_000 wei
```

#### Price Representation

Prices are quoted as **decimal strings** representing probability:

```python
"0.5"   = 50% probability (50¢ per share)
"0.75"  = 75% probability (75¢ per share)
```

**Valid Range:** `0.01` to `0.99` (1% to 99%)

#### Amount Specifications

Orders can specify amounts in two ways:

```python
# Quote token (USDT) amount
PlaceOrderDataInput(
    makerAmountInQuoteToken=10  # Spend 10 USDT
)

# Base token (outcome token) amount
PlaceOrderDataInput(
    makerAmountInBaseToken=5    # Buy/sell 5 YES tokens
)
```

### Caching Strategy

The SDK implements intelligent caching for frequently accessed data:

#### Market Cache

```python
client = Client(
    market_cache_ttl_seconds=60,  # Cache markets for 1 minute
    ...
)
```

**Rationale:** Market metadata rarely changes, reducing API load.

#### Quote Token Cache

```python
client = Client(
    quote_token_cache_ttl_seconds=3600,  # Cache for 1 hour
    ...
)
```

**Rationale:** Supported currencies (USDT) are static configuration.

#### Real-Time Data

**Never cached:**

* Orderbook snapshots
* Latest prices
* User positions
* Order status

### Error Handling Architecture

#### API Errors

Structured response format:

```json
{
  "errno": 400,
  "errmsg": "____"
}
```

**Common Error Codes:**

* `0` - Success
* `400` - Invalid request parameters
* `500` - Internal server error

#### Chain Errors

```python
from opinion_clob_sdk.chain.exception import (
    BalanceNotEnough,           # Insufficient tokens
    NoPositionsToRedeem,        # No winning positions
    InsufficientGasBalance      # Not enough BNB for gas
)
```

**Handling Strategy:**

```python
try:
    tx_hash = client.split(market_id=813, amount=1000000000000000000)
except BalanceNotEnough:
    print("Insufficient USDT balance")
except InsufficientGasBalance:
    print("Need more BNB for gas fees")
except Exception as e:
    print(f"Unexpected error: {e}")
```

### Performance Benchmarks

#### API Response Times

| Operation     | Typical Latency | Factors                |
| ------------- | --------------- | ---------------------- |
| Get markets   | 50-150ms        | Cached: <10ms          |
| Get orderbook | 100-300ms       | Market depth           |
| Place order   | 200-500ms       | Signature verification |
| Get positions | 100-200ms       | Position count         |

#### Blockchain Transactions

| Operation | Block Confirmation | Finality          |
| --------- | ------------------ | ----------------- |
| Split     | 2 block (\~3s)     | 10 blocks (\~15s) |
| Merge     | 2 block (\~3s)     | 10 blocks (\~15s) |
| Redeem    | 2 block (\~3s)     | 10 blocks (\~15s) |
| Approve   | 2 block (\~3s)     | 10 blocks (\~15s) |

**Note:** BNB Chain (BSC) has \~1.5 second block time and recommends waiting 10 blocks for finality.

####

#### Python Compatibility

**Supported:** Python 3.8, 3.9, 3.10, 3.11, 3.12

**Recommended:** Python 3.10+ for best performance and type checking

### Extensibility

#### Custom RPC Providers

The SDK supports any Web3-compatible RPC provider:

```python
# Free public RPC
client = Client(rpc_url='https://bsc-dataseed.binance.org', ...)
```

### Next Steps

* [**Client**](/developer-guide/opinion-clob-python-sdk/core-concepts/client) - Detailed setup guide
* [**Order**](/developer-guide/opinion-clob-python-sdk/core-concepts/order) - Understanding market/limit orders
* [**Gas Operations**](/developer-guide/opinion-clob-python-sdk/core-concepts/gas-operations) - When you need BNB
* [**Precision**](/developer-guide/opinion-clob-python-sdk/core-concepts/precision) - Working with Wei units


# Client

### Overview

The `Client` class provides the primary programmatic interface to the Opinion prediction market infrastructure. Configuration accuracy during initialization determines operational capability and security posture.

### Basic Initialization

#### Minimal Configuration

```python
from opinion_clob_sdk import Client

client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey='your_api_key_here',
    chain_id=56,
    rpc_url='https://bsc-dataseed.binance.org',
    private_key='0x...',
    multi_sig_addr='0x...'
)
```

### Required Parameters

#### `host`

**Type:** `str`

**Description:** CLOB API endpoint URL for HTTP communication.

**Production Value:**

```python
host='https://proxy.opinion.trade:8443'
```

**Endpoint Functions:**

* Market data query routing
* Order submission and cancellation processing
* Position and balance data retrieval

#### `apikey`

**Type:** `str`

**Description:** Bearer token for API authentication and authorization.

**Acquisition:** Contact Opinion Labs at <support@opinion.trade> for API access provisioning.

**Security Best Practices:**

```python
import os
from dotenv import load_dotenv

load_dotenv()
apikey = os.getenv('API_KEY')  # Never hardcode!
```

#### `chain_id`

**Type:** `int`

**Description:** The blockchain network identifier.

**Supported Values:**

```python
from opinion_clob_sdk import CHAIN_ID_BNB_MAINNET

chain_id = CHAIN_ID_BNB_MAINNET  # 56
```

**Current Support:**

* **56** - BNB Chain (BSC) Mainnet

#### `rpc_url`

**Type:** `str`

**Description:** JSON-RPC endpoint URL for BNB Chain node communication.

**Available Providers:**

```python
# Public RPC endpoints (development and testing)
rpc_url = 'https://bsc-dataseed.binance.org'
rpc_url = 'https://bsc.nodereal.io'
```

#### `private_key`

**Type:** `str`

**Description:** secp256k1 private key for ECDSA signature generation (order signing and transaction authorization).

**Format Specification:**

```python
# 64-character hexadecimal string with 0x prefix (32 bytes)
private_key = '0x_______'
```

#### `multi_sig_addr`

**Type:** `str` (ChecksumAddress)

**Description:** Ethereum address (checksummed format) holding USDT collateral and outcome tokens.

**Gnosis Safe (Multi-Signature)**

```python
client = Client(
    private_key='0x...',           # Safe owner's key
    multi_sig_addr='0x8F58a1ab...', # Safe contract address
    ...
)
```

### Optional Parameters

#### `conditional_tokens_addr`

**Type:** `Optional[ChecksumAddress]`

**Default:** `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` (BNB Chain)

**Description:** The ConditionalTokens smart contract address for split/merge/redeem operations.

**When to Override:**

* Testing on custom networks
* Interacting with forked contracts
* Development environments

```python
# Usually you can omit this (uses default)
client = Client(...)

# Override only if needed
client = Client(
    conditional_tokens_addr='0xCustomAddress...',
    ...
)
```

#### `multisend_addr`

**Type:** `Optional[ChecksumAddress]`

**Default:** `0x998739BFdAAdde7C933B942a68053933098f9EDa` (BNB Chain)

**Description:** The Gnosis Safe MultiSend contract for batch transactions.

**When to Override:**

* Testing batch operations
* Custom Safe deployments

```python
# Usually you can omit this (uses default)
client = Client(...)
```

#### `market_cache_ttl_seconds`

**Type:** `int`

**Default:** `60` (1 minute)

**Description:** Time-to-live for cached market data.

**Tuning Guidelines:**

```python
# High-frequency trading (minimize stale data)
client = Client(market_cache_ttl_seconds=10, ...)

# Analytics/dashboards (reduce API load)
client = Client(market_cache_ttl_seconds=300, ...)

# One-time scripts (cache entire run)
client = Client(market_cache_ttl_seconds=3600, ...)

# Disable caching (always fresh data)
client = Client(market_cache_ttl_seconds=0, ...)
```

**Trade-offs:**

| TTL  | API Calls | Data Freshness  | Use Case                |
| ---- | --------- | --------------- | ----------------------- |
| 0    | Maximum   | Real-time       | HFT, critical decisions |
| 60   | Moderate  | 1-min delay OK  | General trading         |
| 300+ | Minimal   | 5-min+ delay OK | Analytics, monitoring   |

#### `quote_token_cache_ttl_seconds`

**Type:** `int`

**Default:** `3600` (1 hour)

**Description:** Time-to-live for cached quote token (currency) information.

**Rationale:** Quote tokens (USDT, etc.) rarely change, so aggressive caching is safe.

```python
# Default is usually fine
client = Client(quote_token_cache_ttl_seconds=3600, ...)
```

### Validation and Error Handling

#### Validate Configuration

```python
from opinion_clob_sdk import Client, SUPPORTED_CHAIN_IDS
from opinion_clob_sdk.chain.exception import InvalidParamError

try:
    client = Client(
        host='https://proxy.opinion.trade:8443',
        apikey='test_key',
        chain_id=999,  # Invalid!
        rpc_url='https://bsc-dataseed.binance.org',
        private_key='0x...',
        multi_sig_addr='0x...'
    )
except InvalidParamError as e:
    print(f"Configuration error: {e}")
    print(f"Supported chain IDs: {SUPPORTED_CHAIN_IDS}")
```

#### Test Connection

```python
client = Client(...)

# Test API connectivity
try:
    currencies = client.get_quote_tokens()
    print(f"✓ API connected: {len(currencies)} currencies available")
except Exception as e:
    print(f"✗ API connection failed: {e}")

# Test blockchain connectivity
try:
    from web3 import Web3
    w3 = Web3(Web3.HTTPProvider(client.rpc_url))
    block = w3.eth.block_number
    print(f"✓ RPC connected: current block {block}")
except Exception as e:
    print(f"✗ RPC connection failed: {e}")

# Test wallet
from web3 import Web3
account = Web3().eth.account.from_key(client.private_key)
print(f"✓ Wallet: {account.address}")
print(f"✓ Multi-sig: {client.multi_sig_addr}")
```

### Common Initialization Errors

#### Error: Invalid Chain ID

```python
# Error message
InvalidParamError: chain_id must be one of [56]

# Solution
client = Client(chain_id=56, ...)  # Use BNB Chain
```

#### Error: Invalid Private Key Format

```python
# Common mistakes
private_key = 'ac0974bec...'  # Missing 0x prefix
private_key = '0xGGGG...'     # Invalid hex

# Correct format
private_key = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
```

#### Error: RPC Connection Failed

```python
# Symptoms
requests.exceptions.ConnectionError: Max retries exceeded

# Solutions
1. Check internet connection
2. Try alternative RPC URL
3. Use paid RPC provider
4. Check firewall settings
```

#### Error: API Authentication Failed

```python
# Error response
{"errno": 40100, "errmsg": "Invalid API key"}

# Solutions
1. Verify API key is correct
2. Check for extra whitespace
3. Ensure key is active
4. Contact support@opinion.trade
```

### Performance Optimization

#### Connection Pooling

```python
# The SDK reuses HTTP connections automatically
# For long-running applications, create one client instance

# ✅ Good
client = Client(...)
for i in range(1000):
    markets = client.get_markets()

# ❌ Bad (creates 1000 connections)
for i in range(1000):
    client = Client(...)
    markets = client.get_markets()
```

#### Caching Strategy

```python
# Aggressive caching for read-heavy workloads
client = Client(
    market_cache_ttl_seconds=300,      # 5 minutes
    quote_token_cache_ttl_seconds=3600 # 1 hour
)

# Minimal caching for trading bots
client = Client(
    market_cache_ttl_seconds=10,       # 10 seconds
    quote_token_cache_ttl_seconds=60   # 1 minute
)
```

### Next Steps

* **Order** - Understanding market and limit orders
* **Gas Operations** - When you need BNB for gas
* **Quick Start Guide** - Your first API calls


# Order

### Overview

The Opinion CLOB implements two order execution types (Market, Limit) and two position directions (Buy, Sell). These primitives enable participation in binary prediction markets through standardized order mechanics.

### Order Sides

#### BUY (Long Position)

**Definition:** Acquisition of outcome tokens representing a prediction that the specified event will occur.

**Example - Binary Market:** "Will BTC reach $100k in 2025?"

```python
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide

# Buy YES tokens (betting it will happen)
side = OrderSide.BUY
```

**Payoff Structure:**

| Position             | Purchase Cost | Resolution | Settlement | Net P\&L         |
| -------------------- | ------------- | ---------- | ---------- | ---------------- |
| Long 100 YES @ $0.60 | $60           | YES        | $100       | +$40 (66.7% ROI) |
| Long 100 YES @ $0.60 | $60           | NO         | $0         | -$60 (100% loss) |

**Risk Parameters:**

* **Maximum Loss:** Premium paid (position cost)
* **Maximum Gain:** $1.00 per share minus premium
* **Breakeven:** Event resolution matches position direction

#### SELL (Short Position or Position Exit)

**Definition:** Transfer of outcome tokens, either closing an existing long position or establishing a synthetic short position.

**Two Use Cases:**

**Use Case 1: Close Position (Take Profit/Loss)**

```python
# You previously bought 100 YES @ $0.50
# Now YES price is $0.75
# Sell to lock in profit

from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide

side = OrderSide.SELL  # Sell your YES tokens
```

**P\&L Calculation:**

* Entry: 100 YES @ $0.50 = $50 cost basis
* Exit: 100 YES @ $0.75 = $75 proceeds
* **Realized P\&L: +$25 (50% return)**

**Use Case 2: Synthetic Short (Advanced)**

**Strategy:** Split collateral into outcome token pairs, sell overpriced outcome, retain opposite side.

```python
# Thesis: YES tokens overpriced at $0.80 (implied 80% probability)
# Strategy: Create position exposed to NO outcome

# Step 1: Convert 100 USDT → 100 YES + 100 NO (via splitPosition)
client.split(market_id=123, amount=100_000000)  # 100 USDT

# Step 2: Sell YES tokens
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

order = PlaceOrderDataInput(
    marketId=123,
    tokenId='token_yes',
    side=OrderSide.SELL,
    orderType=LIMIT_ORDER,
    price='0.80',
    makerAmountInBaseToken=100  # Sell 100 YES
)
client.place_order(order)

# Position Analysis:
# - Received: $80 USDT (from YES sale)
# - Holdings: 100 NO tokens
# - Net cost: $100 - $80 = $20
#
# Payoff scenarios:
# - If NO resolves: 100 NO → $100 USDT, P&L = $100 - $20 = +$80 (400% ROI)
# - If YES resolves: 100 NO → $0, P&L = $0 - $20 = -$20 (100% loss)
```

### Order Types

#### Market Orders

**Definition:** Orders executing immediately at the best available counterparty price, prioritizing fill certainty over price control.

**Execution Characteristics:**

* Fill guarantee (subject to liquidity availability)
* Immediate execution (latency: 200-500ms)
* Price discovery via orderbook matching
* Slippage exposure in thin markets

**Use Cases:**

* Urgent position entry/exit requirements
* Markets with deep liquidity (tight spread)
* Price movement urgency exceeds execution cost sensitivity
* Closing positions under time constraints

**Syntax:**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import MARKET_ORDER

# Market order: buy with specified USDT allocation
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    price='0',  # Ignored for market orders (accepts market price)
    makerAmountInQuoteToken=50  # Allocate 50 USDT for purchase
)

result = client.place_order(order)
```

**Order Matching Mechanism:**

1. Order transmitted to CLOB matching engine
2. Matching engine iterates best available limit orders
3. Fills sequentially until USDT allocation exhausted or orderbook cleared
4. Outcome tokens credited to multi\_sig\_addr
5. Execution report returned (average fill price, total quantity)

**Slippage Example:**

```
Orderbook:
  Sell: 10 YES @ $0.60
  Sell: 20 YES @ $0.61
  Sell: 50 YES @ $0.62

You place: Market BUY for $50 USDT

Execution:
  - Buy 10 YES @ $0.60 = $6.00
  - Buy 20 YES @ $0.61 = $12.20
  - Buy 51.6 YES @ $0.62 = $31.80

Total: 81.6 YES for $50.00
Average price: $0.613 per YES
```

#### Limit Orders

**Definition:** Orders that only execute at your specified price or better.

**Characteristics:**

* ✅ **Price control** (you set maximum/minimum)
* ✅ **No slippage** (always your price or better)
* ❌ **May not fill** (if price never reached)
* ❌ **Delayed execution** (passive waiting)

**When to Use:**

* You want a specific price
* No urgency to execute
* Market making strategies
* Large orders (avoid slippage)

**Syntax:**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

# Limit buy - only buy if price drops to $0.55 or lower
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.55',
    makerAmountInQuoteToken=100  # Willing to spend $100 USDT
)

result = client.place_order(order)
```

**How Limit Orders Execute:**

1. SDK sends order to CLOB
2. CLOB adds order to orderbook
3. Order waits for counterparty
4. Fills when matching order arrives (or immediate if crosses spread)
5. You receive tokens (partial fills possible)

**Order Matching Example:**

```
Orderbook before your order:
  Buy:  20 YES @ $0.58
  Buy:  30 YES @ $0.57
  Sell: 40 YES @ $0.60
  Sell: 50 YES @ $0.61

You place: Limit BUY 100 YES @ $0.59

Orderbook after:
  Buy:  100 YES @ $0.59  ← Your order (waiting)
  Buy:  20 YES @ $0.58
  Buy:  30 YES @ $0.57
  Sell: 40 YES @ $0.60
  Sell: 50 YES @ $0.61

Later, someone places: Limit SELL 60 YES @ $0.59

Result: You buy 60 YES @ $0.59, your order now shows 40 YES remaining
```

### Price Mechanics

#### Price Range

**Valid Prices:** `0.01` to `0.99`

**Interpretation:**

* `0.01` = 1% probability = 1¢ per $1 share
* `0.50` = 50% probability = 50¢ per $1 share
* `0.99` = 99% probability = 99¢ per $1 share

**Invalid Prices:**

```python
price='0.00'   # ❌ Too low
price='1.00'   # ❌ Too high
price='1.05'   # ❌ Greater than 1.00
```

#### Price Precision

Prices are strings with up to 4 decimal places:

```python
price='0.5'      # Valid: 50%
price='0.511'   # Valid: 51.10%
price='0.55555'  # Invalid: too many decimals
```

#### Bid-Ask Spread

The difference between best buy and sell prices.

```
Orderbook:
  Best Buy:  $0.58  ← Highest bid
  Best Sell: $0.62  ← Lowest ask

Spread = $0.62 - $0.58 = $0.04 (4¢)
```

**Spread Implications:**

| Spread | Market Condition | Strategy                 |
| ------ | ---------------- | ------------------------ |
| $0.01  | Tight (liquid)   | Market orders OK         |
| $0.05  | Moderate         | Limit orders recommended |
| $0.10+ | Wide (illiquid)  | Limit orders essential   |

### Amount Specifications

#### Quote Token Amount (USDT)

Specify how much USDT to spend (BUY) or receive (SELL).

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput

# Buy YES tokens by spending $50 USDT
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.60',
    makerAmountInQuoteToken=50  # $50 USDT
)

# Calculation:
# Tokens received = $50 / $0.60 = 83.33 YES tokens
```

**When to Use:**

* You have a fixed budget (e.g., "spend $100")
* Dollar-cost averaging
* Portfolio allocation (e.g., "allocate 10% of portfolio")

#### Base Token Amount (Outcome Tokens)

Specify exact number of outcome tokens to buy/sell.

```python
# Sell exactly 100 YES tokens
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.SELL,
    orderType=LIMIT_ORDER,
    price='0.75',
    makerAmountInBaseToken=100  # 100 YES tokens
)

# Calculation:
# USDT received = 100 × $0.75 = $75 USDT
```

**When to Use:**

* Closing a specific position (e.g., "sell all my 100 YES tokens")
* Rebalancing to exact token counts
* Arbitrage strategies

#### Conversion Between Amounts

```python
# Given: price and one amount type, calculate the other

price = 0.60
quote_amount = 50  # USDT

# Calculate base tokens
base_tokens = quote_amount / price  # 83.33 YES

# Reverse calculation
quote_amount = base_tokens * price  # $50 USDT
```

### Order Examples

#### Example 1: Simple Market Buy

```python
from opinion_clob_sdk import Client
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import MARKET_ORDER

client = Client(...)

# "Buy YES tokens with $100 USDT immediately"
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    price='0',  # Ignored for market orders
    makerAmountInQuoteToken=100
)

result = client.place_order(order)
```

#### Example 2: Limit Buy at Specific Price

```python
# "Buy 50 YES tokens, but only if price drops to $0.45 or lower"
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.45',
    makerAmountInBaseToken=50
)

result = client.place_order(order)
print(f"Order placed, waiting for $0.45 or better")
```

#### Example 3: Take Profit Sell

```python
# You own 200 YES, current price is $0.80, you want to sell
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.SELL,
    orderType=MARKET_ORDER,
    price='0',
    makerAmountInBaseToken=200  # Sell all 200 YES
)

result = client.place_order(order)
# You receive ~$160 USDT (200 × $0.80)
```

#### Example 4: Limit Sell (Ask)

```python
# "Sell 100 YES tokens, but only at $0.85 or higher"
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='84286908393008806294032747949016601113812276485362312899677525031544985576186',
    side=OrderSide.SELL,
    orderType=LIMIT_ORDER,
    price='0.85',
    makerAmountInBaseToken=100
)

result = client.place_order(order)
print(f"Order on book at $0.85")
```

### Order Lifecycle

#### 1. Order Creation

```python
result = client.place_order(order)
```

#### 2. Order States

| State         | Description                                            | Next Actions       |
| ------------- | ------------------------------------------------------ | ------------------ |
| **Pending**   | Waiting in orderbook                                   | Cancel or wait     |
| **Filled**    | Fully executed                                         | View trade history |
| **Cancelled** | Manually cancelled / Cancelled by system default rules | None               |
| **Expired**   | Time limit reached                                     | Place new order    |

#### 3. Checking Order Status

```python
# Get all your orders for a market
orders = client.get_my_orders(market_id=813, limit=50)

for order in orders['result']['data']:
    print(f"Order {order['orderId']}: {order['status']}")
```

#### 4. Cancelling Orders

```python
# Cancel single order
client.cancel_order(orderId='________')

# Cancel all orders for a market
cancelled = client.cancel_all_orders(market_id=813)
print(f"Cancelled {len(cancelled['result'])} orders")
```

### Best Practices

#### 1. Check Orderbook Before Trading

```python
# Always check current prices before placing orders
orderbook = client.get_orderbook(token_id='84286908393008806294032747949016601113812276485362312899677525031544985576186')

best_bid = orderbook['result']['bids'][0]['price'] if orderbook['result']['bids'] else None
best_ask = orderbook['result']['asks'][0]['price'] if orderbook['result']['asks'] else None

print(f"Best bid: {best_bid}, Best ask: {best_ask}")

# Place limit order between bid and ask for better fill chances
if best_bid and best_ask:
    mid_price = (float(best_bid) + float(best_ask)) / 2
    # Place buy slightly above bid, sell slightly below ask
```

#### 2. Use Limit Orders for Large Sizes

```python
# ❌ Bad: Large market order causes slippage
order = PlaceOrderDataInput(
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    makerAmountInQuoteToken=10000  # $10,000 - will move market!
)

# ✅ Good: Break into smaller limit orders
for i in range(10):
    order = PlaceOrderDataInput(
        side=OrderSide.BUY,
        orderType=LIMIT_ORDER,
        price=f'{0.60 + i * 0.001}',  # Incrementing prices
        makerAmountInQuoteToken=1000  # $1,000 each
    )
    client.place_order(order)
```

#### 3. Price Validation

```python
def validate_price(price: str) -> bool:
    try:
        p = float(price)
        return 0.01 <= p <= 0.99
    except ValueError:
        return False

price = '0.75'
if validate_price(price):
    order = PlaceOrderDataInput(price=price, ...)
else:
    raise ValueError("Invalid price")
```

### Common Mistakes

#### Mistake 1: Wrong Amount Type

```python
# ❌ Using quote amount for SELL orders often confusing
order = PlaceOrderDataInput(
    side=OrderSide.SELL,
    makerAmountInQuoteToken=50  # "Sell $50 worth" - hard to calculate
)

# ✅ Better: Specify exact tokens to sell
order = PlaceOrderDataInput(
    side=OrderSide.SELL,
    makerAmountInBaseToken=100  # "Sell 100 tokens" - clear
)
```

#### Mistake 2: Forgetting Price for Limit Orders

```python
# ❌ Missing price
order = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    # price missing!
    makerAmountInQuoteToken=50
)

# ✅ Always specify price for limit orders
order = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    price='0.65',
    makerAmountInQuoteToken=50
)
```

### Next Steps

* **Gas Operations** - Understanding when you need BNB
* **API Reference - Trading** - Full method documentation


# Gas Operations

## Gas vs Gas-Free Operations

### Overview

The Opinion CLOB SDK implements a hybrid execution model: off-chain order matching via CLOB infrastructure eliminates gas costs for order operations, while direct smart contract invocations require BNB native token for transaction fees.

### Gas-Free Operations

#### Off-Chain Order Book

The Central Limit Order Book functions as an off-chain matching engine. Orders are authenticated via EIP712 cryptographic signatures and submitted to the Opinion API. On-chain settlement is executed asynchronously by backend infrastructure, abstracting gas costs from end users.

**Supported Gas-Free Operations**

**Market Data Queries**

* `get_markets()` - Market metadata retrieval
* `get_market()` - Individual market details
* `get_categorical_market()` - Categorical market information
* `get_orderbook()` - Real-time order book snapshots
* `get_latest_price()` - Current market prices
* `get_price_history()` - Historical price data (OHLCV candles)
* `get_quote_tokens()` - Supported collateral currencies
* `get_fee_rates()` - Fee schedule information

**Order Management**

* `place_order()` - Order submission (market and limit)
* `cancel_order()` - Single order cancellation
* `cancel_all_orders()` - Batch order cancellation
* `get_my_orders()` - Active order retrieval
* `get_order_by_id()` - Order status queries

**Position Tracking**

* `get_my_balances()` - Token balance queries
* `get_my_positions()` - Position inventory
* `get_my_trades()` - Trade history

#### Technical Implementation

```python
from opinion_clob_sdk import Client
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

client = Client(
    host='https://proxy.opinion.trade:8443',
    apikey='your_api_key',
    chain_id=56,
    rpc_url='https://bsc-dataseed.binance.org',
    private_key='0x...',      # Signs orders (no gas required)
    multi_sig_addr='0x...'
)

# Gas-free order placement
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='——————',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.65',
    makerAmountInQuoteToken=100
)

result = client.place_order(order)  # Zero gas cost to user
```

#### EIP712 Signature Protocol

Orders employ typed structured data signatures conforming to EIP712 specification. Signatures provide cryptographic proof of authorization without blockchain state modification.

**Signature Generation Flow:**

1. Order parameters encoded per EIP712 TypedData standard
2. Digest computed: `keccak256("\x19\x01" ‖ domainSeparator ‖ structHash)`
3. ECDSA signature generated via secp256k1 curve
4. Signature transmitted with order to API endpoint
5. Backend performs ecrecover validation (signer authenticity)
6. Matched orders batch-settled on-chain (gas paid by infrastructure)

**Pseudocode:**

```python
# EIP712 signature construction (simplified)
domain_separator = keccak256(
    encode(EIP712Domain_TYPEHASH, name, version, chainId, verifyingContract)
)
struct_hash = keccak256(encode(ORDER_TYPEHASH, order_params))
digest = keccak256(concat(0x1901, domain_separator, struct_hash))
signature = ecdsa_sign(digest, private_key)  # Returns (v, r, s)
```

### Gas-Required Operations

#### On-Chain Smart Contract Invocations

Direct blockchain transactions invoke smart contract methods and require gas payment in BNB native tokens. These operations modify on-chain state and are irreversible post-confirmation.

**Operations Requiring Gas**

**Token Approval (One-Time Setup)**

* `enable_trading()` - Grant ERC20/ERC1155 allowances to exchange contracts

**Position Operations**

* `split()` - Invoke `ConditionalTokens.splitPosition()` (USDT → outcome tokens)
* `merge()` - Invoke `ConditionalTokens.mergePositions()` (outcome tokens → USDT)
* `redeem()` - Invoke `ConditionalTokens.redeemPositions()` (claim winning payouts)

**State Query (RPC Call, No Gas)**

* `check_enable_trading()` - Read contract allowance state via `eth_call`

#### Gas Cost Analysis

**BNB Chain Network Parameters**

| Parameter          | Value     | Notes                                  |
| ------------------ | --------- | -------------------------------------- |
| Block Time         | \~1.5s    | Average block production interval      |
| Gas Price          | 0.05 Gwei | Minimum gas price (EIP-1559 base fee)  |
| Finality Threshold | 10 blocks | Recommended confirmation depth (\~15s) |

**Operation Gas Consumption Estimates**

| Operation                        | Gas Units | Cost @ 0.05 Gwei | USD Cost @ $600/BNB |
| -------------------------------- | --------- | ---------------- | ------------------- |
| `enable_trading()` (2 approvals) | \~100,000 | 0.000005 BNB     | $0.003              |
| `split()`                        | \~150,000 | 0.0000075 BNB    | $0.0045             |
| `merge()`                        | \~120,000 | 0.000006 BNB     | $0.0036             |
| `redeem()`                       | \~180,000 | 0.000009 BNB     | $0.0054             |

**Cost Formula:**

```
Transaction Fee = Gas Units × Gas Price (Gwei) × 10^-9 BNB
USD Cost = Transaction Fee × BNB/USD Price
```

**Variability Factors:**

* Network congestion (gas price auction)
* Contract state complexity (storage operations)
* Transaction data size (calldata cost)
* BNB market price volatility

#### Implementation Examples

**Enable Trading (Required Once)**

```python
from opinion_clob_sdk import Client

client = Client(...)

# Check current approval status
status = client.check_enable_trading()
print(f"USDT approved: {status['usdt_approved']}")
print(f"Conditional tokens approved: {status['conditional_tokens_approved']}")

# Grant approvals if needed
if not (status['usdt_approved'] and status['conditional_tokens_approved']):
    tx_hash = client.enable_trading()
    print(f"Approval transaction: {tx_hash}")
    # Wait for confirmation before trading
```

**Required Approvals:**

* **USDT Contract** → Exchange contract (for collateral deposits)
* **ConditionalTokens Contract** → Exchange contract (for outcome token trading)

**Split Position**

```python
# Convert 100 USDT into 100 YES + 100 NO tokens
amount_in_usdt = 100
amount_in_wei = amount_in_usdt * 10**18  # USDT has 18 decimals

tx_hash = client.split(
    market_id=813,
    amount=amount_in_wei
)

print(f"Split transaction: {tx_hash}")
# Result: +100 YES tokens, +100 NO tokens, -100 USDT
```

**Use Cases:**

* Creating outcome tokens for selling
* Market making strategies
* Arbitrage opportunities

**Merge Position**

```python
# Convert 50 YES + 50 NO back into 50 USDT
amount_to_merge = 50 * 10**18  # Outcome tokens use 18 decimals

tx_hash = client.merge(
    market_id=813,
    amount=amount_to_merge
)

print(f"Merge transaction: {tx_hash}")
# Result: -50 YES tokens, -50 NO tokens, +50 USDT
```

**Requirements:**

* Must hold equal amounts of both outcome tokens
* Amount specified in Wei (18 decimals)

**Redeem Winnings**

```python
# Claim winnings from resolved market
try:
    tx_hash = client.redeem(market_id=813)
    print(f"Redeem transaction: {tx_hash}")
except NoPositionsToRedeem:
    print("No winning positions to redeem")
```

**Redemption Logic:**

* Markets resolved to YES: 1 YES token → 1 USDT
* Markets resolved to NO: 1 NO token → 1 USDT
* Losing outcome tokens become worthless

### Gas Balance Requirements

#### Recommended BNB Holdings

Maintain sufficient BNB balance to execute gas-required operations without transaction failures.

**Allocation Guidelines:**

| Use Case       | Minimum BNB         | Rationale                                   |
| -------------- | ------------------- | ------------------------------------------- |
| Initial setup  | 0.001 BNB (\~$0.60) | Single `enable_trading()` call              |
| High-frequency | 0.1 BNB (\~$60.00)  | Hundreds of transactions, failover capacity |

#### Balance Monitoring

```python
from web3 import Web3

w3 = Web3(Web3.HTTPProvider('https://bsc-dataseed.binance.org'))
address = '0xYourWalletAddress'

# Query native token balance
balance_wei = w3.eth.get_balance(address)
balance_bnb = Web3.from_wei(balance_wei, 'ether')

print(f"BNB Balance: {balance_bnb:.6f} BNB")

# Alert threshold
MINIMUM_BALANCE = 0.005  # BNB
if balance_bnb < MINIMUM_BALANCE:
    print(f"WARNING: BNB balance below threshold. Current: {balance_bnb}, Required: {MINIMUM_BALANCE}")
```

#### Position Management Planning

Structure trading strategies to minimize split/merge operations.

**Example Strategy:**

1. Execute single split to create large token inventory
2. Trade via gas-free CLOB orders
3. Merge/redeem only when exiting position or market resolves

```python
# Initial setup (one-time gas cost)
client.split(market_id=813, amount=1000_000000)  # Create 1000 YES + 1000 NO

# Trading loop (no gas costs)
for i in range(100):
    order = PlaceOrderDataInput(...)
    client.place_order(order)  # Gas-free

# Position exit (one-time gas cost)
client.merge(market_id=813, amount=500 * 10**18)  # Merge remaining 500 pairs
```

### Next Steps

* **Precision** - Token decimal systems


# Precision

## Precision and Amount Handling

### Token Decimal Systems

#### Overview

The Opinion CLOB SDK interacts with token standards employing distinct decimal precision schemes. Precision handling accuracy is critical for preventing calculation errors and fund loss.

#### USDT (Collateral Token)

**Decimal Specification:** 18 (ERC-20 standard)

**Conversion Implementation:**

```python
# Human-readable: 100 USDT
usdt_amount = 100

# Contract representation 
amount_micro_usdt = usdt_amount * 10**18  

# Reverse transformation
usdt_amount = amount_micro_usdt / 10**18  # 100.0 USDT
```

**Conversion Table:**

| Human-Readable | Exponential Notation  |
| -------------- | --------------------- |
| 1 USDT         | 1 × 10^18             |
| 10 USDT        | 10 × 10^18            |
| 0.5 USDT       | 0.5 × 10^18           |
| 100.50 USDT    | 100.5 × 10^18         |
| 0.000001 USDT  | 10^-18 (minimum unit) |

#### Outcome Tokens (YES/NO)

**Decimal Specification:** 18 (ERC-1155 standard, Wei base unit)

**Conversion Implementation:**

```python
# Human-readable: 50 YES tokens
token_amount = 50

# Contract representation (Wei)
amount_wei = token_amount * 10**18  # 50_000_000_000_000_000_000 Wei

# Reverse transformation
token_amount = amount_wei / 10**18  # 50.0 tokens
```

**Conversion Table:**

| Human-Readable           | Wei (Contract)                   | Exponential Notation       |
| ------------------------ | -------------------------------- | -------------------------- |
| 1 YES                    | 1\_000\_000\_000\_000\_000\_000  | 1 × 10^18                  |
| 10 YES                   | 10\_000\_000\_000\_000\_000\_000 | 10 × 10^18                 |
| 0.1 YES                  | 100\_000\_000\_000\_000\_000     | 0.1 × 10^18                |
| 0.000000000000000001 YES | 1                                | 10^-18 (Wei, minimum unit) |

### Price Representation

#### Format Specification

Prices encode implied probability as decimal strings, representing the USDT cost per outcome token (normalized to $1.00 payout).

**Type Constraints:**

* Data Type: `str`
* Value Range: `[0.01, 0.99]` (1% to 99% implied probability)
* Precision: Maximum 4 decimal places (0.0001 tick size)

**Price Interpretation Table:**

| Price String | Implied Probability | Cost per Share | Payout (if correct) | Max Profit         |
| ------------ | ------------------- | -------------- | ------------------- | ------------------ |
| `"0.01"`     | 1%                  | $0.01          | $1.00               | $0.99 (9900% ROI)  |
| `"0.50"`     | 50%                 | $0.50          | $1.00               | $0.50 (100% ROI)   |
| `"0.652"`    | 65.2%               | $0.652         | $1.00               | $0.348 (53.3% ROI) |
| `"0.99"`     | 99%                 | $0.99          | $1.00               | $0.01 (1.01% ROI)  |

### Order Amount Specifications

#### Quote Token Amount (makerAmountInQuoteToken)

Specifies the USDT amount to spend (BUY orders) or receive (SELL orders).

**BUY Order Calculation:**

```python
price = "0.60"
maker_amount_quote = 100  # Spend 100 USDT

# Tokens received calculation
price_float = float(price)
tokens_received = maker_amount_quote / price_float  # 166.67 YES tokens
```

**SELL Order Calculation:**

```python
price = "0.75"
maker_amount_quote = 50  # Receive 50 USDT

# Tokens sold calculation
price_float = float(price)
tokens_sold = maker_amount_quote / price_float  # 66.67 YES tokens
```

**Implementation:**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

order = PlaceOrderDataInput(
    marketId=813,
    tokenId='____',
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price='0.60',
    makerAmountInQuoteToken=100  # USDT amount (decimal, not Wei)
)
```

**Note:** The SDK handles conversion to Wei internally. Provide amounts in human-readable decimal format.

#### Base Token Amount (makerAmountInBaseToken)

Specifies the exact number of outcome tokens to buy or sell.

**BUY Order Calculation:**

```python
price = "0.60"
maker_amount_base = 200  # Buy 200 YES tokens

# USDT cost calculation
price_float = float(price)
usdt_cost = maker_amount_base * price_float  # 120 USDT
```

**SELL Order Calculation:**

```python
price = "0.75"
maker_amount_base = 100  # Sell 100 YES tokens

# USDT received calculation
price_float = float(price)
usdt_received = maker_amount_base * price_float  # 75 USDT
```

**Implementation:**

```python
order = PlaceOrderDataInput(
    marketId=813,
    tokenId='_____',
    side=OrderSide.SELL,
    orderType=LIMIT_ORDER,
    price='0.75',
    makerAmountInBaseToken=100  # Token amount (decimal, not Wei)
)
```

### Smart Contract Amount Specifications

#### Split Operation

Converts USDT collateral into outcome token pairs (YES + NO).

```python
# Human amounts
usdt_to_split = 100  # 100 USDT

# Convert to Wei (USDT uses 6 decimals)
amount_in_wei = usdt_to_split * 10**18  

# Execute split
tx_hash = client.split(
    market_id=813,
    amount=amount_in_wei
)

# Result:
# - Deduct: 100 USDT (100_000_000_000_000_000_000 Wei)
# - Credit: 100 YES (100_000_000_000_000_000_000 Wei, 18 decimals)
# - Credit: 100 NO  (100_000_000_000_000_000_000 Wei, 18 decimals)
```

**Decimal Conversion:**

```python
def usdt_to_wei(usdt_amount: float) -> int:
    """Convert USDT amount to Wei representation."""
    return int(usdt_amount * 10**18)

def wei_to_usdt(wei_amount: int) -> float:
    """Convert Wei representation to USDT amount."""
    return wei_amount / 10**18

# Usage
wei = usdt_to_wei(100.50)  # 100_500_000_000_000_000_000
usdt = wei_to_usdt(100_500_000_000_000_000_000)  # 100.5
```

#### Merge Operation

Converts outcome token pairs (YES + NO) back into USDT collateral.

```python
# Human amounts
token_pairs_to_merge = 50  # 50 YES + 50 NO pairs

# Convert to Wei (outcome tokens use 18 decimals)
amount_in_wei = token_pairs_to_merge * 10**18  # 50_000_000_000_000_000_000

# Execute merge
tx_hash = client.merge(
    market_id=123,
    amount=amount_in_wei
)

# Result:
# - Deduct: 50 YES (50_000_000_000_000_000_000 Wei)
# - Deduct: 50 NO  (50_000_000_000_000_000_000 Wei)
# - Credit: 50 USDT (50_000_000_000_000_000_000 Wei, 18 decimals)
```

**Decimal Conversion:**

```python
def tokens_to_wei(token_amount: float) -> int:
    """Convert outcome token amount to Wei representation."""
    return int(token_amount * 10**18)

def wei_to_tokens(wei_amount: int) -> float:
    """Convert Wei representation to outcome token amount."""
    return wei_amount / 10**18

# Usage
wei = tokens_to_wei(50.5)  # 50_500_000_000_000_000_000
tokens = wei_to_tokens(50_500_000_000_000_000_000)  # 50.5
```

#### Redeem Operation

Claims winnings from resolved markets.

```python
# Redeem automatically converts all winning tokens to USDT
# No amount parameter required - redeems entire position

tx_hash = client.redeem(market_id=813)

# If market resolved YES and you hold 100 YES tokens:
# - Deduct: 100 YES (100_000_000_000_000_000_000 Wei)
# - Credit: 100 USDT (100_000_000_000_000_000_000 Wei)
```

### Floating Point Precision Issues

#### Problem: IEEE 754 Rounding Errors

Python's `float` type implements IEEE 754 binary floating-point arithmetic, which cannot precisely represent all decimal fractions.

**Problematic Pattern:**

```python
# ❌ Incorrect: Binary floating-point accumulates rounding errors
price = 0.65
amount = 100
result = price * amount  # May yield 64.99999999999999 (15-17 sig figs)

# Verification
result == 65.0  # False on some systems
```

**Correct Pattern:**

```python
# ✅ Correct: Decimal type provides exact decimal arithmetic
from decimal import Decimal, ROUND_DOWN

price = Decimal('0.65')
amount = Decimal('100')
result = price * amount  # Exactly Decimal('65.00')

# Verification
result == Decimal('65.00')  # True (exact equality)
```

#### Best Practices for Precision

```python
from decimal import Decimal, ROUND_DOWN

def calculate_tokens_from_usdt(usdt_amount: str, price: str) -> str:
    """
    Calculate token amount from USDT budget and price.

    Args:
        usdt_amount: USDT amount as string (e.g., "100.50")
        price: Price as string (e.g., "0.65")

    Returns:
        Token amount as string with appropriate precision
    """
    usdt_decimal = Decimal(usdt_amount)
    price_decimal = Decimal(price)
    tokens = usdt_decimal / price_decimal
    return str(tokens.quantize(Decimal('0.01'), rounding=ROUND_DOWN))

# Usage
tokens = calculate_tokens_from_usdt("100", "0.65")  # "153.84"
```

### Amount Formatting

#### Display Formatting

```python
def format_usdt(amount: float) -> str:
    """Format USDT amount for display."""
    return f"${amount:,.2f}"

def format_tokens(amount: float) -> str:
    """Format token amount for display."""
    return f"{amount:,.4f}"

def format_price(price: str) -> str:
    """Format price for display."""
    return f"{float(price):.4f} USDT"

# Usage
print(format_usdt(1234.56))      # "$1,234.56"
print(format_tokens(1234.5678))  # "1,234.5678"
print(format_price("0.6525"))    # "0.6525 USDT"
```

### Common Precision Errors

#### Error 1: Incorrect Decimal Places

```python
# ❌ Incorrect: Using 6 decimals for USDT
usdt_wei = 100 * 10*6  # Wrong!
client.split(market_id=813, amount=usdt_wei)  # Will behave unexpectedly

# ✅ Correct: Using 18 decimals for USDT
usdt_wei = 100 * 10**18  # Correct
client.split(market_id=813, amount=usdt_wei)
```

#### Error 2: Float to Wei Conversion

```python
# ❌ Problematic: Direct float multiplication
amount = 100.5
wei = int(amount * 10**18)  # May have rounding errors

# ✅ Better: Use Decimal for precision
from decimal import Decimal
amount = Decimal('100.5')
wei = int(amount * 10**18)
```

#### Error 3: Price Outside Valid Range

```python
# ❌ Invalid prices
order = PlaceOrderDataInput(price='0.00', ...)  # Below minimum
order = PlaceOrderDataInput(price='1.00', ...)  # Above maximum
order = PlaceOrderDataInput(price='1.50', ...)  # Far out of range

# ✅ Valid prices
order = PlaceOrderDataInput(price='0.01', ...)  # Minimum
order = PlaceOrderDataInput(price='0.65', ...)  # Typical
order = PlaceOrderDataInput(price='0.99', ...)  # Maximum
```

### Position Size Calculations

#### Total Position Value

```python
def calculate_position_value(token_amount: float, current_price: str) -> float:
    """
    Calculate current market value of position.

    Args:
        token_amount: Number of outcome tokens held
        current_price: Current market price

    Returns:
        Position value in USDT
    """
    from decimal import Decimal
    tokens = Decimal(str(token_amount))
    price = Decimal(current_price)
    return float(tokens * price)

# Usage
position_value = calculate_position_value(100, "0.75")  # 75.0 USDT
```

#### Profit and Loss Calculation

```python
def calculate_pnl(
    buy_amount: float,
    buy_price: str,
    sell_amount: float,
    sell_price: str
) -> dict:
    """
    Calculate profit/loss for a completed trade.

    Args:
        buy_amount: Tokens purchased
        buy_price: Purchase price
        sell_amount: Tokens sold
        sell_price: Sale price

    Returns:
        Dictionary with PnL metrics
    """
    from decimal import Decimal

    buy_cost = Decimal(str(buy_amount)) * Decimal(buy_price)
    sell_proceeds = Decimal(str(sell_amount)) * Decimal(sell_price)
    pnl = sell_proceeds - buy_cost
    pnl_percent = (pnl / buy_cost * 100) if buy_cost > 0 else Decimal('0')

    return {
        'buy_cost': float(buy_cost),
        'sell_proceeds': float(sell_proceeds),
        'pnl': float(pnl),
        'pnl_percent': float(pnl_percent)
    }

# Usage
pnl = calculate_pnl(100, "0.60", 100, "0.75")
# {'buy_cost': 60.0, 'sell_proceeds': 75.0, 'pnl': 15.0, 'pnl_percent': 25.0}
```

#### Break-even Analysis

```python
def calculate_breakeven_price(
    buy_amount: float,
    buy_price: str,
    fee_rate: float = 0.02(taker 2%, maker 0%)
) -> str:
    """
    Calculate price needed to break even after fees.

    Args:
        buy_amount: Tokens purchased
        buy_price: Purchase price
        fee_rate: Trading fee rate (default 2%)

    Returns:
        Break-even price as string
    """
    from decimal import Decimal, ROUND_UP

    cost = Decimal(str(buy_amount)) * Decimal(buy_price)
    fees = cost * Decimal(str(fee_rate))
    total_cost = cost + fees
    breakeven = total_cost / Decimal(str(buy_amount))

    return str(breakeven.quantize(Decimal('0.0001'), rounding=ROUND_UP))

# Usage
breakeven = calculate_breakeven_price(100, "0.60", 0.02)  # "0.6120"
```

### API Response Amount Parsing

#### Parse Balance Response

```python
def parse_balance_response(balance_data: dict) -> dict:
    """
    Parse balance API response to human-readable amounts.

    Args:
        balance_data: Raw balance data from API

    Returns:
        Parsed balance information
    """
    balances = {}
    for item in balance_data.get('result', []):
        token_name = item['quoteTokenName']
        amount_str = item['available']

        # Determine decimal places based on token type
        if token_name == 'USDT':
            amount = float(amount_str) / 10**18

        balances[token_name] = amount

    return balances

# Usage
response = client.get_my_balances()
balances = parse_balance_response(response)
print(f"USDT: {balances.get('USDT', 0):.2f}")
```

### Next Steps

* **API Reference - Models** - Data type specifications


# API References


# Models

## Data Models

Reference for all data models and enums used in the Opinion CLOB SDK.

### Enums

#### TopicType

Defines the type of prediction market. **Topic** is conceptional equivalent to **Market.**

**Module:** `opinion_clob_sdk.model`

```python
from opinion_clob_sdk.model import TopicType

class TopicType(Enum):
    BINARY = 0        # Two-outcome markets (YES/NO)
    CATEGORICAL = 1   # Multi-outcome markets (Option A/B/C/...)
```

**Usage:**

```python
# Filter for binary markets only
markets = client.get_markets(topic_type=TopicType.BINARY)

# Filter for categorical markets
markets = client.get_markets(topic_type=TopicType.CATEGORICAL)
```

***

#### TopicStatus

Market lifecycle status codes.

**Module:** `opinion_clob_sdk.model`

```python
from opinion_clob_sdk.model import TopicStatus

class TopicStatus(Enum):
    CREATED = 1    # Market created but not yet active
    ACTIVATED = 2  # Market is live and accepting trades
    RESOLVING = 3  # Market ended, awaiting resolution
    RESOLVED = 4   # Market resolved with outcome
```

**Usage:**

```python
market = client.get_market(123)
status = market.result.data.status

if status == TopicStatus.ACTIVATED.value:
    print("Market is live for trading")
elif status == TopicStatus.RESOLVED.value:
    print("Market resolved, can redeem winnings")
```

***

#### TopicStatusFilter

Filter values for querying markets by status.

**Module:** `opinion_clob_sdk.model`

```python
from opinion_clob_sdk.model import TopicStatusFilter

class TopicStatusFilter(Enum):
    ALL = None           # All markets regardless of status
    ACTIVATED = "activated"  # Only active markets
    RESOLVED = "resolved"    # Only resolved markets
```

**Usage:**

```python
# Get only active markets
markets = client.get_markets(status=TopicStatusFilter.ACTIVATED)

# Get only resolved markets
markets = client.get_markets(status=TopicStatusFilter.RESOLVED)

# Get all markets
markets = client.get_markets(status=TopicStatusFilter.ALL)
```

***

#### OrderSide

Trade direction for orders.

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.sides`

```python
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide

class OrderSide(IntEnum):
    BUY = 0   # Buy outcome tokens
    SELL = 1  # Sell outcome tokens
```

**Usage:**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput

# Place buy order
buy_order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.BUY,  # Buy YES tokens
    # ...
)

# Place sell order
sell_order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.SELL,  # Sell YES tokens
    # ...
)
```

***

#### Order Types

Constants for order type selection.

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.order_type`

```python
from opinion_clob_sdk.chain.py_order_utils.model.order_type import (
    MARKET_ORDER,
    LIMIT_ORDER
)

MARKET_ORDER = 1  # Execute immediately at best available price
LIMIT_ORDER = 2   # Execute at specified price or better
```

**Usage:**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order_type import MARKET_ORDER, LIMIT_ORDER

# Market order - executes immediately
market_order = PlaceOrderDataInput(
    orderType=MARKET_ORDER,
    price="0",  # Price ignored for market orders
    # ...
)

# Limit order - waits for specified price
limit_order = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    price="0.55",  # Execute at $0.55 or better
    # ...
)
```

***

### Data Classes

#### PlaceOrderDataInput

Input data for placing an order.

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.order`

```python
@dataclass
class PlaceOrderDataInput:
    marketId: int
    tokenId: str
    side: int  # OrderSide.BUY or OrderSide.SELL
    orderType: int  # MARKET_ORDER or LIMIT_ORDER
    price: str
    makerAmountInQuoteToken: str = None  # Amount in USDT (optional)
    makerAmountInBaseToken: str = None   # Amount in YES/NO tokens (optional)
```

**Fields:**

| Field                     | Type  | Required | Description                                           |
| ------------------------- | ----- | -------- | ----------------------------------------------------- |
| `marketId`                | `int` | Yes      | Market ID to trade on                                 |
| `tokenId`                 | `str` | Yes      | Token ID (e.g., "token\_yes")                         |
| `side`                    | `int` | Yes      | `OrderSide.BUY` (0) or `OrderSide.SELL` (1)           |
| `orderType`               | `int` | Yes      | `MARKET_ORDER` (1) or `LIMIT_ORDER` (2)               |
| `price`                   | `str` | Yes      | Price as string (e.g., "0.55"), "0" for market orders |
| `makerAmountInQuoteToken` | `str` | No\*     | Amount in quote token (e.g., "100" for 100 USDT)      |
| `makerAmountInBaseToken`  | `str` | No\*     | Amount in base token (e.g., "50" for 50 YES tokens)   |

\* Must provide exactly ONE of `makerAmountInQuoteToken` or `makerAmountInBaseToken`

**Amount Selection Rules:**

**For BUY orders:**

* ✅ `makerAmountInQuoteToken` - Common (specify how much USDT to spend)
* ✅ `makerAmountInBaseToken` - Specify how many tokens to buy
* ❌ Both - Invalid

**For SELL orders:**

* ✅ `makerAmountInBaseToken` - Common (specify how many tokens to sell)
* ✅ `makerAmountInQuoteToken` - Specify how much USDT to receive
* ❌ Both - Invalid

**Examples:**

**Buy 100 USDT worth at $0.55:**

```python
order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price="0.55",
    makerAmountInQuoteToken="100"  # Spend 100 USDT
)
```

**Sell 50 YES tokens at market price:**

```python
order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.SELL,
    orderType=MARKET_ORDER,
    price="0",
    makerAmountInBaseToken="50"  # Sell 50 tokens
)
```

***

#### OrderData

Internal order data structure (used by OrderBuilder).

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.order`

```python
@dataclass
class OrderData:
    maker: str              # Maker address (multi-sig wallet)
    taker: str              # Taker address (ZERO_ADDRESS for public orders)
    tokenId: str            # Token ID
    makerAmount: str        # Maker amount in wei
    takerAmount: str        # Taker amount in wei
    side: int               # OrderSide
    feeRateBps: str         # Fee rate in basis points
    nonce: str              # Nonce (default "0")
    signer: str             # Signer address
    expiration: str         # Expiration timestamp (default "0" = no expiration)
    signatureType: int      # Signature type (POLY_GNOSIS_SAFE)
```

**Note:** This is an internal structure. Users should use `PlaceOrderDataInput` instead.

***

#### OrderDataInput

Simplified order input (internal use).

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.order`

```python
@dataclass
class OrderDataInput:
    marketId: int
    tokenId: str
    makerAmount: str  # Already calculated amount
    price: str
    side: int
    orderType: int
```

**Note:** This is used internally by `_place_order()`. Users should use `PlaceOrderDataInput`.

***

### Response Models

#### API Response Structure

All API methods return responses with this standard structure:

```python
class APIResponse:
    errno: int        # Error code (0 = success)
    errmsg: str       # Error message
    result: Result    # Result data
```

#### Result Types

**For single objects:**

```python
class Result:
    data: Any  # Single object (market, order, etc.)
```

**For lists/arrays:**

```python
class Result:
    list: List[Any]  # Array of objects
    total: int       # Total count (for pagination)
```

**Example Usage:**

```python
# Single object response
market_response = client.get_market(123)
if market_response.errno == 0:
    market = market_response.result.data  # Access via .data

# List response
markets_response = client.get_markets()
if markets_response.errno == 0:
    markets = markets_response.result.list  # Access via .list
    total = markets_response.result.total
```

***

### Market Data Models

#### Market Object

Returned by `get_market()` and `get_markets()`.

**Key Fields:**

| Field           | Type                    | Description                                                              |
| --------------- | ----------------------- | ------------------------------------------------------------------------ |
| `marketId`      | `int`                   | Market ID                                                                |
| `marketTitle`   | `str`                   | Market question/title                                                    |
| `status`        | `int`                   | Market status (see TopicStatus)                                          |
| `marketType`    | `int`                   | Market type (0=binary, 1=categorical)                                    |
| `conditionId`   | `str`                   | Blockchain condition ID (hex string)                                     |
| `quoteToken`    | `str`                   | Quote token address (e.g., USDT)                                         |
| `chainId`       | `str`                   | Blockchain chain ID                                                      |
| `volume`        | `str`                   | Trading volume                                                           |
| `yesTokenId`    | `str`                   | Token ID of Yes side                                                     |
| `noTokenId`     | `str`                   | Token ID of No side                                                      |
| `resultTokenId` | `str`                   | Token ID of Winning side                                                 |
| `yesLabel`      | `str`                   | Token Label of Yes side                                                  |
| `noLabel`       | `str`                   | Token Label of No side                                                   |
| `rules`         | `str`                   | Market Resolution Criteria                                               |
| `collection`    | `CollectionDataOpenAPI` | Collection information if this market belongs to a collection (optional) |
| `cutoffAt`      | `int`                   | The latest date to resolve the market                                    |
| `resolvedAt`    | `int`                   | The date that market resolved                                            |

**Example:**

```python
market = client.get_market(123).result.data

print(f"ID: {market.topic_id}")
print(f"Title: {market.topic_title}")
print(f"Status: {market.status}")  # 2 = ACTIVATED
print(f"Type: {market.topic_type}")  # 0 = BINARY
print(f"Condition: {market.condition_id}")
```

***

#### CollectionDataOpenAPI

Collection data structure for markets that belong to a collection (e.g., hourly BTC/ETH price markets).

**Module:** `opinion_api.models.collection_data_open_api`

**Fields:**

| Field       | Type                                | Required | Description                                  |
| ----------- | ----------------------------------- | -------- | -------------------------------------------- |
| `title`     | `str`                               | No       | Collection title (e.g., "ETH daily Markets") |
| `symbol`    | `str`                               | No       | Collection symbol (e.g., "ETH")              |
| `frequency` | `str`                               | No       | Collection frequency (e.g., "daily")         |
| `current`   | `CollectionMarketDataOpenAPI`       | No       | Current market in the collection             |
| `next`      | `List[CollectionMarketDataOpenAPI]` | No       | Upcoming markets in the collection           |

**Example:**

```python
from opinion_api.models.collection_data_open_api import CollectionDataOpenAPI

market = client.get_market(123).result.data
if market.collection:
    collection = market.collection
    print(f"Collection: {collection.title}")
    print(f"Symbol: {collection.symbol}")
    print(f"Frequency: {collection.frequency}")
    
    if collection.current:
        print(f"Current market ID: {collection.current.market_id}")
        print(f"Current period: {collection.current.period}")
    
    if collection.next:
        print(f"Upcoming markets: {len(collection.next)}")
        for next_market in collection.next:
            print(f"  - Market {next_market.market_id}: {next_market.period}")
```

#### CollectionMarketDataOpenAPI

Market data within a collection, representing a single market in a series (e.g., one day in a daily price series).

**Module:** `opinion_api.models.collection_market_data_open_api`

**Fields:**

| Field        | Type  | Required | Description                                     |
| ------------ | ----- | -------- | ----------------------------------------------- |
| `marketId`   | `int` | No       | Market ID                                       |
| `period`     | `str` | No       | Period identifier (e.g., "2026-01-25 17:00:00") |
| `startTime`  | `int` | No       | Period start timestamp (Unix timestamp)         |
| `endTime`    | `int` | No       | Period end timestamp (Unix timestamp)           |
| `startPrice` | `str` | No       | Starting price for the period                   |
| `endPrice`   | `str` | No       | Ending price for the period                     |

**Example:**

```python
from opinion_api.models.collection_market_data_open_api import CollectionMarketDataOpenAPI

market = client.get_market(123).result.data
if market.collection and market.collection.current:
    current = market.collection.current
    print(f"Market ID: {current.market_id}")
    print(f"Period: {current.period}")
    print(f"Start time: {current.start_time}")
    print(f"End time: {current.end_time}")
    print(f"Start price: {current.start_price}")
    print(f"End price: {current.end_price}")
```

***

#### Quote Token Object

Returned by `get_quote_tokens()`.

**Key Fields:**

| Field                | Type  | Description                        |
| -------------------- | ----- | ---------------------------------- |
| `quoteTokenAddress`  | `str` | Token contract address             |
| `decimal`            | `int` | Token decimals (e.g., 18 for USDT) |
| `ctfExchangeAddress` | `str` | CTF exchange contract address      |
| `chainId`            | `int` | Blockchain chain ID                |
| `quoteTokenName`     | `str` | Token name (e.g., "USDT")          |
| `symbol`             | `str` | Token symbol                       |

**Example:**

```python
tokens = client.get_quote_tokens().result.list

for token in tokens:
    print(f"{token.symbol}: {token.quote_token_address}")
    print(f"  Decimals: {token.decimal}")
    print(f"  Exchange: {token.ctf_exchange_address}")
```

***

#### Orderbook Object

Returned by `get_orderbook()`.

**Structure:**

```python
{
    "bids": [  # Buy orders
        {"price": "0.55", "amount": "100", ...},
        {"price": "0.54", "amount": "200", ...},
    ],
    "asks": [  # Sell orders
        {"price": "0.56", "amount": "150", ...},
        {"price": "0.57", "amount": "250", ...},
    ]
}
```

**Example:**

```python
book = client.get_orderbook("token_yes").result.data

# Best bid (highest buy price)
best_bid = book.bids[0] if book.bids else None
print(f"Best bid: ${best_bid['price']} x {best_bid['amount']}")

# Best ask (lowest sell price)
best_ask = book.asks[0] if book.asks else None
print(f"Best ask: ${best_ask['price']} x {best_ask['amount']}")

# Spread
if best_bid and best_ask:
    spread = float(best_ask['price']) - float(best_bid['price'])
    print(f"Spread: ${spread:.4f}")
```

***

### Constants

#### Signature Types

**Module:** `opinion_clob_sdk.chain.py_order_utils.model.signatures`

```python
EOA = 0               # Externally Owned Account (regular wallet)
POLY_PROXY = 1        # Polymarket proxy
POLY_GNOSIS_SAFE = 2  # Gnosis Safe (used by Opinion SDK)
```

**Usage:** Orders are signed with `POLY_GNOSIS_SAFE` signature type by default.

***

#### Address Constants

**Module:** `opinion_clob_sdk.chain.py_order_utils.constants`

```python
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
ZX = "0x"  # Hex prefix
```

**Usage:**

* `ZERO_ADDRESS` is used for `taker` field in public orders (anyone can fill)

***

#### Chain IDs

**Module:** `opinion_clob_sdk.sdk`

```python
CHAIN_ID_BNBCHAIN_MAINNET = 56
SUPPORTED_CHAIN_IDS = [56]  # BNB Chain mainnet
```

**Usage:**

```python
# Mainnet
client = Client(chain_id=56, ...)
```

***

#### Decimals

**Module:** `opinion_clob_sdk.sdk`

```python
MAX_DECIMALS = 18  # Maximum token decimals (ERC20 standard)
```

**Common Decimals:**

* USDT: 18 decimals
* BNB: 18 decimals
* Outcome tokens: Usually match quote token decimals

***

### Helper Functions

#### safe\_amount\_to\_wei()

Convert human-readable amount to wei units.

**Module:** `opinion_clob_sdk.sdk`

**Signature:**

```python
def safe_amount_to_wei(amount: float, decimals: int) -> int
```

**Parameters:**

* `amount` - Human-readable amount (e.g., `1.5`)
* `decimals` - Token decimals (e.g., `18` for USDT)

**Returns:** Integer amount in wei units

**Example:**

```python
from opinion_clob_sdk.sdk import safe_amount_to_wei

# Convert 10.5 USDT to wei (18 decimals)
amount_wei = safe_amount_to_wei(10.5, 18)
print(amount_wei)  # 105000000000000000000

# Convert 1 BNB to wei (18 decimals)
amount_wei = safe_amount_to_wei(1.0, 18)
print(amount_wei)  # 100000000000000000000
```

***

#### calculate\_order\_amounts()

Calculate maker and taker amounts for limit orders.

**Module:** `opinion_clob_sdk.chain.py_order_utils.utils`

**Signature:**

```python
def calculate_order_amounts(
    price: float,
    maker_amount: int,
    side: int,
    decimals: int
) -> Tuple[int, int]
```

**Parameters:**

* `price` - Order price (e.g., `0.55`)
* `maker_amount` - Maker amount in wei
* `side` - `OrderSide.BUY` or `OrderSide.SELL`
* `decimals` - Token decimals

**Returns:** Tuple of `(recalculated_maker_amount, taker_amount)`

**Example:**

```python
from opinion_clob_sdk.chain.py_order_utils.utils import calculate_order_amounts
from opinion_clob_sdk.chain.py_order_utils.model.sides import BUY

maker_amount = 100000000000000000000  # 100 USDT (18 decimals)
price = 0.55
side = BUY
decimals = 18

maker, taker = calculate_order_amounts(price, maker_amount, side, decimals)
print(f"Maker: {maker}, Taker: {taker}")
```

***

### Next Steps

* [**Methods**](/developer-guide/opinion-clob-python-sdk/api-references/methods): Full API Reference


# Methods

Complete reference for all methods available in the `Client` class.

### Overview

The `Client` class provides a unified interface for interacting with OPINION prediction markets. Methods are organized into these categories:

* **Market Data** - Query markets, prices, and orderbooks
* **Trading Operations** - Place and manage orders
* **User Data** - Access balances, positions, and trades
* **Smart Contract Operations** - Blockchain interactions (split, merge, redeem)

### Response Format

All API methods return responses with this structure:

```python
response = client.get_markets()

# Check success
if response.errno == 0:
    # Success - access data
    data = response.result.data  # For single objects
    # or
    items = response.result.list  # For arrays
else:
    # Error - check error message
    print(f"Error {response.errno}: {response.errmsg}")
```

**Response fields:**

* `errno` - Error code (`0` = success, non-zero = error)
* `errmsg` - Error message string
* `result` - Contains `data` (single object) or `list` (array of objects)

### Market Data Methods

#### get\_markets()

Get a paginated list of prediction markets.

**Signature:**

```python
def get_markets(
    topic_type: Optional[TopicType] = None,
    page: int = 1,
    limit: int = 20,
    status: Optional[TopicStatusFilter] = None
) -> Any
```

**Parameters:**

| Name         | Type                | Required | Default | Description                                                           |
| ------------ | ------------------- | -------- | ------- | --------------------------------------------------------------------- |
| `topic_type` | `TopicType`         | No       | `None`  | Filter by market type (`TopicType.BINARY` or `TopicType.CATEGORICAL`) |
| `page`       | `int`               | No       | `1`     | Page number (≥ 1)                                                     |
| `limit`      | `int`               | No       | `20`    | Items per page (1-20)                                                 |
| `status`     | `TopicStatusFilter` | No       | `None`  | Filter by status (`ACTIVATED`, `RESOLVED`, or `ALL`)                  |

**Returns:** API response with `result.list` containing market objects

**Example:**

```python
from opinion_clob_sdk.model import TopicType, TopicStatusFilter

# Get all active binary markets
response = client.get_markets(
    topic_type=TopicType.BINARY,
    status=TopicStatusFilter.ACTIVATED,
    page=1,
    limit=10
)

if response.errno == 0:
    markets = response.result.list
    for market in markets:
        print(f"{market.market_id}: {market.market_title}")
```

**Raises:**

* `InvalidParamError` - If page < 1 or limit not in range \[1, 20]

***

#### get\_market()

Get detailed information about a specific market.

**Signature:**

```python
def get_market(market_id: int, use_cache: bool = True) -> Any
```

**Parameters:**

| Name        | Type   | Required | Default | Description                             |
| ----------- | ------ | -------- | ------- | --------------------------------------- |
| `market_id` | `int`  | Yes      | -       | Market ID to query                      |
| `use_cache` | `bool` | No       | `True`  | Whether to use cached data if available |

**Returns:** API response with `result.data` containing market details

**Example:**

```python
response = client.get_market(market_id=123, use_cache=True)

if response.errno == 0:
    market = response.result.data
    print(f"Title: {market.market_title}")
    print(f"Status: {market.status}")
    print(f"Condition ID: {market.condition_id}")
    print(f"Quote Token: {market.quote_token}")
```

**Caching:**

* Cache duration controlled by `market_cache_ttl` (default: 300 seconds)
* Set `use_cache=False` to force fresh data
* Set `market_cache_ttl=0` in Client constructor to disable caching

**Raises:**

* `InvalidParamError` - If market\_id is missing or invalid
* `OpenApiError` - If API request fails

***

#### get\_categorical\_market()

Get detailed information about a categorical market (multi-outcome).

**Signature:**

```python
def get_categorical_market(market_id: int) -> Any
```

**Parameters:**

| Name        | Type  | Required | Description           |
| ----------- | ----- | -------- | --------------------- |
| `market_id` | `int` | Yes      | Categorical market ID |

**Returns:** API response with categorical market data

**Example:**

```python
response = client.get_categorical_market(market_id=456)

if response.errno == 0:
    market = response.result.data
    print(f"Options: {market.options}")  # Multiple outcomes
```

***

#### get\_quote\_tokens()

Get list of supported quote tokens (collateral currencies).

**Signature:**

```python
def get_quote_tokens(use_cache: bool = True) -> Any
```

**Parameters:**

| Name        | Type   | Required | Default | Description                |
| ----------- | ------ | -------- | ------- | -------------------------- |
| `use_cache` | `bool` | No       | `True`  | Whether to use cached data |

**Returns:** API response with `result.list` containing quote token objects

**Example:**

```python
response = client.get_quote_tokens()

if response.errno == 0:
    tokens = response.result.list
    for token in tokens:
        print(f"Token: {token.quote_token_address}")
        print(f"Decimals: {token.decimal}")
        print(f"Exchange: {token.ctf_exchange_address}")
```

**Caching:**

* Default TTL: 3600 seconds (1 hour)
* Controlled by `quote_tokens_cache_ttl` parameter

***

#### get\_orderbook()

Get orderbook (bids and asks) for a specific token.

**Signature:**

```python
def get_orderbook(token_id: str) -> Any
```

**Parameters:**

| Name       | Type  | Required | Description                                 |
| ---------- | ----- | -------- | ------------------------------------------- |
| `token_id` | `str` | Yes      | Token ID (e.g., "token\_yes", "token\_123") |

**Returns:** API response with orderbook data

**Example:**

```python
response = client.get_orderbook(token_id="token_yes")

if response.errno == 0:
    book = response.result.data
    print("Bids (buy orders):")
    for bid in book.bids[:5]:  # Top 5
        print(f"  Price: {bid.price}, Size: {bid.size}")

    print("Asks (sell orders):")
    for ask in book.asks[:5]:
        print(f"  Price: {ask.price}, Size: {ask.size}")
```

**Raises:**

* `InvalidParamError` - If token\_id is missing
* `OpenApiError` - If API request fails

***

#### get\_latest\_price()

Get the current/latest price for a token.

**Signature:**

```python
def get_latest_price(token_id: str) -> Any
```

**Parameters:**

| Name       | Type  | Required | Description |
| ---------- | ----- | -------- | ----------- |
| `token_id` | `str` | Yes      | Token ID    |

**Returns:** API response with latest price data

**Example:**

```python
response = client.get_latest_price(token_id="token_yes")

if response.errno == 0:
    price_data = response.result.data
    print(f"Latest price: {price_data.price}")
    print(f"Timestamp: {price_data.timestamp}")
```

***

#### get\_price\_history()

Get historical price data (candlestick/OHLCV) for a token.

**Signature:**

```python
def get_price_history(
    token_id: str,
    interval: str = "1h",
    start_at: Optional[int] = None,
    end_at: Optional[int] = None
) -> Any
```

**Parameters:**

| Name       | Type  | Required | Default | Description                                  |
| ---------- | ----- | -------- | ------- | -------------------------------------------- |
| `token_id` | `str` | Yes      | -       | Token ID                                     |
| `interval` | `str` | No       | `"1h"`  | Time interval: `1m`, `1h`, `1d`, `1w`, `max` |
| `start_at` | `int` | No       | `None`  | Start timestamp (Unix seconds)               |
| `end_at`   | `int` | No       | `None`  | End timestamp (Unix seconds)                 |

**Returns:** API response with price history data

**Example:**

```python
import time

# Get last 24 hours of hourly data
end_time = int(time.time())
start_time = end_time - (24 * 3600)  # 24 hours ago

response = client.get_price_history(
    token_id="token_yes",
    interval="1h",
    start_at=start_time,
    end_at=end_time
)

if response.errno == 0:
    candles = response.result.data
    for candle in candles:
        print(f"Time: {candle.timestamp}, Price: {candle.close}")
```

***

#### get\_fee\_rates()

Get trading fee rates for a token.

**Signature:**

```python
def get_fee_rates(token_id: str) -> Any
```

**Parameters:**

| Name       | Type  | Required | Description |
| ---------- | ----- | -------- | ----------- |
| `token_id` | `str` | Yes      | Token ID    |

**Returns:** API response with fee rate data

**Example:**

```python
response = client.get_fee_rates(token_id="token_yes")

if response.errno == 0:
    fees = response.result.data
    print(f"Maker fee: {fees.maker_fee}")
    print(f"Taker fee: {fees.taker_fee}")
```

***

### Trading Operations

#### place\_order()

Place a market or limit order.

**Signature:**

```python
def place_order(
    data: PlaceOrderDataInput,
    check_approval: bool = False
) -> Any
```

**Parameters:**

| Name             | Type                  | Required | Description                                         |
| ---------------- | --------------------- | -------- | --------------------------------------------------- |
| `data`           | `PlaceOrderDataInput` | Yes      | Order parameters (see below)                        |
| `check_approval` | `bool`                | No       | Whether to check and enable trading approvals first |

**PlaceOrderDataInput fields:**

| Field                     | Type             | Required | Description                                                |
| ------------------------- | ---------------- | -------- | ---------------------------------------------------------- |
| `marketId`                | `int`            | Yes      | Market ID                                                  |
| `tokenId`                 | `str`            | Yes      | Token ID to trade                                          |
| `side`                    | `OrderSide`      | Yes      | `OrderSide.BUY` or `OrderSide.SELL`                        |
| `orderType`               | `int`            | Yes      | `MARKET_ORDER` (1) or `LIMIT_ORDER` (2)                    |
| `price`                   | `str`            | Yes\*    | Price string (required for limit orders, `"0"` for market) |
| `makerAmountInQuoteToken` | `int` or `float` | No\*\*   | Amount in quote token (e.g., 100 for 100 USDT)             |
| `makerAmountInBaseToken`  | `int` or `float` | No\*\*   | Amount in base token (e.g., 50 for 50 YES tokens)          |

\* Price is required for limit orders, set to `"0"` for market orders \*\* Must provide exactly ONE of `makerAmountInQuoteToken` or `makerAmountInBaseToken`

**Returns:** API response with order result

**Examples:**

**Limit Buy Order (using quote token):**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order import PlaceOrderDataInput
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.BUY,
    orderType=LIMIT_ORDER,
    price="0.55",  # Buy at $0.55 or better
    makerAmountInQuoteToken=100  # Spend 100 USDT (int or float)
)

result = client.place_order(order, check_approval=True)
if result.errno == 0:
    print(f"Order placed: {result.result.data.order_id}")
```

**Market Sell Order (using base token):**

```python
from opinion_clob_sdk.chain.py_order_utils.model.order_type import MARKET_ORDER

order = PlaceOrderDataInput(
    marketId=123,
    tokenId="token_yes",
    side=OrderSide.SELL,
    orderType=MARKET_ORDER,
    price="0",  # Market orders don't need price
    makerAmountInBaseToken=50  # Sell 50 YES tokens (int or float)
)

result = client.place_order(order)
```

**Raises:**

* `InvalidParamError` - If parameters are invalid or missing
* `OpenApiError` - If API request fails or chain\_id mismatch

***

#### place\_orders\_batch()

Place multiple orders in a single batch operation.

**Signature:**

```python
def place_orders_batch(
    orders: List[PlaceOrderDataInput],
    check_approval: bool = False
) -> List[Any]
```

**Parameters:**

| Name             | Type                        | Required | Description                                          |
| ---------------- | --------------------------- | -------- | ---------------------------------------------------- |
| `orders`         | `List[PlaceOrderDataInput]` | Yes      | A list containing the order details.                 |
| `check_approval` | `bool`                      | No       | Determines if approvals are verified for all orders. |

**Returns:** List of results with `success`, `result`, and `error` fields for each order

**Example:**

```python
orders = [
    PlaceOrderDataInput(marketId=123, tokenId="token_yes", side=OrderSide.BUY, ...),
    PlaceOrderDataInput(marketId=124, tokenId="token_no", side=OrderSide.SELL, ...),
]

results = client.place_orders_batch(orders, check_approval=True)

for i, result in enumerate(results):
    if result['success']:
        print(f"Order {i}: Success - {result['result']}")
    else:
        print(f"Order {i}: Failed - {result['error']}")
```

***

#### cancel\_order()

Cancel a single order by order ID.

**Signature:**

```python
def cancel_order(order_id: str) -> Any
```

**Parameters:**

| Name       | Type  | Required | Description        |
| ---------- | ----- | -------- | ------------------ |
| `order_id` | `str` | Yes      | Order ID to cancel |

**Returns:** API response for cancellation

**Example:**

```python
result = client.cancel_order(order_id="order_123")

if result.errno == 0:
    print("Order cancelled successfully")
```

***

#### cancel\_orders\_batch()

Cancel multiple orders in a batch.

**Signature:**

```python
def cancel_orders_batch(order_ids: List[str]) -> List[Any]
```

**Parameters:**

| Name        | Type        | Required | Description                 |
| ----------- | ----------- | -------- | --------------------------- |
| `order_ids` | `List[str]` | Yes      | List of order IDs to cancel |

**Returns:** List of cancellation results for each order

**Example:**

```python
order_ids = ["order_123", "order_456", "order_789"]
results = client.cancel_orders_batch(order_ids)

for i, result in enumerate(results):
    if result['success']:
        print(f"Cancelled: {order_ids[i]}")
    else:
        print(f"Failed: {order_ids[i]} - {result['error']}")
```

***

#### cancel\_all\_orders()

Cancel all open orders, optionally filtered by market and/or side.

**Signature:**

```python
def cancel_all_orders(
    market_id: Optional[int] = None,
    side: Optional[OrderSide] = None
) -> Dict[str, Any]
```

**Parameters:**

| Name        | Type        | Required | Description                                  |
| ----------- | ----------- | -------- | -------------------------------------------- |
| `market_id` | `int`       | No       | Filter by market ID (all markets if None)    |
| `side`      | `OrderSide` | No       | Filter by side (BUY/SELL, all sides if None) |

**Returns:** Dictionary with cancellation summary:

```python
{
    'total_orders': int,      # Total orders found matching filter
    'cancelled': int,         # Successfully cancelled count
    'failed': int,            # Failed cancellation count
    'results': List[dict]     # Detailed results for each order
}
```

**Example:**

```python
# Cancel all open orders across all markets
result = client.cancel_all_orders()
print(f"Cancelled {result['cancelled']} out of {result['total_orders']} orders")

# Cancel all BUY orders in market 123
result = client.cancel_all_orders(market_id=123, side=OrderSide.BUY)
print(f"Success: {result['cancelled']}, Failed: {result['failed']}")

# Cancel all orders in market 456 (both sides)
result = client.cancel_all_orders(market_id=456)
```

***

#### get\_my\_orders()

Get user's orders with optional filters.

**Signature:**

```python
def get_my_orders(
    market_id: int = 0,
    status: str = "",
    limit: int = 10,
    page: int = 1
) -> Any
```

**Parameters:**

| Name        | Type  | Required | Default | Description                                            |
| ----------- | ----- | -------- | ------- | ------------------------------------------------------ |
| `market_id` | `int` | No       | `0`     | Filter by market (0 = all markets)                     |
| `status`    | `str` | No       | `""`    | Filter by status (e.g., "open", "filled", "cancelled") |
| `limit`     | `int` | No       | `10`    | Items per page                                         |
| `page`      | `int` | No       | `1`     | Page number                                            |

**Returns:** API response with `result.list` containing orders

**Example:**

```python
# Get all open orders
response = client.get_my_orders(status="open", limit=50)

if response.errno == 0:
    orders = response.result.list
    for order in orders:
        print(f"Order {order.order_id}: {order.side} @ {order.price}")
```

***

#### get\_order\_by\_id()

Get details for a specific order by ID.

**Signature:**

```python
def get_order_by_id(order_id: str) -> Any
```

**Parameters:**

| Name       | Type  | Required | Description |
| ---------- | ----- | -------- | ----------- |
| `order_id` | `str` | Yes      | Order ID    |

**Returns:** API response with order details

**Example:**

```python
response = client.get_order_by_id(order_id="order_123")

if response.errno == 0:
    order = response.result.data
    print(f"Status: {order.status}")
    print(f"Filled: {order.filled_amount}/{order.maker_amount}")
```

***

### User Data Methods

#### get\_my\_balances()

Get user's token balances.

**Signature:**

```python
def get_my_balances() -> Any
```

**Returns:** API response with `result.data.balances` containing list of balance objects

**Example:**

```python
response = client.get_my_balances()

if response.errno == 0:
    balance_data = response.result.data
    balances = balance_data.balances  # List of quote token balances
    for balance in balances:
        print(f"Token: {balance.quote_token}")
        print(f"  Available: {balance.available_balance}")
        print(f"  Frozen: {balance.frozen_balance}")
        print(f"  Total: {balance.total_balance}")
```

***

#### get\_my\_positions()

Get user's open positions across markets.

**Signature:**

```python
def get_my_positions(
    market_id: int = 0,
    page: int = 1,
    limit: int = 10
) -> Any
```

**Parameters:**

| Name        | Type  | Required | Default | Description                |
| ----------- | ----- | -------- | ------- | -------------------------- |
| `market_id` | `int` | No       | `0`     | Filter by market (0 = all) |
| `page`      | `int` | No       | `1`     | Page number                |
| `limit`     | `int` | No       | `10`    | Items per page             |

**Returns:** API response with `result.list` containing positions

**Example:**

```python
response = client.get_my_positions(limit=50)

if response.errno == 0:
    positions = response.result.list
    for pos in positions:
        print(f"Market {pos.market_id}: {pos.market_title}")
        print(f"  Shares: {pos.shares_owned} ({pos.outcome_side_enum})")
        print(f"  Value: {pos.current_value_in_quote_token}")
        print(f"  P&L: {pos.unrealized_pnl} ({pos.unrealized_pnl_percent}%)")
```

***

#### get\_my\_trades()

Get user's trade history.

**Signature:**

```python
def get_my_trades(
    market_id: Optional[int] = None,
    page: int = 1,
    limit: int = 10
) -> Any
```

**Parameters:**

| Name        | Type  | Required | Default | Description      |
| ----------- | ----- | -------- | ------- | ---------------- |
| `market_id` | `int` | No       | `None`  | Filter by market |
| `page`      | `int` | No       | `1`     | Page number      |
| `limit`     | `int` | No       | `10`    | Items per page   |

**Returns:** API response with `result.list` containing trade history

**Example:**

```python
response = client.get_my_trades(market_id=123, limit=20)

if response.errno == 0:
    trades = response.result.list
    for trade in trades:
        print(f"{trade.created_at}: {trade.side} {trade.shares} shares @ {trade.price}")
        print(f"  Amount: {trade.amount}, Fee: {trade.fee}")
        print(f"  Status: {trade.status_enum}")
```

***

### Smart Contract Operations

These methods interact directly with the blockchain and **require gas (BNB)**.

#### enable\_trading()

Enable trading by approving quote tokens for the exchange contract. Must be called once before placing orders or doing split/merge/redeem operations.

**Signature:**

```python
def enable_trading() -> Tuple[Any, Any, Any]
```

**Returns:** Tuple of `(tx_hash, tx_receipt, contract_event)`

**Example:**

```python
tx_hash, receipt, event = client.enable_trading()
print(f"Trading enabled! TX: {tx_hash.hex()}")
```

**Notes:**

* Only needs to be called once (result is cached for `enable_trading_check_interval` seconds)
* Automatically called by `split()`, `merge()`, `redeem()` if `check_approval=True`

***

#### split()

Convert collateral tokens (e.g., USDT) into outcome tokens (e.g., YES + NO).

**Signature:**

```python
def split(
    market_id: int,
    amount: int,
    check_approval: bool = True
) -> Tuple[Any, Any, Any]
```

**Parameters:**

| Name             | Type   | Required | Description                                                             |
| ---------------- | ------ | -------- | ----------------------------------------------------------------------- |
| `market_id`      | `int`  | Yes      | Market ID                                                               |
| `amount`         | `int`  | Yes      | Amount in wei (e.g., 105000000000000000000 for 1 USDT with 18 decimals) |
| `check_approval` | `bool` | No       | Auto-call `enable_trading()` if needed                                  |

**Returns:** Tuple of `(tx_hash, tx_receipt, contract_event)`

**Example:**

```python
# Split 10 USDT (18 decimals) into YES + NO tokens
amount_wei = 10 * 10**18  # 10 USDT

tx_hash, receipt, event = client.split(
    market_id=123,
    amount=amount_wei,
    check_approval=True
)

print(f"Split complete! TX: {tx_hash.hex()}")
print(f"Gas used: {receipt.gasUsed}")
```

**Raises:**

* `InvalidParamError` - If market\_id or amount is invalid
* `OpenApiError` - If market is not in valid state or chain mismatch
* Blockchain errors - If insufficient balance or gas

***

#### merge()

Convert outcome tokens back into collateral tokens.

**Signature:**

```python
def merge(
    market_id: int,
    amount: int,
    check_approval: bool = True
) -> Tuple[Any, Any, Any]
```

**Parameters:**

| Name             | Type   | Required | Description                            |
| ---------------- | ------ | -------- | -------------------------------------- |
| `market_id`      | `int`  | Yes      | Market ID                              |
| `amount`         | `int`  | Yes      | Amount of outcome tokens in wei        |
| `check_approval` | `bool` | No       | Auto-call `enable_trading()` if needed |

**Returns:** Tuple of `(tx_hash, tx_receipt, contract_event)`

**Example:**

```python
# Merge 5 YES + 5 NO tokens back to 5 USDT
amount_wei = 5 * 10**18

tx_hash, receipt, event = client.merge(
    market_id=123,
    amount=amount_wei
)

print(f"Merge complete! TX: {tx_hash.hex()}")
```

***

#### redeem()

Claim winnings after a market is resolved. Redeems winning outcome tokens for collateral.

**Signature:**

```python
def redeem(
    market_id: int,
    check_approval: bool = True
) -> Tuple[Any, Any, Any]
```

**Parameters:**

| Name             | Type   | Required | Description                            |
| ---------------- | ------ | -------- | -------------------------------------- |
| `market_id`      | `int`  | Yes      | Resolved market ID                     |
| `check_approval` | `bool` | No       | Auto-call `enable_trading()` if needed |

**Returns:** Tuple of `(tx_hash, tx_receipt, contract_event)`

**Example:**

```python
# Redeem winnings from resolved market
tx_hash, receipt, event = client.redeem(market_id=123)

print(f"Winnings redeemed! TX: {tx_hash.hex()}")
```

**Raises:**

* `InvalidParamError` - If market\_id is invalid
* `OpenApiError` - If market is not resolved or chain mismatch
* `NoPositionsToRedeem` - If no winning positions to claim

***

### Error Handling

#### Exceptions

The SDK defines these custom exceptions:

```python
from opinion_clob_sdk import InvalidParamError, OpenApiError
from opinion_clob_sdk.chain.exception import (
    BalanceNotEnough,
    NoPositionsToRedeem,
    InsufficientGasBalance
)
```

| Exception                | Description                              |
| ------------------------ | ---------------------------------------- |
| `InvalidParamError`      | Invalid method parameters                |
| `OpenApiError`           | API communication or response errors     |
| `BalanceNotEnough`       | Insufficient token balance for operation |
| `NoPositionsToRedeem`    | No winning positions to redeem           |
| `InsufficientGasBalance` | Not enough BNB for gas fees              |

#### Example Error Handling

```python
try:
    result = client.place_order(order_data)
    if result.errno == 0:
        print("Success!")
    else:
        print(f"API Error: {result.errmsg}")

except InvalidParamError as e:
    print(f"Invalid parameter: {e}")
except OpenApiError as e:
    print(f"API error: {e}")
except BalanceNotEnough as e:
    print(f"Insufficient balance: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")
```


# Support


# FAQ

Common questions and answers about the Opinion CLOB SDK.

### Installation & Setup

#### Q: What Python versions are supported?

**A:** Python 3.8 and higher. The SDK is tested on Python 3.8 through 3.13.

```bash
python --version  # Must be 3.8+
```

***

#### Q: How do I install the SDK?

**A:** Use pip:

```bash
pip install opinion_clob_sdk
```

See Installation Guide for details.

***

#### Q: Where do I get API credentials?

**A:** You need:

1. **API Key** - Fill out this [short application form ](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII)
2. **Private Key** - From your EVM wallet (e.g., MetaMask)
3. **Multi-sig Address** - Your wallet address (visible in "MyProfile")
4. **RPC URL** - Get from Nodereal, Alchemy, drpc etc..

Never share your private key or API key!

***

#### Q: What's the difference between `private_key` and `multi_sig_addr`?

**A:**

* **`private_key`**: The **signer** wallet that signs orders/transactions (hot wallet)
* **`multi_sig_addr`**: The **assets** wallet that holds funds/positions (can be cold wallet)

They can be the same address, or different for security (hot wallet signs for cold wallet).

**Example:**

```python
client = Client(
    private_key='0x...',      # Hot wallet private key (signs orders)
    multi_sig_addr='0x...'    # Cold wallet address (holds assets)
)
```

***

### Configuration

#### Q: Which chain IDs are supported?

**A:** Only BNB blockchain:

* **BNB Mainnet**: `chain_id=56` (production)

```python
# Mainnet
client = Client(chain_id=56, ...)
```

***

#### Q: How do I configure caching?

**A:** Use these parameters when creating the Client:

```python
client = Client(
    # ... other params ...
    market_cache_ttl=300,        # Cache markets for 5 minutes (default)
    quote_tokens_cache_ttl=3600, # Cache tokens for 1 hour (default)
    enable_trading_check_interval=3600  # Cache approval checks for 1 hour
)
```

Set to `0` to disable caching:

```python
client = Client(
    # ...
    market_cache_ttl=0  # Always fetch fresh data
)
```

***

### Trading

#### Q: What's the difference between market and limit orders?

**A:**

| Feature         | Market Order        | Limit Order                         |
| --------------- | ------------------- | ----------------------------------- |
| **Execution**   | Immediate           | When price reached                  |
| **Price**       | Best available      | Your specified price or better      |
| **Guarantee**   | Fills immediately\* | May not fill                        |
| **Price field** | Set to "0"          | Set to desired price (e.g., "0.55") |

\* If sufficient liquidity exists

**Examples:**

```python
# Market order - executes now at best price
market = PlaceOrderDataInput(
    orderType=MARKET_ORDER,
    price="0",
    makerAmountInQuoteToken="100"
)

# Limit order - waits for price $0.55 or better
limit = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    price="0.55",
    makerAmountInQuoteToken="100"
)
```

***

#### Q: Should I use `makerAmountInQuoteToken` or `makerAmountInBaseToken`?

**A:** Depends on order side:

**For BUY orders:**

* ✅ **Recommended**: `makerAmountInQuoteToken` (specify how much USDT to spend)
* Alternative: `makerAmountInBaseToken` (specify how many tokens to buy)

**For SELL orders:**

* ✅ **Recommended**: `makerAmountInBaseToken` (specify how many tokens to sell)
* Alternative: `makerAmountInQuoteToken` (specify how much USDT to receive)

**Rules:**

* ❌ Cannot specify both
* ❌ Market BUY cannot use `makerAmountInBaseToken`
* ❌ Market SELL cannot use `makerAmountInQuoteToken`

***

#### Q: Do I need to call `enable_trading()` before every order?

**A:** No, only once! The SDK caches the result for `enable_trading_check_interval` seconds (default 1 hour).

**Option 1: Manual (recommended for multiple orders)**

```python
client.enable_trading()  # Call once

# Place many orders without checking again
client.place_order(order1)
client.place_order(order2)
client.place_order(order3)
```

**Option 2: Automatic (convenient for single orders)**

```python
# Automatically checks and enables if needed
client.place_order(order, check_approval=True)
```

***

#### Q: How do I cancel all my open orders?

**A:** Use `cancel_all_orders()`:

```python
# Cancel all orders across all markets
result = client.cancel_all_orders()
print(f"Cancelled {result['cancelled_count']} orders")

# Cancel only orders in a specific market
result = client.cancel_all_orders(market_id=123)

# Cancel only BUY orders in a market
result = client.cancel_all_orders(market_id=123, side=OrderSide.BUY)
```

***

### Smart Contracts

#### Q: What's the difference between split, merge, and redeem?

**A:**

| Operation  | Purpose                | When to Use                        | Gas Required |
| ---------- | ---------------------- | ---------------------------------- | ------------ |
| **split**  | USDT → YES + NO tokens | Before trading (create positions)  | ✅ Yes        |
| **merge**  | YES + NO → USDT        | Exit position on unresolved market | ✅ Yes        |
| **redeem** | Winning tokens → USDT  | Claim winnings after resolution    | ✅ Yes        |

**Examples:**

```python
# 1. Split 10 USDT into 10 YES + 10 NO tokens
client.split(market_id=123, amount=10_000000)  # 6 decimals for USDT

# 2. Trade tokens (no gas, signed orders)
client.place_order(...)  # Sell some YES tokens

# 3a. Market still open: Merge remaining tokens back to USDT
client.merge(market_id=123, amount=5_000000)  # Merge 5 YES + 5 NO → 5 USDT

# 3b. Market resolved: Redeem winning tokens
client.redeem(market_id=123)  # Convert winning tokens → USDT
```

***

#### Q: Why do I need BNB if orders are gas-free?

**A:** BNB is needed for **blockchain operations**:

**Gas-free (signed orders):**

* ✅ `place_order()` - No BNB needed
* ✅ `cancel_order()` - No BNB needed
* ✅ All GET methods - No BNB needed

**Requires** BN&#x42;**:**

* ⛽ `enable_trading()` - On-chain approval
* ⛽ `split()` - On-chain transaction
* ⛽ `merge()` - On-chain transaction
* ⛽ `redeem()` - On-chain transaction

**How much** BN&#x42;**?** Usually $0.005-0.05 per transaction on BNB Chain.

***

#### Q: Can I split without calling `enable_trading()`?

**A:** Yes, but it will fail without approval. Use `check_approval=True`:

```python
# Option 1: Enable first (manual)
client.enable_trading()
client.split(market_id=123, amount=1000000, check_approval=False)

# Option 2: Auto-enable (recommended)
client.split(market_id=123, amount=1000000, check_approval=True)
```

The same applies to `merge()` and `redeem()`.

***

### Errors

#### Q: What does `InvalidParamError` mean?

**A:** Your method parameters are invalid. Common causes:

```python
# ✗ Price = 0 for limit order
order = PlaceOrderDataInput(orderType=LIMIT_ORDER, price="0", ...)
# Error: Price must be positive for limit orders

# ✗ Amount below minimum
order = PlaceOrderDataInput(makerAmountInQuoteToken="0.5", ...)
# Error: makerAmountInQuoteToken must be at least 1

# ✗ Wrong amount field for market buy
order = PlaceOrderDataInput(
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    makerAmountInBaseToken="100"  # Should use makerAmountInQuoteToken
)
# Error: makerAmountInBaseToken is not allowed for market buy

# ✗ Page < 1
markets = client.get_markets(page=0)
# Error: page must be >= 1
```

***

#### Q: What does `OpenApiError` mean?

**A:** API communication or business logic error. Common causes:

```python
# Chain ID mismatch
# Your client is on chain 8453 but market is on chain 56
client.place_order(order)  # Error: Cannot place order on different chain

# Market not active
client.split(market_id=999)  # Error: Cannot split on non-activated market

# Quote token not found
# Token not supported for your chain
```

Check:

1. `response.errno != 0` → API returned error
2. `response.errmsg` → Error message
3. Chain ID matches between client and market

***

#### Q: What does `errno != 0` mean in responses?

**A:** The API returned an error.

**Success:**

```python
response = client.get_markets()
if response.errno == 0:
    # Success! Access data
    markets = response.result.list
```

**Error:**

```python
response = client.get_market(99999)  # Non-existent market
if response.errno != 0:
    # Error occurred
    print(f"Error {response.errno}: {response.errmsg}")
    # Example: "Error 404: Market not found"
```

**Always check `errno` before accessing `result`.**

***

### Performance

#### Q: Why are my API calls slow?

**A:** Possible reasons:

1. **No caching** - Enable caching for better performance:

   ```python
   client = Client(
       market_cache_ttl=300,        # 5 minutes
       quote_tokens_cache_ttl=3600  # 1 hour
   )
   ```
2. **Slow RPC** - Use a faster provider:

   ```python
   # Slow: Public RPC
   rpc_url='https://some.slow.rpc.io'

   # Fast: Private RPC (Nodereal, dRPC)
   rpc_url='https://bsc.nodereal.io'
   ```
3. **Too many calls** - Use batch operations:

   ```python
   # Slow: One at a time
   for order in orders:
       client.place_order(order)

   # Fast: Batch
   client.place_orders_batch(orders)
   ```

***

#### Q: How do I reduce API calls?

**A:** Use caching and batch operations:

**Caching:**

```python
# Enable caching
client = Client(market_cache_ttl=300, ...)

# First call: Fetches from API
market = client.get_market(123)

# Second call within 5 minutes: Returns cached data
market = client.get_market(123, use_cache=True)  # Fast!

# Force fresh data
market = client.get_market(123, use_cache=False)
```

**Batch operations:**

```python
# Place multiple orders
results = client.place_orders_batch(orders)

# Cancel multiple orders
results = client.cancel_orders_batch(order_ids)
```

***

### Data & Precision

#### Q: How do I convert USDT amount to wei?

**A:** Use `safe_amount_to_wei()`:

```python
from opinion_clob_sdk.sdk import safe_amount_to_wei

# USDT has 18 decimals
amount_wei = safe_amount_to_wei(10.5, 18)
print(amount_wei) # 105000000000000000000

# Use in split
client.split(market_id=123, amount=amount_wei)
```

**Common decimals:**

* USDT: 18 decimals
* BNB: 18 decimals
* Outcome tokens: Same as quote token

***

#### Q: How are prices formatted?

**A:** Prices are strings with up to 2 decimal places:

```python
# Valid prices
"0.5"    # ✓ 50 cents
"0.55"   # ✓ 55 cents
"0.555"   # ✓ 55.5 cents 
"1"      # ✓ $1.00
"1.00"   # ✓ $1.00

# Invalid
"0.5555"  # ✗ Too many decimals
0.5      # ✗ Must be string
```

***

#### Q: What token amounts are in the API responses?

**A:** Amounts are in **wei units** (smallest unit).

**Example:**

```python
balance = client.get_my_balances().result.list[0]
print(balance.amount)  # e.g., "105000000000000000000" (not "10.5")

# Convert to human-readable
decimals = 18  # USDT decimals
amount_usdt = int(balance.amount) / (10 ** decimals)
print(f"{amount_usdt} USDT")  # "10.5 USDT"
```

***

### Troubleshooting

#### Q: "ModuleNotFoundError: No module named 'opinion\_clob\_sdk'"

**A:** SDK not installed. Install it:

```bash
pip install opinion_clob_sdk

# Verify installation
python -c "import opinion_clob_sdk; print(opinion_clob_sdk.__version__)"
```

***

#### Q: "InvalidParamError: chain\_id must be one of \[56]"

**A:** You're using an unsupported chain ID. Use BNB mainnet:

```python
# ✓ BNB Chain Mainnet
client = Client(chain_id=56, ...)

# ✗ Unsupported
client = Client(chain_id=1, ...)  # Ethereum mainnet not supported
```

***

#### Q: "OpenApiError: Cannot place order on different chain"

**A:** Your client and market are on different chains.

**Fix:** Ensure client chain\_id matches market chain\_id:

```python
# Check market's chain
market = client.get_market(123).result.data
print(f"Market chain: {market.chain_id}")

# Ensure client matches
client = Client(chain_id=int(market.chain_id), ...)
```

***

#### Q: "BalanceNotEnough" error when calling split/merge

**A:** Insufficient token balance.

**For split:** Need enough USDT

```python
# Check balance first
balances = client.get_my_balances().result.list
usdt_balance = next(b for b in balances if b.token.lower() == 'usdt')
print(f"USDT balance: {usdt_balance.amount}")

# Ensure you have enough
amount_to_split = 10_000000  # 10 USDT
if int(usdt_balance.amount) >= amount_to_split:
    client.split(market_id=123, amount=amount_to_split)
```

**For merge:** Need equal amounts of both outcome tokens

***

#### Q: "InsufficientGasBalance" error

**A:** Not enough BNB for gas fees.

**Fix:** Add BNB to your signer wallet:

```python
# Check which wallet needs BNB
print(f"Signer address: {client.contract_caller.signer.address()}")

# Send BNB to this address
# Usually $1-5 worth is enough for many transactions
```


# Troubleshooting

Solutions to common issues when using the Opinion CLOB SDK.

### Installation Issues

#### ImportError: No module named 'opinion\_clob\_sdk'

**Problem:**

```python
import opinion_clob_sdk
# ModuleNotFoundError: No module named 'opinion_clob_sdk'
```

**Solutions:**

1. **Install the SDK:**

   ```bash
   pip install opinion_clob_sdk
   ```
2. **Verify installation:**

   ```bash
   pip list | grep opinion
   python -c "import opinion_clob_sdk; print(opinion_clob_sdk.__version__)"
   ```
3. **Check Python environment:**

   ```bash
   which python  # Ensure correct Python interpreter
   which pip     # Ensure pip matches Python
   ```
4. **Use virtual environment:**

   ```bash
   python3 -m venv venv
   source venv/bin/activate
   pip install opinion_clob_sdk
   ```

***

#### Dependency Conflicts

**Problem:**

```
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.
This behaviour is the source of the following dependency conflicts.
```

**Solutions:**

1. **Create fresh virtual environment:**

   ```bash
   python3 -m venv fresh_env
   source fresh_env/bin/activate
   pip install opinion_clob_sdk
   ```
2. **Upgrade pip:**

   ```bash
   pip install --upgrade pip setuptools wheel
   ```
3. **Force reinstall:**

   ```bash
   pip install --force-reinstall opinion_clob_sdk
   ```

***

### Configuration Issues

#### InvalidParamError: chain\_id must be 56

**Problem:**

```python
client = Client(chain_id=1, ...)
# InvalidParamError: chain_id must be 56
```

**Solution:** Use BNB Chain Mainnet 56

```python
# ✓ BNB CHain Mainnet
client = Client(
    chain_id=56,
    rpc_url='___',
    ...
)
```

***

#### Missing Environment Variables

**Problem:**

```python
apikey=os.getenv('API_KEY')
# TypeError: Client.__init__() argument 'apikey' must be str, not None
```

**Solutions:**

1. **Create `.env` file:**

   ```bash
   # .env
   API_KEY=your_api_key_here
   RPC_URL=___
   PRIVATE_KEY=0x...
   MULTI_SIG_ADDRESS=0x...
   ```
2. **Load environment variables:**

   ```python
   from dotenv import load_dotenv
   import os

   load_dotenv()  # Load .env file

   # Verify loaded
   print(os.getenv('API_KEY'))  # Should not be None
   ```
3. **Provide defaults:**

   ```python
   apikey = os.getenv('API_KEY')
   if not apikey:
       raise ValueError("API_KEY environment variable not set")
   ```

***

### API Errors

#### OpenApiError: errno != 0

**Problem:**

```python
response = client.get_market(99999)
if response.errno != 0:
    print(response.errmsg)  # "Market not found"
```

**Solutions:**

1. **Always check errno:**

   ```python
   response = client.get_market(market_id)

   if response.errno == 0:
       # Success
       market = response.result.data
   else:
       # Handle error
       print(f"Error {response.errno}: {response.errmsg}")
   ```
2. **Common errno codes:**

   | Code | Meaning      | Solution                       |
   | ---- | ------------ | ------------------------------ |
   | 0    | Success      | Proceed with result            |
   | 404  | Not found    | Check ID exists                |
   | 400  | Bad request  | Check parameters               |
   | 401  | Unauthorized | Check API key                  |
   | 500  | Server error | Retry later or contact support |
3. **Wrap in try-except:**

   ```python
   try:
       response = client.get_market(market_id)
       if response.errno == 0:
           market = response.result.data
       else:
           logging.error(f"API error: {response.errmsg}")
   except OpenApiError as e:
       logging.error(f"API communication error: {e}")
   ```

***

### InvalidParamError: market\_id is required

**Problem:**

```python
client.get_market(market_id=None)
# InvalidParamError: market_id is required
```

**Solution:** Always provide required parameters:

```python
# ✗ Bad
market = client.get_market(None)

# ✓ Good
market_id = 123
if market_id:
    market = client.get_market(market_id)
```

***

### Trading Errors

#### InvalidParamError: Price must be positive for limit orders

**Problem:**

```python
order = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    price="0",  # ✗ Invalid for limit orders
    ...
)
```

**Solution:** Set valid price for limit orders:

```python
# ✓ Limit order with price
order = PlaceOrderDataInput(
    orderType=LIMIT_ORDER,
    price="0.55",  # Must be > 0
    ...
)

# ✓ Market order with price = 0
order = PlaceOrderDataInput(
    orderType=MARKET_ORDER,
    price="0",  # OK for market orders
    ...
)
```

***

#### InvalidParamError: makerAmountInBaseToken is not allowed for market buy

**Problem:**

```python
order = PlaceOrderDataInput(
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    makerAmountInBaseToken="100"  # ✗ Not allowed
)
```

**Solution:** Use correct amount field:

**Market BUY:**

```python
# ✓ Use makerAmountInQuoteToken
order = PlaceOrderDataInput(
    side=OrderSide.BUY,
    orderType=MARKET_ORDER,
    price="0",
    makerAmountInQuoteToken="100"  # ✓ Spend 100 USDT
)
```

**Market SELL:**

```python
# ✓ Use makerAmountInBaseToken
order = PlaceOrderDataInput(
    side=OrderSide.SELL,
    orderType=MARKET_ORDER,
    price="0",
    makerAmountInBaseToken="50"  # ✓ Sell 50 tokens
)
```

***

#### InvalidParamError: makerAmountInQuoteToken must be at least 1

**Problem:**

```python
order = PlaceOrderDataInput(
    makerAmountInQuoteToken="0.5",  # ✗ Below minimum
    ...
)
```

**Solution:** Use minimum amount of 1:

```python
# ✓ Minimum amounts
order = PlaceOrderDataInput(
    makerAmountInQuoteToken="1",  # ✓ At least 1 USDT
    # or
    makerAmountInBaseToken="1",   # ✓ At least 1 token
    ...
)
```

### Blockchain Errors

#### BalanceNotEnough

**Problem:**

```python
client.split(market_id=123, amount=100_000000)
# BalanceNotEnough: Insufficient balance for operation
```

**Solutions:**

1. **Check balance:**

   ```python
   balances = client.get_my_balances().result.data
   usdt = next((b for b in balances if 'usdt' in b.token.lower()), None)

   if usdt:
       balance_wei = int(usdt.amount)
       balance_usdt = balance_wei / 1e6  # Convert from wei
       print(f"USDT balance: ${balance_usdt}")

       amount_to_split = 10 * 1e18  # 10 USDT
       if balance_wei >= amount_to_split:
           client.split(market_id=123, amount=int(amount_to_split))
       else:
           print("Insufficient balance")
   ```
2. **For merge - need both outcome tokens:**

   ```python
   positions = client.get_my_positions().result.list
   yes_pos = next((p for p in positions if p.token_id == 'token_yes'), None)
   no_pos = next((p for p in positions if p.token_id == 'token_no'), None)

   if yes_pos and no_pos:
       # Can only merge min of both
       merge_amount = min(int(yes_pos.amount), int(no_pos.amount))
       client.merge(market_id=123, amount=merge_amount)
   ```

***

#### InsufficientGasBalance

**Problem:**

```python
client.enable_trading()
# InsufficientGasBalance: Not enough ETH for gas fees
```

**Solution:** Add ETH to signer wallet:

```python
# 1. Check signer address
signer_addr = client.contract_caller.signer.address()
print(f"Signer address: {signer_addr}")

# 2. Check ETH balance
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(rpc_url))
balance = w3.eth.get_balance(signer_addr)
balance_eth = balance / 1e18

print(f"BNB balance: {balance_bnb}")

# 3. If balance low, send BNB to signer_addr
# Usually $1-5 worth of BNB is enough for many transactions
```

***

**Problem:**

```bash
web3.exceptions.ContractLogicError: execution reverted
```

**Common Causes:**

1. **Insufficient approval:**

   ```python
   # Solution: Enable trading
   client.enable_trading()
   # Then retry operation
   ```
2. **Insufficient balance:** Check balance before operation (see BalanceNotEnough above)
3. **Gas price too low:**

   ```python
   # Usually handled automatically
   # If issues persist, try increasing gas
   ```
4. **Contract state changed:**

   ```python
   # Market may have resolved or status changed
   # Refresh market data
   market = client.get_market(market_id, use_cache=False)
   ```

***

### Performance Issues

#### Too Many API Calls

**Problem:** Hitting rate limits.

**Solutions:**

1. **Use caching:**

   ```python
   # Don't disable cache unless necessary
   client = Client(market_cache_ttl=300, ...)  # Enable caching
   ```
2. **Fetch once, use multiple times:**

   ```python
   # ✗ Bad: Multiple calls for same data
   for i in range(10):
       market = client.get_market(123)
       process(market)

   # ✓ Good: Fetch once
   market = client.get_market(123)
   for i in range(10):
       process(market)
   ```
3. **Paginate efficiently:**

   ```python
   # Fetch all markets efficiently
   all_markets = []
   page = 1
   limit = 20  # Max allowed

   while True:
       response = client.get_markets(page=page, limit=limit)
       if response.errno != 0:
           break

       markets = response.result.list
       all_markets.extend(markets)

       if len(markets) < limit:  # Last page
           break

       page += 1
   ```

***

### Data Issues

#### Precision Errors

**Problem:**

```python
amount = 10.5 * 1e18  # Float precision issues
# 105000000000000000000.0 instead of exact 105000000000000000000
```

**Solution:** Use `safe_amount_to_wei()`:

```python
from opinion_clob_sdk.sdk import safe_amount_to_wei

# ✓ Exact conversion using Decimal
amount_wei = safe_amount_to_wei(10.5, 18)  # Returns int: 105000000000000000000

client.split(market_id=123, amount=amount_wei)
```

***

#### Type Mismatch

**Problem:**

```python
order = PlaceOrderDataInput(
    price=0.55,  # ✗ Float instead of string
    ...
)
```

**Solution:** Use correct types:

```python
# ✓ Correct types
order = PlaceOrderDataInput(
    marketId=123,              # int
    tokenId="token_yes",       # str
    side=OrderSide.BUY,        # int (enum)
    orderType=LIMIT_ORDER,     # int
    price="0.55",              # str ✓
    makerAmountInQuoteToken="100"  # str
)
```

***

### Authentication Issues

#### 401 Unauthorized

**Problem:**

```
HTTPError: 401 Client Error: Unauthorized
```

**Solutions:**

1. **Check API key:**

   ```python
   import os
   apikey = os.getenv('API_KEY')
   print(f"API Key: {apikey[:10]}...")  # Print first 10 chars

   if not apikey or apikey == 'your_api_key_here':
       print("Invalid API key")
   ```
2. **Verify key format:**

   ```python
   # Should look like:
   # opn_prod_abc123xyz789 (production)
   # opn_dev_abc123xyz789 (development)
   ```
3. **Contact support:** If key is correct but still failing, contact <nik@opinionlabs.xyz>

***

#### Private Key Issues

**Problem:**

```
ValueError: Private key must be exactly 32 bytes long
```

**Solutions:**

1. **Check format:**

   ```python
   # ✓ Valid formats
   private_key = "0x1234567890abcdef..."  # With 0x prefix (64 hex chars)
   private_key = "1234567890abcdef..."    # Without 0x prefix (64 hex chars)

   # ✗ Invalid
   private_key = "0x123"  # Too short
   private_key = 12345    # Not a string
   ```
2. **Verify length:**

   ```python
   pk = os.getenv('PRIVATE_KEY')
   if pk.startswith('0x'):
       pk = pk[2:]  # Remove 0x prefix

   if len(pk) != 64:
       print(f"Invalid private key length: {len(pk)} (expected 64)")
   ```

***

### Debug Tips

#### Enable Logging

```python
import logging

# Set to DEBUG for detailed logs
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Now all SDK operations will log details
client = Client(...)
```

#### Inspect Responses

```python
import json

response = client.get_market(123)

# Pretty print response
print(json.dumps(response.to_dict(), indent=2))
```

#### Check SDK Version

```python
import opinion_clob_sdk
print(f"SDK Version: {opinion_clob_sdk.__version__}")

# Check dependencies
import web3
import eth_account
print(f"Web3 version: {web3.__version__}")
print(f"eth_account version: {eth_account.__version__}")
```

#### Verify Network Connection

```python
# Test RPC connection
from web3 import Web3

w3 = Web3(Web3.HTTPProvider(rpc_url))
print(f"Connected: {w3.is_connected()}")
print(f"Chain ID: {w3.eth.chain_id}")
print(f"Latest block: {w3.eth.block_number}")
```

***

### Getting Help

If you're still experiencing issues:

1. **Check FAQ:** Frequently Asked Questions
2. **Join** [**Discord Dev Channel**](https://discord.com/channels/1254615232496533545/1434480634742702100)

When reporting issues, include:

* SDK version
* Python version
* Full error traceback
* Minimal code to reproduce
* Expected vs actual behavior


# Builder Mode

> Non-custodial integration for building trading terminals on top of Opinion's prediction market.
>
> To request Builder access, Please kindly fill out this [short application form ](https://forms.gle/tqL84A5mMXjZ1xT86).&#x20;

## What is Builder Mode?

Builder mode lets you create your own trading application on top of Opinion's prediction market:

* **2,000 free gasless transactions per day** (order placement and cancellation)
* **Non-custodial**: you never handle user private keys; users sign with their own wallets
* **Full API coverage**: user management, order building, Safe transactions, and more

## Architecture

```
┌──────────────┐     ┌──────────────────┐     ┌────────────┐
│  Your App    │     │  Opinion Backend  │     │  BNB Chain  │
│  (Builder)   │────>│  (API + Relayer)  │────>│  (On-chain) │
│              │     │                   │     │             │
│ BuilderClient│<────│  - Order matching │<────│  - Safe     │
│ + UserClient │     │  - TX relay       │     │  - CTF      │
│              │     │  - Safe creation  │     │  - ERC20    │
└──────────────┘     └──────────────────┘     └────────────┘
```

**Three identities involved:**

| Wallet                  | What                               | Who Controls             |
| ----------------------- | ---------------------------------- | ------------------------ |
| **EOA** (signer)        | Signs orders and Safe transactions | User (private key)       |
| **Safe** (asset wallet) | Holds funds, executes trades       | Multi-sig (EOA is owner) |
| **Builder API key**     | Authenticates builder API calls    | Builder (your server)    |

## Quick Start

```python
from opinion_clob_sdk.builder_sdk import BuilderClient
from opinion_clob_sdk.user_client import UserClient

# 1. Initialize the builder client
builder = BuilderClient(
    host="https://openapi.opinion.trade",
    builder_apikey="YOUR_BUILDER_API_KEY",
    chain_id=56,
    rpc_url="https://bsc-dataseed.binance.org",
)

# 2. Create a user sub-account
result = builder.create_user("0xUserEOAAddress...")
user_apikey = result["apikey"]       # Save this! Only returned once.
safe_address = result["multi_sig_wallet"]

# 3. Enable trading (one-time)
tx_result = builder.build_enable_trading_tx(safe_address)
# User signs tx_result["eip712_data"] with their wallet
# signature = user_wallet.sign_typed_data(tx_result["eip712_data"])
# builder.submit_safe_tx(user_address, tx_result, signature)

# 4. Build and place an order
from opinion_clob_sdk.chain.py_order_utils.model.sides import BUY
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER

order_data = builder.build_order_for_signing(
    market_id=123,
    token_id="outcome_token_id",
    user_wallet_address=safe_address,
    side=BUY,
    order_type=LIMIT_ORDER,
    amount=10.0,
    price="0.5",
    signer_address="0xUserEOAAddress...",
)
# User signs order_data["struct_hash"] with their wallet
# signature = user_wallet.sign_hash(order_data["struct_hash"])
# builder.place_order_for_user_from_build_result(order_data, signature, safe_address)
```

## Constructor Parameters

<table><thead><tr><th width="190">Parameter</th><th width="83">Type</th><th width="136">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>host</code></td><td>str</td><td>Yes</td><td>API host URL</td></tr><tr><td><code>builder_apikey</code></td><td>str</td><td>Yes</td><td>Builder API key (uses <code>builder-apikey</code> header)</td></tr><tr><td><code>chain_id</code></td><td>int</td><td>Yes</td><td>Blockchain chain ID (56 for BNB Chain)</td></tr><tr><td><code>rpc_url</code></td><td>str</td><td>No</td><td>RPC endpoint URL (required for Safe operations)</td></tr><tr><td><code>use_beta</code></td><td>bool</td><td>No</td><td>Use beta (test env) mode (default: <code>False</code>)</td></tr><tr><td><code>contract_addresses</code></td><td>dict</td><td>No</td><td>Override contract addresses (required if <code>use_beta=True</code>)</td></tr></tbody></table>

## API Reference

| Page                                                                                               | Methods                                                                                                          |
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [Create User](/developer-guide/opinion-clob-python-sdk/builder-mode/create-user)                   | `create_user()`                                                                                                  |
| [Get User](/developer-guide/opinion-clob-python-sdk/builder-mode/get-user)                         | `get_user()`, `regenerate_user_apikey()`                                                                         |
| [Get Quote Tokens](/developer-guide/opinion-clob-python-sdk/builder-mode/get-quote-tokens)         | `get_quote_tokens()`                                                                                             |
| [Get Market](/developer-guide/opinion-clob-python-sdk/builder-mode/get-market)                     | `get_market()`, `get_orderbook()`                                                                                |
| [Build Order](/developer-guide/opinion-clob-python-sdk/builder-mode/build-order)                   | `build_order_for_signing()`, `sign_order_with_private_key()`                                                     |
| [Place Order](/developer-guide/opinion-clob-python-sdk/builder-mode/place-order)                   | `place_order_for_user()`, `place_order_for_user_from_build_result()`                                             |
| [Cancel Order](/developer-guide/opinion-clob-python-sdk/builder-mode/cancel-order)                 | `cancel_order_for_user()`, `cancel_orders_batch_for_user()`, `cancel_all_orders_for_user()`, `get_user_orders()` |
| [Enable Trading](/developer-guide/opinion-clob-python-sdk/builder-mode/enable-trading)             | `build_enable_trading_tx()`                                                                                      |
| [Split / Merge / Redeem](/developer-guide/opinion-clob-python-sdk/builder-mode/split-merge-redeem) | `build_split_tx()`, `build_merge_tx()`, `build_redeem_tx()`, `build_withdraw_tx()`, `submit_safe_tx()`           |
| [UserClient](/developer-guide/opinion-clob-python-sdk/builder-mode/userclient)                     | `UserClient` testing helper                                                                                      |

## Signing Patterns

There are two distinct signing patterns:

**Order signing** (gasless, for placing orders):

```python
build_result = builder.build_order_for_signing(...)
signature = user.sign_hash(build_result["struct_hash"])
```

**Safe TX signing** (relayed, for on-chain operations):

```python
tx_result = builder.build_enable_trading_tx(safe_address)
signature = user.sign_typed_data(tx_result["eip712_data"])
```

## Signature Types

<table><thead><tr><th width="171">Type</th><th width="146">Value</th><th>Description</th></tr></thead><tbody><tr><td>EOA</td><td>0</td><td>Direct EOA signature (maker == signer)</td></tr><tr><td>GNOSIS_SAFE</td><td>2</td><td>Gnosis Safe signature (maker != signer)</td></tr></tbody></table>

The type is determined automatically based on whether `signer_address` differs from `user_wallet_address`.

## Error Handling

```python
from opinion_clob_sdk.builder_sdk import BuilderError, InvalidParamError, ApiError

try:
    result = builder.create_user(address)
except InvalidParamError as e:
    # Bad input (invalid address, missing field)
    print(f"Invalid parameter: {e}")
except ApiError as e:
    # Backend error (user exists, rate limit, server error)
    print(f"API error: {e}")
except BuilderError as e:
    # General builder error (signing failure, etc.)
    print(f"Builder error: {e}")
```

Exception hierarchy: `BuilderError` > `InvalidParamError`, `ApiError`.


# Create User

> Create a user sub-account under your builder and get their API key.

## Usage

```python
result = builder.create_user("0x1234567890abcdef1234567890abcdef12345678")

user_apikey = result["apikey"]              # SAVE THIS! Only returned once.
safe_address = result["multi_sig_wallet"]   # Gnosis Safe wallet (may be empty initially)
```

## Parameters

| Parameter        | Type | Required | Description                                             |
| ---------------- | ---- | -------- | ------------------------------------------------------- |
| `wallet_address` | str  | Yes      | User's login wallet address (EOA, must start with `0x`) |

## Response

```python
{
    "apikey": "usr_abc123...",              # User's API key (only returned once!)
    "wallet_address": "0xabcd...",          # User's EOA address (lowercased)
    "builder_name": "my_builder",           # Parent builder name
    "multi_sig_wallet": "0x5678...",        # Gnosis Safe address (empty if still creating)
    "wallet_creation_tx_hash": "0xef01...", # Safe creation TX hash
    "enable_trading": False,                # Whether trading is enabled
}
```

## Notes

* The API key is returned **only once** during creation. Store it securely in your database.
* Safe wallet creation is asynchronous (1-10 minutes). Poll `get_user()` until `multi_sig_wallet` is non-empty.
* If the user already exists, the API returns an error. Catch the exception and call `get_user()` instead.

```python
try:
    result = builder.create_user(address)
except ApiError as e:
    if "already exists" in str(e).lower():
        result = builder.get_user(address)
```


# Get User

> Retrieve user information and regenerate API keys.

## Get User Info

```python
user_info = builder.get_user("0x1234567890abcdef1234567890abcdef12345678")

print(user_info["wallet_address"])     # EOA address
print(user_info["multi_sig_wallet"])   # Safe wallet address
print(user_info["enable_trading"])     # True/False
```

### Parameters

| Parameter        | Type | Required | Description                                             |
| ---------------- | ---- | -------- | ------------------------------------------------------- |
| `wallet_address` | str  | Yes      | User's login wallet address (EOA, must start with `0x`) |

### Response

```python
{
    "wallet_address": "0xabcd...",     # User's EOA address
    "builder_name": "my_builder",      # Parent builder name
    "multi_sig_wallet": "0x5678...",   # Gnosis Safe address
    "enable_trading": True,            # Whether trading is enabled
}
```

## Regenerate API Key

Regenerate the API key for a user. The old key is invalidated immediately.

```python
result = builder.regenerate_user_apikey("0x1234567890abcdef1234567890abcdef12345678")
new_apikey = result["apikey"]  # SAVE THIS! Only returned once.
```

### Parameters

| Parameter        | Type | Required | Description                                             |
| ---------------- | ---- | -------- | ------------------------------------------------------- |
| `wallet_address` | str  | Yes      | User's login wallet address (EOA, must start with `0x`) |

### Response

```python
{
    "apikey": "usr_new_key...",        # New API key (only returned once!)
    "wallet_address": "0xabcd...",
    "builder_name": "my_builder",
    "multi_sig_wallet": "0x5678...",
    "enable_trading": True,
}
```

## Notes

* `get_user()` does **not** return the API key for security reasons. You must have saved it from `create_user()` or `regenerate_user_apikey()`.


# Get Quote Tokens

> List supported quote tokens (currencies) for trading.

## Usage

```python
response = builder.get_quote_tokens()

tokens = response.result.list
for token in tokens:
    print(f"Address:  {token.quote_token_address}")
    print(f"Exchange: {token.ctf_exchange_address}")
    print(f"Decimals: {token.decimal}")
    print(f"Chain ID: {token.chain_id}")
```

## Parameters

| Parameter   | Type | Required | Description                                                               |
| ----------- | ---- | -------- | ------------------------------------------------------------------------- |
| `use_cache` | bool | No       | Use cached result (default: `True`). Pass `False` to force a fresh fetch. |

## Response

The response object has `response.result.list` containing a list of quote token objects, each with:

| Property               | Type | Description                                 |
| ---------------------- | ---- | ------------------------------------------- |
| `quote_token_address`  | str  | ERC20 token contract address                |
| `ctf_exchange_address` | str  | CTF Exchange contract address               |
| `decimal`              | str  | Token decimal places (e.g., `"6"` for USDC) |
| `chain_id`             | str  | Chain ID (e.g., `"56"`)                     |

## Notes

* Results are cached for 1 hour by default. Use `use_cache=False` to bypass the cache.
* The `quote_token_address` and `decimal` are needed when calculating order amounts for `build_split_tx()` and `build_merge_tx()`.
* The `ctf_exchange_address` is used internally by `build_order_for_signing()` and `build_enable_trading_tx()`.


# Enable Trading

> Build, sign, and submit an Enable Trading transaction for a user's Safe wallet.

Enable Trading is a one-time setup that approves the CTF Exchange and ConditionalTokens contracts to spend the user's quote tokens. Without this, the user cannot place orders.

## Usage

```python
# Step 1: Build the transaction
tx_result = builder.build_enable_trading_tx(safe_address)

# Step 2: User signs the EIP-712 data
signature = user.sign_typed_data(tx_result["eip712_data"])

# Step 3: Submit to backend for relay
result = builder.submit_safe_tx(
    wallet_address=user_address,      # User's EOA address
    safe_tx_result=tx_result,
    signature=signature,
)
```

## Parameters

### `build_enable_trading_tx()`

| Parameter      | Type | Required | Description                                                                 |
| -------------- | ---- | -------- | --------------------------------------------------------------------------- |
| `safe_address` | str  | Yes      | User's Safe wallet address                                                  |
| `quote_tokens` | dict | No       | `{token_address: ctf_exchange_address}`. If not provided, fetched from API. |

### `submit_safe_tx()`

| Parameter        | Type | Required | Description                             |
| ---------------- | ---- | -------- | --------------------------------------- |
| `wallet_address` | str  | Yes      | User's EOA wallet address (Safe owner)  |
| `safe_tx_result` | dict | Yes      | Result from `build_enable_trading_tx()` |
| `signature`      | str  | Yes      | User's signature on the EIP-712 data    |

## Response

`build_enable_trading_tx()` returns:

```python
{
    "safe_tx": <SafeTx>,             # SafeTx object
    "eip712_data": { ... },          # EIP-712 structured data for user signing
    "safe_tx_hash": "0xabcdef...",   # Hash of the Safe transaction
    "submission_data_template": { ... },
}
```

`submit_safe_tx()` returns the API response dict.

## Notes

* Requires `rpc_url` to be set in the `BuilderClient` constructor.
* This only needs to be done **once per user**. Check `get_user()["enable_trading"]` before calling.
* The backend relays the transaction on-chain; no gas is needed from the user.
* On-chain confirmation takes approximately 30-60 seconds after submission.
* Safe TX signing uses `user.sign_typed_data(eip712_data)`, which is different from order signing (`user.sign_hash(struct_hash)`).


# Get Market

> Retrieve market details and orderbook data.

## Get Market Details

```python
response = builder.get_market(123)

market = response.result.data
print(market.topic_id)        # Market ID
print(market.condition_id)    # Condition ID (needed for split/merge/redeem)
print(market.quote_token)     # Quote token address
print(market.status)          # Market status
print(market.chain_id)        # Chain ID
```

### Parameters

| Parameter   | Type | Required | Description                                                               |
| ----------- | ---- | -------- | ------------------------------------------------------------------------- |
| `market_id` | int  | Yes      | Market ID (positive integer)                                              |
| `use_cache` | bool | No       | Use cached result (default: `True`). Pass `False` to force a fresh fetch. |

### Response

The response object has `response.result.data` containing the market object. Key properties include `topic_id`, `condition_id`, `quote_token`, `status`, and `chain_id`.

## Get Orderbook

```python
response = builder.get_orderbook("outcome_token_id_here")
```

### Parameters

| Parameter  | Type | Required | Description      |
| ---------- | ---- | -------- | ---------------- |
| `token_id` | str  | Yes      | Outcome token ID |

## Notes

* Market results are cached for 5 minutes by default. Use `use_cache=False` to bypass.
* The `condition_id` from market data is required for `build_split_tx()`, `build_merge_tx()`, and `build_redeem_tx()`.
* Orderbook data is not cached.


# Build Order

> Build an EIP-712 order structure for a user to sign.

## Usage

```python
from opinion_clob_sdk.chain.py_order_utils.model.sides import BUY, SELL
from opinion_clob_sdk.chain.py_order_utils.model.order_type import LIMIT_ORDER, MARKET_ORDER

build_result = builder.build_order_for_signing(
    market_id=123,
    token_id="outcome_token_id",
    user_wallet_address="0xSafeAddress...",    # Maker (Safe wallet)
    side=BUY,
    order_type=LIMIT_ORDER,
    amount=10.0,
    price="0.5",
    signer_address="0xUserEOAAddress...",      # Signer (EOA)
)

print(build_result["order"])           # Order struct dict
print(build_result["struct_hash"])     # Hash for the user to sign
print(build_result["typed_data"])      # Full EIP-712 typed data (for reference)
```

## Parameters

| Parameter             | Type      | Required   | Description                                                                                       |
| --------------------- | --------- | ---------- | ------------------------------------------------------------------------------------------------- |
| `market_id`           | int       | Yes        | Market ID                                                                                         |
| `token_id`            | str       | Yes        | Outcome token ID to trade                                                                         |
| `user_wallet_address` | str       | Yes        | Safe address (maker, where funds are held)                                                        |
| `side`                | OrderSide | Yes        | `BUY` or `SELL`                                                                                   |
| `order_type`          | int       | Yes        | `LIMIT_ORDER` (2) or `MARKET_ORDER` (1)                                                           |
| `amount`              | float     | Yes        | Amount in human-readable units                                                                    |
| `price`               | str       | Limit only | Price per share (e.g., `"0.5"` for 50 cents). Required for `LIMIT_ORDER`.                         |
| `amount_type`         | str       | No         | `"quote"` (default) or `"base"`                                                                   |
| `signer_address`      | str       | No         | EOA signer address. If different from `user_wallet_address`, Safe mode (signatureType=2) is used. |

## Response

```python
{
    "order": {                        # Order struct for submission
        "salt": "123...",
        "maker": "0xSafeAddress...",
        "signer": "0xEOAAddress...",
        "taker": "0x0000...0000",
        "tokenId": "...",
        "makerAmount": "10000000",
        "takerAmount": "20000000",
        "expiration": "...",
        "nonce": "0",
        "feeRateBps": "0",
        "side": "0",
        "signatureType": "2",
    },
    "struct_hash": "0xabcdef...",     # Hash for user to sign
    "typed_data": { ... },            # Full EIP-712 typed data
    "domain": { ... },                # EIP-712 domain
    "exchange_address": "0x...",
    "currency_address": "0x...",
    "currency_decimal": 6,
    "market_id": 123,
    "order_type": 2,
    "price": "0.5",
}
```

## Market Order Restrictions

* Market BUY: `amount_type` must be `"quote"` (how much to spend)
* Market SELL: `amount_type` must be `"base"` (how many tokens to sell)
* Market orders set `price` to `"0"` internally.

## Signing the Order

Order signing uses `sign_hash(struct_hash)`, **not** `sign_typed_data`. This is different from Safe TX signing.

```python
# Using UserClient (testing helper)
from opinion_clob_sdk.user_client import UserClient

user = UserClient("0xPrivateKey...")
signature = user.sign_hash(build_result["struct_hash"])
```

For testing, you can also use the static helper method:

```python
signature = BuilderClient.sign_order_with_private_key(
    build_result["typed_data"],
    "0xPrivateKey...",
)
```

## Notes

* In production, users sign the `struct_hash` using their wallet (e.g., `personal_sign` or equivalent).
* The `sign_order_with_private_key()` static method signs the full `typed_data` (EIP-712). This is for testing convenience only.
* Orders expire after 30 days by default.
* The signature type (EOA=0 or Safe=2) is set automatically based on whether `signer_address` differs from `user_wallet_address`.


# Place Order

> Submit a signed order on behalf of a user.

## Usage (Convenience Method)

The easiest way to place an order is using the build result directly:

```python
result = builder.place_order_for_user_from_build_result(
    build_result=build_result,     # From build_order_for_signing()
    signature=signature,           # User's signature on struct_hash
    user_wallet_address="0xSafeAddress...",
)
```

### Parameters

| Parameter             | Type | Required | Description                                         |
| --------------------- | ---- | -------- | --------------------------------------------------- |
| `build_result`        | dict | Yes      | Result from `build_order_for_signing()`             |
| `signature`           | str  | Yes      | User's signature (hex string starting with `0x`)    |
| `user_wallet_address` | str  | Yes      | User's Safe wallet address (must match order maker) |

## Usage (Full Control)

For more control, pass the order fields explicitly:

```python
result = builder.place_order_for_user(
    order=build_result["order"],
    signature=signature,
    user_wallet_address="0xSafeAddress...",
    market_id=build_result["market_id"],
    order_type=build_result["order_type"],
    price=build_result["price"],
    currency_address=build_result["currency_address"],
)
```

### Parameters

| Parameter             | Type | Required | Description                                              |
| --------------------- | ---- | -------- | -------------------------------------------------------- |
| `order`               | dict | Yes      | Order struct from `build_order_for_signing()`            |
| `signature`           | str  | Yes      | User's signature (hex string starting with `0x`)         |
| `user_wallet_address` | str  | Yes      | User's Safe wallet address (must match `order["maker"]`) |
| `market_id`           | int  | No       | Market ID                                                |
| `order_type`          | int  | No       | `LIMIT_ORDER` (2) or `MARKET_ORDER` (1)                  |
| `price`               | str  | No       | Price for display                                        |
| `currency_address`    | str  | No       | Quote token address                                      |

## Response

API response dict with order details (structure depends on backend).

## Notes

* The `order["maker"]` must match `user_wallet_address` (lowercased). An `InvalidParamError` is raised if they differ.
* The signature must start with `0x`.
* Order signing uses `user.sign_hash(struct_hash)` -- see [Build Order](broken://pages/93a5a1a4b9b707b015990111262146f8c4116ee6) for details.


# Cancel Order

> Cancel orders for a user. Requires the user's API key.

## Cancel Single Order

```python
result = builder.cancel_order_for_user(user_apikey, "order_id_here")
# Returns: {"result": True}
```

### Parameters

| Parameter     | Type | Required | Description                                                         |
| ------------- | ---- | -------- | ------------------------------------------------------------------- |
| `user_apikey` | str  | Yes      | User's API key (from `create_user()` or `regenerate_user_apikey()`) |
| `order_id`    | str  | Yes      | Order ID to cancel                                                  |

## Cancel Multiple Orders

```python
results = builder.cancel_orders_batch_for_user(user_apikey, [
    "order_id_1",
    "order_id_2",
    "order_id_3",
])

for r in results:
    if r["success"]:
        print(f"Cancelled: {r['order_id']}")
    else:
        print(f"Failed: {r['order_id']}, Error: {r['error']}")
```

### Parameters

| Parameter     | Type       | Required | Description                 |
| ------------- | ---------- | -------- | --------------------------- |
| `user_apikey` | str        | Yes      | User's API key              |
| `order_ids`   | list\[str] | Yes      | List of order IDs to cancel |

### Response

List of result dicts, each containing:

| Key        | Type | Description                    |
| ---------- | ---- | ------------------------------ |
| `index`    | int  | Position in the input list     |
| `success`  | bool | Whether cancellation succeeded |
| `order_id` | str  | The order ID                   |
| `result`   | dict | API response (if success)      |
| `error`    | str  | Error message (if failed)      |

## Cancel All Orders

```python
result = builder.cancel_all_orders_for_user(user_apikey)

print(f"Cancelled: {result['cancelled']}/{result['total_orders']}")
```

With optional filters:

```python
from opinion_clob_sdk.chain.py_order_utils.model.sides import OrderSide

result = builder.cancel_all_orders_for_user(
    user_apikey,
    market_id=123,            # Optional: only this market
    side=OrderSide.BUY,       # Optional: only BUY orders
)
```

### Parameters

| Parameter     | Type      | Required | Description                                          |
| ------------- | --------- | -------- | ---------------------------------------------------- |
| `user_apikey` | str       | Yes      | User's API key                                       |
| `market_id`   | int       | No       | Filter by market ID                                  |
| `side`        | OrderSide | No       | Filter by side (`OrderSide.BUY` or `OrderSide.SELL`) |

### Response

```python
{
    "total_orders": 5,
    "cancelled": 4,
    "failed": 1,
    "results": [ ... ]   # List of per-order results
}
```

## Get User Orders

```python
response = builder.get_user_orders(
    user_apikey=user_apikey,
    market_id=123,     # 0 for all markets
    status="1",        # "1"=pending, "2"=filled, "3"=canceled, "4"=expired, "5"=failed
    limit=20,          # Max 20
    page=1,
)
```

### Parameters

| Parameter     | Type | Required | Description                                                   |
| ------------- | ---- | -------- | ------------------------------------------------------------- |
| `user_apikey` | str  | Yes      | User's API key                                                |
| `market_id`   | int  | No       | Filter by market (0 for all, default: 0)                      |
| `status`      | str  | No       | Status filter. Comma-separated for multiple (e.g., `"1,2,3"`) |
| `limit`       | int  | No       | Orders per page, max 20 (default: 10)                         |
| `page`        | int  | No       | Page number (default: 1)                                      |

## Notes

* All cancellation methods use the **user's API key**, not the builder API key.
* The builder manages user API keys and acts on their behalf.
* `cancel_all_orders_for_user()` uses pagination internally to collect all open orders before cancelling.


# Split / Merge / Redeem

> Build Safe transactions for on-chain token operations.

All methods in this page follow the same pattern: **build** the transaction, have the **user sign** it, then **submit** it to the backend for relay.

## Common Pattern

```python
# 1. Build the transaction
tx_result = builder.build_split_tx(...)   # or build_merge_tx, build_redeem_tx, build_withdraw_tx

# 2. User signs the EIP-712 data
signature = user.sign_typed_data(tx_result["eip712_data"])

# 3. Submit to backend
result = builder.submit_safe_tx(
    wallet_address=user_address,
    safe_tx_result=tx_result,
    signature=signature,
)
```

All build methods return:

```python
{
    "safe_tx": <SafeTx>,             # SafeTx object
    "eip712_data": { ... },          # EIP-712 data for user signing
    "safe_tx_hash": "0xabcdef...",   # Safe transaction hash
    "submission_data_template": { ... },
}
```

## Split Position

Convert collateral tokens (e.g., USDC) into Yes and No outcome tokens.

```python
tx_result = builder.build_split_tx(
    safe_address="0xSafeAddress...",
    collateral_token="0xQuoteTokenAddress...",
    condition_id="0xConditionId...",
    amount=1000000,          # Amount in wei (e.g., 1 USDC = 1000000 for 6 decimals)
    partition=[1, 2],        # Default: [1, 2] for binary outcomes
)
```

| Parameter          | Type       | Required | Description                                           |
| ------------------ | ---------- | -------- | ----------------------------------------------------- |
| `safe_address`     | str        | Yes      | User's Safe wallet address                            |
| `collateral_token` | str        | Yes      | Quote token contract address                          |
| `condition_id`     | str        | Yes      | Market condition ID (hex string, from `get_market()`) |
| `amount`           | int        | Yes      | Amount in wei                                         |
| `partition`        | list\[int] | No       | Partition array (default: `[1, 2]`)                   |

## Merge Position

Convert equal amounts of Yes and No outcome tokens back into collateral.

```python
tx_result = builder.build_merge_tx(
    safe_address="0xSafeAddress...",
    collateral_token="0xQuoteTokenAddress...",
    condition_id="0xConditionId...",
    amount=1000000,
    partition=[1, 2],
)
```

Parameters are identical to `build_split_tx()`.

## Redeem Position

Claim winnings from a resolved market. Converts winning outcome tokens back into collateral.

```python
tx_result = builder.build_redeem_tx(
    safe_address="0xSafeAddress...",
    collateral_token="0xQuoteTokenAddress...",
    condition_id="0xConditionId...",
    partition=[1, 2],
)
```

| Parameter          | Type       | Required | Description                         |
| ------------------ | ---------- | -------- | ----------------------------------- |
| `safe_address`     | str        | Yes      | User's Safe wallet address          |
| `collateral_token` | str        | Yes      | Quote token contract address        |
| `condition_id`     | str        | Yes      | Market condition ID                 |
| `partition`        | list\[int] | No       | Partition array (default: `[1, 2]`) |

Note: `build_redeem_tx()` does not take an `amount` parameter. It redeems all eligible tokens.

## Withdraw Tokens

Transfer ERC20 tokens from the Safe wallet to any address.

```python
tx_result = builder.build_withdraw_tx(
    safe_address="0xSafeAddress...",
    token_address="0xTokenAddress...",
    amount=1000000,              # Amount in wei
    to_address="0xRecipientAddress...",
)
```

| Parameter       | Type | Required | Description                     |
| --------------- | ---- | -------- | ------------------------------- |
| `safe_address`  | str  | Yes      | User's Safe wallet address      |
| `token_address` | str  | Yes      | ERC20 token address to withdraw |
| `amount`        | int  | Yes      | Amount in wei                   |
| `to_address`    | str  | Yes      | Recipient address               |

## Submit Safe Transaction

All build methods produce a result that is submitted via `submit_safe_tx()`:

```python
result = builder.submit_safe_tx(
    wallet_address=user_address,      # User's EOA address
    safe_tx_result=tx_result,
    signature=signature,
)
```

| Parameter        | Type | Required | Description                            |
| ---------------- | ---- | -------- | -------------------------------------- |
| `wallet_address` | str  | Yes      | User's EOA wallet address (Safe owner) |
| `safe_tx_result` | dict | Yes      | Result from any `build_*_tx()` method  |
| `signature`      | str  | Yes      | User's signature on the EIP-712 data   |

## Testing Helper

For testing, you can sign Safe transactions with a private key directly:

```python
signature = BuilderClient.sign_safe_tx_with_private_key(
    tx_result["eip712_data"],
    "0xPrivateKey...",
)
```

## Notes

* All methods require `rpc_url` to be set in the `BuilderClient` constructor.
* Amounts for `build_split_tx()`, `build_merge_tx()`, and `build_withdraw_tx()` must be in **wei** (smallest token unit). Use `safe_amount_to_wei(amount, decimals)` from `opinion_clob_sdk.builder_sdk` for conversion.
* The backend relays these transactions on-chain; no gas is needed from the user.
* On-chain confirmation typically takes 30-60 seconds.
* The `condition_id` is available from `get_market()` response (`market.condition_id`).


# UserClient

> Testing helper that simulates a user's wallet for signing operations.

`UserClient` holds a private key and provides signing methods for both orders and Safe transactions. In production, replace `UserClient` with actual wallet integration (MetaMask, WalletConnect, etc.).

## Import

```python
from opinion_clob_sdk.user_client import UserClient
```

## Create a Random User

```python
user = UserClient.create_random()
print(user.address)  # Random wallet address
```

## Create from Private Key

```python
user = UserClient("0xYourPrivateKey...")
print(user.address)  # Derived wallet address
```

## File Persistence

Load a user from a JSON file, or create a new one and save it:

```python
# Load or create (saves to file if new)
user = UserClient.load_or_create(".test_user.json")

# Load from existing file (raises FileNotFoundError if missing)
user = UserClient.load_from_file(".test_user.json")

# Save current user to file
user.save_to_file(".test_user.json")
```

The JSON file format:

```json
{
  "address": "0x1234...",
  "private_key": "0xabcd..."
}
```

## Sign Safe Transactions

Use `sign_typed_data()` for Safe transaction signing (Enable Trading, Split, Merge, Redeem, Withdraw):

```python
tx_result = builder.build_enable_trading_tx(safe_address)
signature = user.sign_typed_data(tx_result["eip712_data"])
```

## Sign Orders

Use `sign_hash()` for order signing. This signs the order struct hash directly:

```python
build_result = builder.build_order_for_signing(...)
signature = user.sign_hash(build_result["struct_hash"])
```

## Properties

| Property  | Type | Description                  |
| --------- | ---- | ---------------------------- |
| `address` | str  | Checksummed Ethereum address |

## Notes

* `UserClient` is a **testing helper only**. Do not use it in production with real user funds.
* Order signing uses `sign_hash()` (signs raw hash), while Safe TX signing uses `sign_typed_data()` (signs EIP-712 structured data). These are different operations and are not interchangeable.
* The `sign_hash()` method calls `Account._sign_hash()` internally, matching the original SDK signing behavior.


# Opinion CLOB Typescript SDK


# Overview

## Opinion CLOB TypeScript SDK

Welcome to the official documentation for the **Opinion CLOB TypeScript SDK** - a TypeScript library for interacting with Opinion Labs' prediction markets via the Central Limit Order Book (CLOB) API.

{% hint style="warning" %}
**Technical Preview**: TypeScript SDK Version 0.6.1 features BNB Chain support. While fully functional and tested, we recommend thorough testing before production use.

To request SDK/API access, please kindly fill out this [short application form](https://docs.google.com/forms/d/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII).

*API Key can be used for Opinion OpenAPI, Opinion Websocket, and Opinion CLOB SDK*
{% endhint %}

### What is Opinion CLOB TypeScript SDK?

The Opinion CLOB TypeScript SDK provides a TypeScript interface for building applications on top of Opinion prediction market infrastructure. It enables developers to:

* **Query market data** - Access real-time market information, prices, and orderbooks
* **Execute trades** - Place market and limit orders with EIP712 signing (including post-only)
* Discover markets — getLabels() / getMarkets({ labelId })
* **Manage positions** - Track balances, positions, and trading history
* **Interact with smart contracts** - Split, merge, and redeem tokens on BNB Chain blockchain

### Key Features

#### Production-Ready

* **Type-safe** - Full TypeScript types and interfaces
* **Well-tested** - Test suite with 95%+ coverage
* **Reliable** - Built on industry-standard libraries (viem)
* **Documented** - Extensive documentation with examples

#### Performance Optimized

* **Smart caching** - Configurable TTL for market data and quote tokens
* **Batch operations** - Place or cancel multiple orders efficiently
* **Gas optimization** - Minimal on-chain transactions

#### Secure by Design

* **EIP712 signing** - Industry-standard typed data signatures
* **Multi-sig support** - Gnosis Safe integration for institutional users
* **Private key safety** - Keys never leave your environment

#### Blockchain Support

* **BNB Chain Mainnet** (Chain ID: 56)

### Use Cases

#### Trading Applications

Build automated trading bots, market-making applications, or custom trading interfaces.

{% code title="example.ts" %}

```typescript
import { Client, DEFAULT_API_HOST, CHAIN_ID_BNB_MAINNET, OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({ host: DEFAULT_API_HOST, apiKey: 'your_key', ... });

// Place a limit order
const result = await client.placeOrder({
  marketId: 123,
  tokenId: 'token_yes',
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.55',
  makerAmountInQuoteToken: '100',
});
```

{% endcode %}

#### Market Analytics

Aggregate and analyze market data for research or monitoring dashboards.

{% code title="analytics.ts" %}

```typescript
// Get all active markets
const markets = await client.getMarkets({ status: TopicStatusFilter.ACTIVATED, limit: 100 });

// Analyze orderbook depth
const orderbook = await client.getOrderbook('token_123');
console.log('Orderbook:', orderbook.result);
```

{% endcode %}

#### Portfolio Management

Track positions and balances across multiple markets.

{% code title="portfolio.ts" %}

```typescript
// Get user positions
const positions = await client.getMyPositions({ limit: 50 });

// Get balances
const balances = await client.getMyBalances();

// Get trade history
const trades = await client.getMyTrades({ marketId: 123 });
```

{% endcode %}

### Architecture

The Opinion CLOB TypeScript SDK is built with a modular architecture:

```
+---------------------------------------------+
|          Application Layer                   |
|        (Your TypeScript Code)                |
+--------------+------------------------------+
               |
+--------------v------------------------------+
|         Opinion CLOB SDK                     |
|  +--------------+   +-----------------+      |
|  | Client API   |   | Contract Caller |      |
|  | (REST)       |   | (Blockchain)    |      |
|  +------+-------+   +----------+------+      |
+---------+----------------------+-------------+
          |                      |
+---------v----------+  +-------v------------+
|  Opinion API       |  |     Blockchain     |
|  (CLOB Exchange)   |  |  (Smart Contracts) |
+--------------------+  +--------------------+
```

### Quick Links

* [Installation Guide](/developer-guide/opinion-clob-typescript-sdk/getting-started/installation)
* [Quick Start](/developer-guide/opinion-clob-typescript-sdk/getting-started/quick-start)
* [Core Concepts](/developer-guide/opinion-clob-typescript-sdk/core-concepts)
* [API Reference](/developer-guide/opinion-clob-typescript-sdk/api-references)
* [FAQ](/developer-guide/opinion-clob-typescript-sdk/support/faq)

Ready to get started? Head to the Installation Guide to begin building with Opinion CLOB TypeScript SDK!


# Getting Started


# Installation

This guide will help you install the Opinion CLOB TypeScript SDK and its dependencies.

Package registry: <https://www.npmjs.com/package/@opinion-labs/opinion-clob-sdk>

### Requirements

#### Node.js Version

* **Node.js 18 or higher**

Check your Node.js version:

```bash
node --version
```

#### System Requirements

* **Operating Systems**: Linux, macOS, Windows
* **Network**: Internet connection for API access and blockchain RPC
* **Optional**: Git (for development installation)

### Installation Methods

#### Install from npm (Recommended)

The simplest way to install the Opinion CLOB TypeScript SDK is via a package manager.

{% tabs %}
{% tab title="npm" %}

```bash
npm install @opinion-labs/opinion-clob-sdk
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @opinion-labs/opinion-clob-sdk
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add @opinion-labs/opinion-clob-sdk
```

{% endtab %}
{% endtabs %}

This will install the latest stable version and all required dependencies including `viem`.

{% hint style="info" %}
The TypeScript SDK is ESM-only. Ensure your `package.json` has `"type": "module"`.
{% endhint %}

### Dependencies

The SDK automatically installs the following key dependency:

| Package | Purpose                                                      |
| ------- | ------------------------------------------------------------ |
| `viem`  | Blockchain interactions, contract calls, transaction signing |

### Verify Installation

After installation, verify it works:

```typescript
import {
  Client,
  TopicType,
  TopicStatus,
  CHAIN_ID_BNB_MAINNET,
} from '@opinion-labs/opinion-clob-sdk';

console.log('Opinion CLOB TypeScript SDK installed successfully!');
console.log('Supported chain:', CHAIN_ID_BNB_MAINNET);
```

### Project Setup

Create a new project and configure it for the SDK:

#### Create a new project

```bash
mkdir my-opinion-bot
cd my-opinion-bot
npm init -y
```

#### Add ESM support

Add `"type": "module"` to `package.json`, then install dependencies:

```bash
npm install @opinion-labs/opinion-clob-sdk
npm install -D typescript @types/node dotenv
```

#### Initialize TypeScript

```bash
npx tsc --init
```

Ensure your `tsconfig.json` includes the following settings:

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "./dist"
  }
}
```

### Upgrading

To upgrade to the latest version:

```bash
npm install @opinion-labs/opinion-clob-sdk@latest
```

### Uninstalling

To remove the SDK:

```bash
npm uninstall @opinion-labs/opinion-clob-sdk
```

### Next Steps

Once installed, proceed to:

1. [Quick Start Guide - Build your first application](/developer-guide/opinion-clob-typescript-sdk/getting-started/quick-start)
2. [Configuration - Set up API keys and credentials](/developer-guide/opinion-clob-typescript-sdk/getting-started/configuration)
3. [API Reference - Explore available methods](/developer-guide/opinion-clob-typescript-sdk/api-references)

<details>

<summary>Having issues?</summary>

Check the Troubleshooting Guide or FAQ.

</details>


# Quick Start

Get up and running with the Opinion CLOB TypeScript SDK in minutes. This guide will walk you through your first integration.

### Prerequisites

Before starting, ensure you have:

* Node.js 18+ installed
* Opinion CLOB TypeScript SDK installed ([Installation Guide](/developer-guide/opinion-clob-typescript-sdk/getting-started/installation))
* API credentials from Opinion Labs:
  * API Key
  * Private Key (for signing orders)
  * Multi-sig wallet address (create on [https://app.opinion.trade](https://app.opinion.trade/))
  * RPC URL (BNB Chain mainnet)

{% hint style="info" %}
**Need credentials?** Fill out this [short application form](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII) to get your API key.
{% endhint %}

### 5-Minute Quickstart

{% stepper %}
{% step %}

### Set Up Environment

Create a `.env` file in your project directory:

{% code title=".env" %}

```bash
# .env file
API_KEY=your_api_key_here
RPC_URL=https://bsc-dataseed.binance.org
PRIVATE_KEY=0x1234567890abcdef...
MULTI_SIG_ADDRESS=0xYourWalletAddress...
```

{% endcode %}
{% endstep %}

{% step %}

### Initialize the Client

Create a new TypeScript file (`my_first_app.ts`):

{% code title="my\_first\_app.ts (initialization)" %}

```typescript
import 'dotenv/config';
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!,
  chainId: CHAIN_ID_BNB_MAINNET, // 56
  rpcUrl: process.env.RPC_URL!,
  privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
  multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
});

console.log('Client initialized successfully!');
```

{% endcode %}
{% endstep %}

{% step %}

### Fetch Market Data

Add market data fetching:

{% code title="fetch-markets.ts" %}

```typescript
import { TopicStatusFilter } from '@opinion-labs/opinion-clob-sdk';

// Get all active markets
const marketsResponse = await client.getMarkets({
  status: TopicStatusFilter.ACTIVATED,
  page: 1,
  limit: 10,
});

// Parse the response
if (marketsResponse.errno === 0) {
  const markets = marketsResponse.result.list;
  console.log(`\nFound ${markets.length} active markets:`);

  for (const market of markets.slice(0, 3)) {
    console.log(`  - Market #${market.market_id}: ${market.market_title}`);
    console.log(`    Status: ${market.status}`);
  }
} else {
  console.error(`Error: ${marketsResponse.errmsg}`);
}
```

{% endcode %}
{% endstep %}

{% step %}

### Get Market Details

```typescript
// Get details for a specific market
const marketId = markets[0].market_id;

const marketDetail = await client.getMarket(marketId);
if (marketDetail.errno === 0) {
  const market = marketDetail.result.data;
  console.log(`\nMarket Details for #${marketId}:`);
  console.log(`  Title: ${market.market_title}`);
  console.log(`  Question ID: ${market.question_id}`);
  console.log(`  Quote Token: ${market.quote_token}`);
  console.log(`  Chain ID: ${market.chain_id}`);
}
```

{% endstep %}

{% step %}

### Check Orderbook

```typescript
const tokenId = 'your_token_id_here'; // Replace with actual token ID

try {
  const orderbook = await client.getOrderbook(tokenId);
  if (orderbook.errno === 0) {
    console.log(`\nOrderbook for token ${tokenId}:`);
    console.log('Orderbook:', JSON.stringify(orderbook.result, null, 2));
  }
} catch (e) {
  console.log(`  (Skip if token_id not set: ${e})`);
}
```

{% endstep %}

{% step %}

### Complete Example

Here is the full `my_first_app.ts` combining all the steps above:

{% code title="my\_first\_app.ts" %}

```typescript
import 'dotenv/config';
import {
  Client,
  CHAIN_ID_BNB_MAINNET,
  DEFAULT_API_HOST,
  TopicStatusFilter,
} from '@opinion-labs/opinion-clob-sdk';

async function main() {
  // Initialize client
  const client = new Client({
    host: DEFAULT_API_HOST,
    apiKey: process.env.API_KEY!,
    chainId: CHAIN_ID_BNB_MAINNET,
    rpcUrl: process.env.RPC_URL!,
    privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
    multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
  });
  console.log('Client initialized successfully!');

  // Get active markets
  const marketsResponse = await client.getMarkets({
    status: TopicStatusFilter.ACTIVATED,
    limit: 5,
  });

  if (marketsResponse.errno === 0) {
    const markets = marketsResponse.result.list;
    console.log(`\nFound ${markets.length} active markets\n`);

    // Display markets
    markets.forEach((market, i) => {
      console.log(`${i + 1}. ${market.market_title}`);
      console.log(`   Market ID: ${market.market_id}`);
      console.log();
    });

    // Get details for first market
    if (markets.length > 0) {
      const firstMarket = markets[0];
      const detail = await client.getCategoricalMarket(firstMarket.market_id);

      if (detail.errno === 0) {
        const m = detail.result.data;
        console.log(`Details for '${m.market_title}':`);
        console.log(`  Status: ${m.status}`);
        console.log(`  Question ID: ${m.question_id}`);
        console.log(`  Quote Token: ${m.quote_token}`);
      }
    }
  } else {
    console.error(`Error fetching markets: ${marketsResponse.errmsg}`);
  }
}

main();
```

{% endcode %}
{% endstep %}
{% endstepper %}

### Run Your App

```bash
# Install dotenv if not already installed
npm install dotenv

# Run the script
npx tsx my_first_app.ts
```

**Expected Output:**

```
Client initialized successfully!

Found 5 active markets

1. Will Bitcoin reach $100k by end of 2025?
   Market ID: 1

2. Will AI surpass human intelligence by 2030?
   Market ID: 2

...

Details for 'Will Bitcoin reach $100k by end of 2025?':
  Status: 2
  Condition ID: 0xabc123...
  Quote Token: 0xdef456...
```

### Next Steps

Now that you've fetched market data, explore more advanced features:

#### Trading

Learn how to place orders:

{% code title="place-order.ts" %}

```typescript
import { OrderSide, OrderType, type PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';

// Enable trading (required once before placing orders)
await client.enableTrading();

// Place a buy order of "No" token
const orderData: PlaceOrderDataInput = {
  marketId: 813,
  tokenId: '33095770954068818933468604332582424490740136703838404213332258128147961949614',
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.55',
  makerAmountInQuoteToken: '10', // 10 USDT
};

const result = await client.placeOrder(orderData);
console.log('Order placed:', result);
```

{% endcode %}

See Placing Orders for detailed examples.

#### Position Management

Track your positions:

{% code title="positions.ts" %}

```typescript
// Get balances
const balances = await client.getMyBalances();

// Get positions
const positions = await client.getMyPositions({ limit: 20 });

// Get trade history
const trades = await client.getMyTrades({ marketId: 813 });
```

{% endcode %}

See Managing Positions for more.

#### Smart Contract Operations

Interact with blockchain:

{% code title="contract-ops.ts" %}

```typescript
// Split USDT into outcome tokens
const splitResult = await client.split(813, BigInt('1000000000000000000')); // 1 USDT

// Merge outcome tokens back to USDT
const mergeResult = await client.merge(813, BigInt('1000000000000000000'));

// Redeem winnings after market resolves
const redeemResult = await client.redeem(813);
```

{% endcode %}

See Contract Operations for details.

### Common Patterns

#### Error Handling

Always check response status:

{% code title="error-handling.ts" %}

```typescript
const response = await client.getMarkets();

if (response.errno === 0) {
  // Success
  const markets = response.result.list;
} else {
  // Error
  console.error(`Error ${response.errno}: ${response.errmsg}`);
}
```

{% endcode %}

#### Using Try-Catch

{% code title="try-catch.ts" %}

```typescript
import { InvalidParamError, OpenApiError } from '@opinion-labs/opinion-clob-sdk';

try {
  const market = await client.getMarket(123);
} catch (e) {
  if (e instanceof InvalidParamError) {
    console.error(`Invalid parameter: ${e.message}`);
  } else if (e instanceof OpenApiError) {
    console.error(`API error: ${e.message}`);
  } else {
    console.error(`Unexpected error: ${e}`);
  }
}
```

{% endcode %}

#### Pagination

For large datasets:

{% code title="pagination.ts" %}

```typescript
let page = 1;
const allMarkets: any[] = [];

while (true) {
  const response = await client.getMarkets({ page, limit: 20 });
  if (response.errno !== 0) break;

  const markets = response.result.list;
  allMarkets.push(...markets);

  if (markets.length < 20) break;
  page++;
}

console.log(`Total markets: ${allMarkets.length}`);
```

{% endcode %}

### Configuration Tips

#### Cache Settings

Optimize performance with caching:

{% code title="cache-config.ts" %}

```typescript
const client = new Client({
  // ... other params ...
  marketCacheTtl: 300,              // Cache markets for 5 minutes
  quoteTokensCacheTtl: 3600,        // Cache quote tokens for 1 hour
  enableTradingCheckInterval: 3600,  // Check trading status hourly
});
```

{% endcode %}

Set to `0` to disable caching:

{% code title="disable-cache.ts" %}

```typescript
const client = new Client({
  // ... other params ...
  marketCacheTtl: 0,  // Disable market caching
});
```

{% endcode %}

#### Chain Selection

For production deployment, ensure you're using the correct configuration:

{% code title="chain-selection.ts" %}

```typescript
import { CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  chainId: CHAIN_ID_BNB_MAINNET, // 56
  rpcUrl: 'https://bsc-dataseed.binance.org',
  // ... other params ...
});
```

{% endcode %}

### Resources

* [**API Reference**](/developer-guide/opinion-clob-typescript-sdk/api-references): All Supported Methods
* [**Configuration Guide**](/developer-guide/opinion-clob-typescript-sdk/getting-started/configuration): Configuration
* [**Core Concepts**](/developer-guide/opinion-clob-typescript-sdk/core-concepts): Architecture
* [**Troubleshooting**](/developer-guide/opinion-clob-typescript-sdk/support/troubleshooting): Common Issues

***

Ready to build? Explore the API Reference to see all available methods!


# Configuration

This guide covers how to configure the Opinion CLOB TypeScript SDK for different environments and use cases.

### Client Configuration

The `Client` class accepts a configuration object during initialization:

{% code title="example.ts" %}

```typescript
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: 'your_api_key',
  chainId: CHAIN_ID_BNB_MAINNET,  // 56
  rpcUrl: 'your_rpc_url',
  privateKey: '0x...' as `0x${string}`,
  multiSigAddress: '0x...' as `0x${string}`,
  conditionalTokensAddress: '0xAD1a38cEc043e70E83a3eC30443dB285ED10D774',
  multiSendAddress: '0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526',
  feeManagerAddress: '0xC9063Dc52dEEfb518E5b6634A6b8D624bc5d7c36',
  enableTradingCheckInterval: 3600,
  quoteTokensCacheTtl: 3600,
  marketCacheTtl: 300,
  proxyUrl: 'http://127.0.0.1:7890',  // Optional HTTP proxy
});
```

{% endcode %}

### Required Parameters

#### host

**Type**: `string`\
**Description**: Opinion API host URL\
**Default**: No default (required)

```typescript
import { DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';
// DEFAULT_API_HOST = 'https://openapi.opinion.trade/openapi'
host: DEFAULT_API_HOST
```

#### apiKey

**Type**: `string`\
**Description**: API authentication key provided by Opinion Labs\
**Default**: No default (required)

How to obtain: fill out this [short application form](https://docs.google.com/forms/d/1h7gp8UffZeXzYQ-lv4jcou9PoRNOqMAQhyW4IwZDnII)

```typescript
apiKey: '________'
```

{% hint style="warning" %}
Security: Store API keys in environment variables, never in source code.
{% endhint %}

#### chainId

**Type**: `number`\
**Description**: Blockchain network chain ID\
**Supported values**:

* `56` - BNB Chain Mainnet (production)

```typescript
import { CHAIN_ID_BNB_MAINNET } from '@opinion-labs/opinion-clob-sdk';
chainId: CHAIN_ID_BNB_MAINNET  // 56
```

#### rpcUrl

**Type**: `string`\
**Description**: Blockchain RPC endpoint URL\
**Default**: No default (required)

Common providers:

* BNB Chain Mainnet: `https://bsc-dataseed.binance.org`
* BNB Chain (Nodereal): <https://bsc.nodereal.io>

```typescript
// Public RPC (rate limited)
rpcUrl: 'https://bsc-dataseed.binance.org'

// Private RPC (recommended for production)
rpcUrl: 'https://bsc.nodereal.io'
```

#### privateKey

**Type**: `` `0x${string}` ``\
**Description**: Private key for signing orders and transactions\
**Format**: 64-character hex string with `0x` prefix

```typescript
privateKey: '0x1234567890abcdef...' as `0x${string}`  // Must have 0x prefix
```

{% hint style="danger" %}
Critical Security:

* Never commit private keys to version control
* Use environment variables or secure key management systems
* Ensure the associated address has BNB for gas fees
* This is the **signer** address, may differ from multiSigAddress
  {% endhint %}

#### multiSigAddress

**Type**: `Address`\
**Description**: Multi-signature wallet address (your assets/portfolio wallet)\
**Format**: Ethereum address (checksummed or lowercase)

```typescript
multiSigAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' as `0x${string}`
```

Relationship to `privateKey`:

* `privateKey` -> Signer address (signs orders/transactions)
* `multiSigAddress` -> Assets address (holds funds/positions)
* Can be the same address or different (e.g., hot wallet signs for cold wallet)

Where to find:

* Check your Opinion platform "My Profile" section
* Or use the wallet address where you hold USDT/positions

***

### Optional Parameters

#### conditionalTokensAddress

**Type**: `Address`\
**Description**: ConditionalTokens contract address\
**Default**: `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` (BNB Chain mainnet)\
When to set: Only if using a custom deployment

#### multiSendAddress

**Type**: `Address`\
**Description**: Gnosis Safe MultiSend contract address\
**Default**: `0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526`\
When to set: Only if using a custom Gnosis Safe deployment

#### feeManagerAddress

**Type**: `Address`\
**Description**: Fee rate management contract address\
**Default**: `0xC9063Dc52dEEfb518E5b6634A6b8D624bc5d7c36`\
When to set: Only if using a custom deployment

#### enableTradingCheckInterval

**Type**: `number`\
**Description**: Cache duration (in seconds) for trading approval checks\
**Default**: `3600` (1 hour)\
**Range**: `0` to infinity

```typescript
// Default: check approval status every hour
enableTradingCheckInterval: 3600

// Check every time (no caching)
enableTradingCheckInterval: 0

// Check daily
enableTradingCheckInterval: 86400
```

Impact:

* Higher values -> Fewer RPC calls -> Faster performance
* `0` -> Always check -> Slower but always current
* Recommended: `3600` (approvals rarely change)

#### quoteTokensCacheTtl

**Type**: `number`\
**Description**: Cache duration (in seconds) for quote token data\
**Default**: `3600` (1 hour)\
**Range**: `0` to infinity

Impact:

* Quote tokens rarely change
* Higher values improve performance
* Recommended: `3600` or higher

#### marketCacheTtl

**Type**: `number`\
**Description**: Cache duration (in seconds) for market data\
**Default**: `300` (5 minutes)\
**Range**: `0` to infinity

Impact:

* Markets change frequently (prices, status)
* Lower values -> More current data
* Recommended: `300` for balance of performance and freshness

#### proxyUrl

**Type**: `string`\
**Description**: HTTP proxy URL for API requests\
**Default**: undefined (no proxy)

```typescript
proxyUrl: 'http://127.0.0.1:7890'
```

***

### Environment Variables

#### Using .env Files

Create a `.env` file in your project root:

```bash
# .env
API_KEY=opn_prod_abc123xyz789
RPC_URL=____
PRIVATE_KEY=0x1234567890abcdef...
MULTI_SIG_ADDRESS=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
CHAIN_ID=56
```

Load in your code:

{% code title="env-load.ts" %}

```typescript
import 'dotenv/config';
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!,
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: process.env.RPC_URL!,
  privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
  multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
});
```

{% endcode %}

#### Using System Environment Variables

Set in shell:

```bash
# Linux/macOS
export API_KEY="opn_prod_abc123xyz789"
export RPC_URL=___
export PRIVATE_KEY="0x..."
export MULTI_SIG_ADDRESS="0x..."

# Windows (Command Prompt)
set API_KEY=opn_prod_abc123xyz789
set RPC_URL=___

# Windows (PowerShell)
$env:API_KEY="opn_prod_abc123xyz789"
$env:RPC_URL=___
```

Then access in your code:

```typescript
const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!, // Throws if not set at runtime
  // ...
});
```

***

### Configuration Patterns

#### Multi-Environment Setup

Manage different environments (dev, staging, prod):

{% code title="multi-env.ts" %}

```typescript
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const ENVIRONMENTS = {
  production: {
    host: DEFAULT_API_HOST,
    chainId: CHAIN_ID_BNB_MAINNET,
    rpcUrl: 'https://bsc-dataseed.binance.org',
  },
} as const;

function createClient(env: 'production' = 'production') {
  const config = ENVIRONMENTS[env];
  const prefix = env.toUpperCase();

  return new Client({
    host: config.host,
    apiKey: process.env[`${prefix}_API_KEY`]!,
    chainId: config.chainId,
    rpcUrl: config.rpcUrl,
    privateKey: process.env[`${prefix}_PRIVATE_KEY`]! as `0x${string}`,
    multiSigAddress: process.env[`${prefix}_MULTI_SIG_ADDRESS`]! as `0x${string}`,
  });
}

const prodClient = createClient('production');
```

{% endcode %}

#### Factory Function

Organize configuration in a factory function:

{% code title="factory.ts" %}

```typescript
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

function createClientFromEnv(): Client {
  return new Client({
    host: DEFAULT_API_HOST,
    apiKey: process.env.API_KEY!,
    chainId: CHAIN_ID_BNB_MAINNET,
    rpcUrl: process.env.RPC_URL!,
    privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
    multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
    marketCacheTtl: 300,
  });
}

const client = createClientFromEnv();
```

{% endcode %}

#### Read-Only Client

For applications that only read data (no trading):

{% code title="readonly.ts" %}

```typescript
// Minimal configuration for read-only access
const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!,
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: '',
  privateKey: `0x${'00'.repeat(32)}` as `0x${string}`,
  multiSigAddress: '0x0000000000000000000000000000000000000000',
});

// Can use all GET methods
const markets = await client.getMarkets();
const market = await client.getMarket(123);
const orderbook = await client.getOrderbook('token_123');

// Cannot use trading or contract methods
```

{% endcode %}

***

### Performance Tuning

#### High-Frequency Trading

For trading bots with frequent API calls:

{% code title="hft.ts" %}

```typescript
const client = new Client({
  // ... required params ...
  marketCacheTtl: 60,
  quoteTokensCacheTtl: 3600,
  enableTradingCheckInterval: 7200,
});
```

{% endcode %}

#### Analytics/Research

For data analysis with less frequent updates:

{% code title="analytics.ts" %}

```typescript
const client = new Client({
  // ... required params ...
  marketCacheTtl: 1800,
  quoteTokensCacheTtl: 86400,
  enableTradingCheckInterval: 0,
});
```

{% endcode %}

#### Real-Time Monitoring

For dashboards requiring fresh data:

{% code title="realtime.ts" %}

```typescript
const client = new Client({
  // ... required params ...
  marketCacheTtl: 0,
  quoteTokensCacheTtl: 0,
  enableTradingCheckInterval: 0,
});
```

{% endcode %}

***

### Smart Contract Addresses

#### BNB Chain Mainnet (Chain ID: 56)

The following smart contract addresses are used by the Opinion CLOB TypeScript SDK on BNB Chain mainnet:

| Contract              | Address                                      | Description                                            |
| --------------------- | -------------------------------------------- | ------------------------------------------------------ |
| **ConditionalTokens** | `0xAD1a38cEc043e70E83a3eC30443dB285ED10D774` | ERC1155 conditional tokens contract for outcome tokens |
| **MultiSend**         | `0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526` | Gnosis Safe MultiSend contract for batch transactions  |
| **FeeManager**        | `0xC9063Dc52dEEfb518E5b6634A6b8D624bc5d7c36` | Fee rate management contract                           |

These addresses are automatically used by the SDK when you specify `chainId: 56`. You only need to provide custom addresses if you are using a custom deployment.

Verification:

* ConditionalTokens: [View on BscScan](https://bscscan.com/address/0xAD1a38cEc043e70E83a3eC30443dB285ED10D774)
* MultiSend: [View on BscScan](https://bscscan.com/address/0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526)
* FeeManager: [View on BscScan](https://bscscan.com/address/0xC9063Dc52dEEfb518E5b6634A6b8D624bc5d7c36)

***

### Next Steps

* [**API Reference**](/developer-guide/opinion-clob-typescript-sdk/api-references): All Supported Methods
* [**Configuration Guide**](/developer-guide/opinion-clob-typescript-sdk/getting-started/configuration): Configuration
* [**Core Concepts**](/developer-guide/opinion-clob-typescript-sdk/core-concepts): Architecture
* [**Troubleshooting**](/developer-guide/opinion-clob-typescript-sdk/support/troubleshooting): Common Issues


# Core Concepts


# Architecture

The Opinion CLOB TypeScript SDK employs a hybrid architecture integrating off-chain order matching with on-chain settlement, providing access to BNB Chain prediction markets.

## System Design

### Core Layers

```
Client (client.ts)
├── API Layer          → @opinion-labs/opinion-api (auto-generated OpenAPI client)
├── Chain Layer         → viem
│   ├── ContractCaller → ConditionalTokens, MultiSend, ERC20, FeeManager
│   └── Safe           → Gnosis Safe v1.3.0 multi-sig
└── Order Utils        → EIP712 signing (viem hashTypedData)
```

{% hint style="info" %}
Key dependencies: `viem`, `@opinion-labs/opinion-api`
{% endhint %}

### Operational Patterns

| Operation Type      | Gas Required       | Latency                   | Examples                                       |
| ------------------- | ------------------ | ------------------------- | ---------------------------------------------- |
| Market Data Queries | No                 | 50-150ms (cached: <10ms)  | `getMarkets`, `getOrderbook`, `getLatestPrice` |
| Order Placement     | No (gas-free CLOB) | 200-500ms                 | `placeOrder`, `cancelOrder`                    |
| Smart Contract Ops  | Yes (BNB)          | \~3s (block confirmation) | `enableTrading`, `split`, `merge`, `redeem`    |

## Authentication Model

A two-key security model separates signing from funding:

{% stepper %}
{% step %}

### Signing key

Private Key — Signs orders and transactions (can be a hot wallet).
{% endstep %}

{% step %}

### Funding key

Multi-Sig Address — Holds USDT and outcome tokens (typically Gnosis Safe v1.3.0).
{% endstep %}
{% endstepper %}

This architecture enables both individual traders and multi-signature wallet holders to participate seamlessly.

## Token Specifications

* All tokens use **18 decimal places** (Wei standard)
* `1 USDT = 1,000,000,000,000,000,000 wei (10^18)`
* Prices expressed as probability decimals: `"0.5"` = 50% chance
* Valid price range: `0.01` to `0.99`


# Client

The `Client` class is the main interface to the Opinion prediction market infrastructure. It provides methods for market data queries, trading, position management, and blockchain interactions.

## Initialization

{% code title="example.ts" %}

```typescript
import 'dotenv/config';
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!,
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: process.env.RPC_URL!,
  privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
  multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
});
```

{% endcode %}

{% hint style="warning" %}
Security: Never hardcode API keys or private keys. Always load from environment variables.
{% endhint %}

## Method Categories

| Category         | Description                         | Gas Required       |
| ---------------- | ----------------------------------- | ------------------ |
| Market Data      | Query markets, orderbooks, prices   | No                 |
| Trading          | Place and cancel orders             | No (gas-free CLOB) |
| User Data        | Query positions, balances, trades   | No                 |
| Token Operations | split, merge, redeem, enableTrading | Yes (BNB)          |

## Response Format

All API methods return a consistent response wrapper:

{% code title="example.ts" %}

```typescript
const response = await client.getMarkets();

// Response structure
response.errno    // 0 = success
response.errmsg   // Error message (empty on success)
response.result   // Data payload
```

{% endcode %}

The `ApiResponse<T>` type is defined as:

{% code title="types.ts" %}

```typescript
interface ApiResponse<T> {
  errno: number;
  errmsg: string;
  result: T;
}
```

{% endcode %}

## Caching

The Client implements built-in caching for frequently accessed data:

| Cache          | Default TTL      | Recommendation                   |
| -------------- | ---------------- | -------------------------------- |
| Quote Tokens   | 3600s (1 hour)   | Rarely changes, keep high        |
| Market Data    | 300s (5 minutes) | Balance freshness vs performance |
| Enable Trading | 3600s (1 hour)   | Approvals rarely change          |

Cache TTLs can be configured during initialization:

{% code title="example.ts" %}

```typescript
const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: process.env.API_KEY!,
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: process.env.RPC_URL!,
  privateKey: process.env.PRIVATE_KEY! as `0x${string}`,
  multiSigAddress: process.env.MULTI_SIG_ADDRESS! as `0x${string}`,
  quoteTokensCacheTtl: 7200,  // 2 hours
  marketCacheTtl: 120,         // 2 minutes
  enableTradingCheckInterval: 1800, // 30 minutes
});
```

{% endcode %}

Bypass cache for individual calls:

{% code title="example.ts" %}

```typescript
// Bypass cache
const tokens = await client.getQuoteTokens(false);
const market = await client.getMarket(123, false);
```

{% endcode %}

## Best Practices

{% stepper %}
{% step %}

### Single Instance

Create one Client for your application; connection pooling is automatic.
{% endstep %}

{% step %}

### Error Checking

Always check `errno` before accessing `result`.
{% endstep %}

{% step %}

### Cache Tuning

Adjust cache TTLs based on your use case.
{% endstep %}

{% step %}

### Connectivity Verification

Test with `getQuoteTokens()` before trading.
{% endstep %}

{% step %}

### Async/Await

All Client methods are async; always use `await` when calling them.
{% endstep %}
{% endstepper %}


# Order

This section explains how orders work in the Opinion prediction market, including order types, sides, and amount specifications.

## Order Types

<table><thead><tr><th width="130">Type</th><th>Enum Value</th><th width="213">Description</th><th>Price Parameter</th></tr></thead><tbody><tr><td><strong>Market Order</strong></td><td><code>OrderType.MARKET_ORDER</code></td><td>Execute immediately at best available price</td><td>Ignored, price is always  <code>"0"</code> for market order.</td></tr><tr><td><strong>Limit Order</strong></td><td><code>OrderType.LIMIT_ORDER</code></td><td>Execute only at specified price or better</td><td>Required (e.g., <code>"0.50"</code>)</td></tr></tbody></table>

## Order Sides

| Side     | Enum Value       | Description                                    |
| -------- | ---------------- | ---------------------------------------------- |
| **BUY**  | `OrderSide.BUY`  | Purchase outcome tokens (predict event occurs) |
| **SELL** | `OrderSide.SELL` | Sell outcome tokens (close position or short)  |

## Post-Only Orders

Post-only orders are limit orders that must add liquidity. Market orders with `postOnly` throw `InvalidParamError`. If the order would cross the spread, the system cancels it instead of executing it as a taker, with the comment: `post-only order would cross the spread`.

```typescript
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInQuoteToken: '100',
  price: '0.6',
  orderType: OrderType.LIMIT_ORDER,
  side: OrderSide.BUY,
  postOnly: true,
};

const result = await client.placeOrder(orderData, false);
```

## Price

Prices represent implied probability:

* Valid range: `0.01` to `0.99`
* Maximum 4 decimal places
* `"0.50"` = 50% probability, costs $0.50 per token, pays $1.00 if correct

## Amount Specifications

You must specify exactly **one** of:

| Parameter                 | Type     | Usage                         |
| ------------------------- | -------- | ----------------------------- |
| `makerAmountInQuoteToken` | `string` | Specify USDT to spend/receive |
| `makerAmountInBaseToken`  | `string` | Specify exact token quantity  |

Examples:

* BUY order at price `0.60`, spending `100` USDT -> receives \~166.67 tokens
* SELL order at price `0.60`, selling `200` tokens -> receives 120 USDT

### Restrictions

| Side | Order Type | Allowed Amount                 |
| ---- | ---------- | ------------------------------ |
| BUY  | Market     | `makerAmountInQuoteToken` only |
| SELL | Market     | `makerAmountInBaseToken` only  |
| BUY  | Limit      | Either                         |
| SELL | Limit      | Either                         |

## Placing Orders

{% code title="place-orders.ts" %}

```typescript
import { OrderSide, OrderType, type PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';

// Limit BUY: spend 10 USDT on YES tokens at 0.5
const buyOrder: PlaceOrderDataInput = {
  marketId: 123,
  tokenId: 'yes_token_id',
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.5',
  makerAmountInQuoteToken: '10',
};
const result = await client.placeOrder(buyOrder);

// Market BUY: spend 10 USDT at market price
const marketBuy: PlaceOrderDataInput = {
  marketId: 123,
  tokenId: 'yes_token_id',
  side: OrderSide.BUY,
  orderType: OrderType.MARKET_ORDER,
  price: '0',
  makerAmountInQuoteToken: '10',
};
const result2 = await client.placeOrder(marketBuy);

// Limit SELL: sell 20 YES tokens at 0.6
const sellOrder: PlaceOrderDataInput = {
  marketId: 123,
  tokenId: 'yes_token_id',
  side: OrderSide.SELL,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.6',
  makerAmountInBaseToken: '20',
};
const result3 = await client.placeOrder(sellOrder);
```

{% endcode %}

## Cancelling Orders

{% code title="cancel-orders.ts" %}

```typescript
// Cancel single order
await client.cancelOrder('order_123');

// Cancel multiple orders
const results = await client.cancelOrdersBatch(['order_1', 'order_2', 'order_3']);

// Cancel all open orders (optionally filter by market/side)
const summary = await client.cancelAllOrders({ marketId: 123, side: OrderSide.BUY });
console.log(`Cancelled: ${summary.cancelled}, Failed: ${summary.failed}`);
```

{% endcode %}

## Batch Operations

{% code title="batch-operations.ts" %}

```typescript
// Place multiple orders at once
const orders = [order1, order2, order3];
const results = await client.placeOrdersBatch(orders, true);

for (const r of results) {
  if (r.success) {
    console.log(`Order ${r.index}: placed successfully`);
  } else {
    console.log(`Order ${r.index}: failed - ${r.error}`);
  }
}
```

{% endcode %}

Note: The `placeOrdersBatch` method accepts an optional second parameter `checkApproval` (default `false`). When set to `true`, it calls `enableTrading()` once before placing any orders.

## Best Practices

* Review orderbook before placing market orders to assess slippage
* Break large orders into smaller limit orders to minimize market impact
* Validate price is within `[0.01, 0.99]` range before submission
* Use batch methods for multiple orders to reduce latency


# Gas Operations

The Opinion CLOB SDK uses a hybrid execution model. Order matching occurs off-chain via CLOB infrastructure (gas-free), while direct smart contract operations require BNB for transaction fees.

## Gas-Free Operations

These operations are authenticated via EIP712 cryptographic signatures and submitted to the Opinion API. No gas is required.

Supported Operations:

* Market data queries (`getMarkets`, `getOrderbook`, `getLatestPrice`, etc.)
* Order management (`placeOrder`, `cancelOrder`, `placeOrdersBatch`, etc.)
* Position tracking (`getMyBalances`, `getMyPositions`, `getMyTrades`)

## Gas-Required Operations

These operations modify blockchain state and require BNB native token for gas fees.

| Operation       | Approx. Gas | Description                               |
| --------------- | ----------: | ----------------------------------------- |
| `enableTrading` |   \~100,000 | One-time ERC20/ERC1155 approvals          |
| `split`         |   \~150,000 | Convert collateral to outcome tokens      |
| `merge`         |   \~120,000 | Convert outcome tokens back to collateral |
| `redeem`        |   \~180,000 | Claim winning payouts after resolution    |

### Enable Trading

One-time approval to allow the exchange to use your tokens. Result is cached.

{% code title="example.ts" %}

```typescript
const result = await client.enableTrading();
if (result.txHash) {
  console.log(`Trading enabled. TX: ${result.txHash}`);
} else {
  console.log('Trading already enabled (cached)');
}
```

{% endcode %}

The return type is `TransactionResult`:

{% code title="types.ts" %}

```typescript
interface TransactionResult {
  txHash: string;
  safeTxHash: string;
  returnValue: string;
}
```

{% endcode %}

### Split

Convert collateral (USDT) into outcome token pairs (YES + NO).

{% code title="split.ts" %}

```typescript
// Split 100 USDT into YES + NO tokens
const amountWei = BigInt(100) * BigInt(10 ** 18);
const result = await client.split(
  123,       // marketId
  amountWei, // amount in wei
  true,      // checkApproval: auto-enable trading if needed
);
console.log(`Split TX: ${result.txHash}`);
```

{% endcode %}

Parameters:

* `marketId` (number) - The market ID
* `amount` (bigint) - Amount in wei
* `checkApproval` (boolean, default `true`) - Auto-call `enableTrading()` if needed

### Merge

Convert outcome token pairs back to collateral.

{% code title="merge.ts" %}

```typescript
const amountWei = BigInt(50) * BigInt(10 ** 18);
const result = await client.merge(123, amountWei, true);
```

{% endcode %}

Parameters:

* `marketId` (number) - The market ID
* `amount` (bigint) - Amount in wei
* `checkApproval` (boolean, default `true`) - Auto-call `enableTrading()` if needed

### Redeem

Claim winnings from resolved markets.

{% code title="redeem.ts" %}

```typescript
const result = await client.redeem(123, true);
```

{% endcode %}

Parameters:

* `marketId` (number) - The market ID (must be in RESOLVED status)
* `checkApproval` (boolean, default `true`) - Auto-call `enableTrading()` if needed

## Gas Cost Estimation

At typical BNB Chain conditions (0.05 Gwei gas price, \~$600/BNB):

| Operation       | Approximate Cost |
| --------------- | ---------------: |
| `enableTrading` |         \~$0.003 |
| `split`         |        \~$0.0045 |
| `merge`         |         \~$0.004 |
| `redeem`        |         \~$0.005 |

## Recommended BNB Balance

| Use Case       |     Recommended BNB |
| -------------- | ------------------: |
| Initial setup  | 0.001 BNB (\~$0.60) |
| Active trading |  0.01 BNB (\~$6.00) |
| High-frequency |  0.1 BNB (\~$60.00) |

## Optimization Strategy

{% stepper %}
{% step %}

### Enable trading once

Approval is cached, so you only need to call `enableTrading()` one time.
{% endstep %}

{% step %}

### Split in bulk

Create a large token inventory in one transaction to reduce the number of on-chain operations.
{% endstep %}

{% step %}

### Trade via CLOB

All order placement and cancellation are gas-free when performed via the CLOB off-chain infrastructure.
{% endstep %}

{% step %}

### Merge/Redeem when needed

Only perform `merge` or `redeem` when exiting positions or after market resolution.
{% endstep %}
{% endstepper %}


# Precision

Understanding token decimals, price formats, and amount conversions is critical for correct trading.

## Token Decimal System

Both USDT (collateral) and outcome tokens (YES/NO) use **18 decimal places**:

| Human Amount | Wei Amount                        |
| ------------ | --------------------------------- |
| 1 USDT       | 1,000,000,000,000,000,000 (10^18) |
| 0.5 USDT     | 500,000,000,000,000,000           |
| 100 USDT     | 100,000,000,000,000,000,000       |

## Price Format

Prices are strings representing implied probability:

* Valid range: `0.01` to `0.99`
* Maximum 4 decimal places
* `"0.50"` = 50% probability = $0.50 per token = $1.00 payout if correct

## Wei Conversion

The SDK provides a `safeAmountToWei` utility for converting human-readable amounts to wei:

{% code title="example.ts" %}

```typescript
import { safeAmountToWei } from '@opinion-labs/opinion-clob-sdk';

// Convert human amount to wei
const weiAmount = safeAmountToWei(100.0, 18); // 100 USDT -> wei
console.log(weiAmount); // 100000000000000000000n (BigInt)

// For smart contract operations, pass BigInt directly
await client.split(123, weiAmount);
```

{% endcode %}

{% hint style="warning" %}
Use BigInt for wei amounts to avoid JavaScript number precision issues.
{% endhint %}

Example comparisons:

{% code title="bigint-examples.ts" %}

```typescript
// Correct - BigInt
const amount = BigInt(100) * BigInt(10 ** 18);

// Avoid - Number (loses precision for large values)
const amount = 100 * 10 ** 18; // May lose precision!
```

{% endcode %}

You can also construct wei values directly with `BigInt`:

{% code title="direct-wei.ts" %}

```typescript
// 100 USDT in wei
const oneHundredUsdt = BigInt(100) * BigInt(10 ** 18);

// 0.5 USDT in wei
const halfUsdt = BigInt(5) * BigInt(10 ** 17);
```

{% endcode %}

## Order Amount Handling

When placing orders, provide amounts in **human-readable format** (not wei). The SDK handles conversion internally.

{% code title="place-order.ts" %}

```typescript
import { OrderSide, OrderType, type PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';

// Spend 10 USDT - provide as string, SDK converts to wei internally
const order: PlaceOrderDataInput = {
  marketId: 123,
  tokenId: 'token_id',
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.5',
  makerAmountInQuoteToken: '10', // 10 USDT (human-readable)
};
```

{% endcode %}

The distinction is important:

* **Order amounts** (`makerAmountInQuoteToken`, `makerAmountInBaseToken`): human-readable strings, SDK converts internally
* **Smart contract amounts** (`split`, `merge`): wei as `bigint`, you must convert manually

## Common Errors to Avoid

{% stepper %}
{% step %}
Wrong decimals

USDT uses 18 decimals (not 6) in this system.
{% endstep %}

{% step %}
Float arithmetic

Use `BigInt` for precise wei calculations.
{% endstep %}

{% step %}
Price range

Prices must be within `[0.01, 0.99]`.
{% endstep %}

{% step %}
Manual wei conversion for orders

The SDK handles this; provide human-readable amounts.
{% endstep %}

{% step %}
Manual wei conversion for split/merge

You must provide wei amounts directly.
{% endstep %}
{% endstepper %}


# API References


# Models

This page documents the enums, data types, response structures, and exception types used in the TypeScript SDK (`@opinion-labs/opinion-clob-sdk` v0.6.1).

***

## Enums

### TopicType

Market type for filtering.

```typescript
import { TopicType } from '@opinion-labs/opinion-clob-sdk';
```

| Value                   | Numeric | Description                            |
| ----------------------- | ------- | -------------------------------------- |
| `TopicType.BINARY`      | 0       | Binary (Yes/No) markets                |
| `TopicType.CATEGORICAL` | 1       | Categorical (multiple outcome) markets |
| `TopicType.ALL`         | 2       | All market types                       |

### TopicStatus

Market lifecycle status.

```typescript
import { TopicStatus } from '@opinion-labs/opinion-clob-sdk';
```

| Value                   | Numeric | Description                                   |
| ----------------------- | ------- | --------------------------------------------- |
| `TopicStatus.CREATED`   | 1       | Market has been created but is not yet active |
| `TopicStatus.ACTIVATED` | 2       | Market is active and open for trading         |
| `TopicStatus.RESOLVING` | 3       | Market is in the resolution process           |
| `TopicStatus.RESOLVED`  | 4       | Market has been resolved with a final outcome |
| `TopicStatus.FAILED`    | 5       | Market creation or resolution failed          |
| `TopicStatus.DELETED`   | 6       | Market has been deleted                       |

### TopicStatusFilter

Status filter for querying markets via `getMarkets()`.

```typescript
import { TopicStatusFilter } from '@opinion-labs/opinion-clob-sdk';
```

| Value                         | String        | Description                             |
| ----------------------------- | ------------- | --------------------------------------- |
| `TopicStatusFilter.ALL`       | `""`          | Return all markets regardless of status |
| `TopicStatusFilter.ACTIVATED` | `"activated"` | Only active/tradable markets            |
| `TopicStatusFilter.RESOLVED`  | `"resolved"`  | Only resolved markets                   |

### TopicSortType

Sorting options for market listings.

```typescript
import { TopicSortType } from '@opinion-labs/opinion-clob-sdk';
```

| Value                              | Numeric | Description                          |
| ---------------------------------- | ------- | ------------------------------------ |
| `TopicSortType.BY_TIME_DESC`       | 1       | Sort by creation time (newest first) |
| `TopicSortType.BY_CUTOFF_TIME_ASC` | 2       | Sort by cutoff time (soonest first)  |
| `TopicSortType.BY_VOLUME_DESC`     | 3       | Sort by total volume (highest first) |
| `TopicSortType.BY_VOLUME_ASC`      | 4       | Sort by total volume (lowest first)  |
| `TopicSortType.BY_VOLUME_24H_DESC` | 5       | Sort by 24h volume (highest first)   |
| `TopicSortType.BY_VOLUME_24H_ASC`  | 6       | Sort by 24h volume (lowest first)    |
| `TopicSortType.BY_VOLUME_7D_DESC`  | 7       | Sort by 7d volume (highest first)    |
| `TopicSortType.BY_VOLUME_7D_ASC`   | 8       | Sort by 7d volume (lowest first)     |

### OrderSide

Order direction.

```typescript
import { OrderSide } from '@opinion-labs/opinion-clob-sdk';
```

| Value            | Numeric | Description |
| ---------------- | ------- | ----------- |
| `OrderSide.BUY`  | 0       | Buy order   |
| `OrderSide.SELL` | 1       | Sell order  |

### OrderType

Order execution type.

```typescript
import { OrderType } from '@opinion-labs/opinion-clob-sdk';
```

| Value                    | Numeric | Description                                         |
| ------------------------ | ------- | --------------------------------------------------- |
| `OrderType.MARKET_ORDER` | 1       | Market order (executed at best available price)     |
| `OrderType.LIMIT_ORDER`  | 2       | Limit order (executed at specified price or better) |

***

## Data Types

### PlaceOrderDataInput

The input type used when placing orders via `placeOrder()` or `placeOrdersBatch()`.

```typescript
import type { PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';
import { OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';

const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  price: '0.6',
  makerAmountInQuoteToken: '100',  // 100 USDT
};
```

| Field                     | Type      | Required | Description                                                                                                                    |
| ------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `marketId`                | number    | Yes      | Market ID / Topic ID                                                                                                           |
| `tokenId`                 | string    | Yes      | Token ID of the CTF ERC1155 asset (e.g., Yes token ID)                                                                         |
| `side`                    | OrderSide | Yes      | `OrderSide.BUY` or `OrderSide.SELL`                                                                                            |
| `orderType`               | OrderType | Yes      | `OrderType.MARKET_ORDER` or `OrderType.LIMIT_ORDER`                                                                            |
| `price`                   | string    | Yes      | Price per outcome token (e.g., `"0.5"` for 50 cents). Required for limit orders; set to any value for market orders.           |
| `makerAmountInQuoteToken` | string    | No       | Amount in quote token (e.g., `"100"` USDC). Use for buy orders or when specifying by dollar value.                             |
| `makerAmountInBaseToken`  | string    | No       | Amount in outcome tokens (e.g., `"50"` Yes tokens). Use for sell orders or when specifying by share count.                     |
| postOnly                  | boolean   | No       | Limit orders only; if it would cross the spread, it is cancelled instead of taking liquidity. Market orders are not supported. |

Amount rules:

* You must provide exactly one of `makerAmountInQuoteToken` or `makerAmountInBaseToken`.
* For **market buy** orders: only `makerAmountInQuoteToken` is allowed.
* For **market sell** orders: only `makerAmountInBaseToken` is allowed.
* For **limit** orders: either field is accepted.

### LabelData

```
interface LabelData {
  labelId: number;
  labelName: string;
  imageUrl?: string;
  imageMobileUrl?: string;
}
```

### ClientConfig

Configuration options for initializing the `Client`.

```typescript
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: 'YOUR_API_KEY',
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: 'YOUR_RPC_URL',
  privateKey: '0xYOUR_PRIVATE_KEY',
  multiSigAddress: '0xYOUR_MULTI_SIG_ADDRESS',
});
```

| Field                        | Type    | Required | Description                                                    |
| ---------------------------- | ------- | -------- | -------------------------------------------------------------- |
| `host`                       | string  | Yes      | API host URL                                                   |
| `apiKey`                     | string  | Yes      | API authentication key                                         |
| `chainId`                    | number  | Yes      | Blockchain chain ID (56 for BNB Chain)                         |
| `rpcUrl`                     | string  | Yes      | RPC endpoint URL                                               |
| `privateKey`                 | Hex     | Yes      | Private key for signing transactions                           |
| `multiSigAddress`            | Address | Yes      | Multi-signature wallet address                                 |
| `conditionalTokensAddress`   | Address | No       | Conditional tokens contract address                            |
| `multiSendAddress`           | Address | No       | MultiSend contract address                                     |
| `feeManagerAddress`          | Address | No       | FeeManager contract address                                    |
| `ctfExchangeAddress`         | Address | No       | CTF Exchange contract address (overrides API-provided)         |
| `enableTradingCheckInterval` | number  | No       | Cache TTL for enable trading checks in seconds (default: 3600) |
| `quoteTokensCacheTtl`        | number  | No       | Cache TTL for quote tokens in seconds (default: 3600)          |
| `marketCacheTtl`             | number  | No       | Cache TTL for market data in seconds (default: 300)            |
| `proxyUrl`                   | string  | No       | HTTP proxy URL for API requests                                |

### TransactionResult

Returned by blockchain operations (`enableTrading`, `split`, `merge`, `redeem`).

```typescript
interface TransactionResult {
  txHash?: string;  // Transaction hash (undefined if no on-chain tx was needed)
  success: boolean;
}
```

***

## Response Structure

All API methods return responses in a consistent wrapper format:

```typescript
interface ApiResponse<T> {
  errno: number;   // 0 = success, non-zero = error
  errmsg: string;  // Error message (empty on success)
  result: T;       // Response payload
}
```

### Example: Successful response

```json
{
  "errno": 0,
  "errmsg": "",
  "result": {
    "total": 10,
    "list": [
      {
        "marketId": 5,
        "marketTitle": "Example Market",
        "status": 2,
        "statusEnum": "activated",
        "yesTokenId": "101128430008250248394161770787866737734499464157137890629662678617009651760181",
        "noTokenId": "60119751433323231793441870248949152266777555824072219518426778460783591201702",
        "yesLabel": "Yes",
        "noLabel": "No",
        "quoteToken": "0x5Fd47a476d9c8309dF84D50cfAa0AB576c28DF0D",
        "volume": "2.000000000000000000",
        "conditionId": "...",
        "cutoffAt": 1734537600,
        "createdAt": 1734515671,
        "rules": ""
      }
    ]
  }
}
```

### Example: Error response

```json
{
  "errno": 500,
  "errmsg": "error message",
  "result": null
}
```

### Common errno Values

| errno | Description              |
| ----- | ------------------------ |
| 0     | Success                  |
| 1     | Internal error           |
| 2     | Invalid parameter        |
| 100   | Invalid API key          |
| 200   | Market not found         |
| 300   | Order not found          |
| 301   | Order cannot be canceled |
| 303   | Insufficient balance     |
| 304   | Price out of range       |
| 305   | Amount too small         |
| 429   | Rate limit exceeded      |
| 500   | Internal server error    |

***

## Exception Types

The SDK defines custom exception classes for different error scenarios. All exceptions extend the base `SdkError` class.

```typescript
import {
  SdkError,
  InvalidParamError,
  OpenApiError,
  BalanceNotEnough,
  NoPositionsToRedeem,
  InsufficientGasBalance,
  ValidationException,
} from '@opinion-labs/opinion-clob-sdk';
```

| Exception                | When Thrown                                                                                                       |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `SdkError`               | Base class for all SDK errors. Not thrown directly.                                                               |
| `InvalidParamError`      | Invalid parameters passed to SDK methods (e.g., invalid `marketId`, missing required fields, price out of range). |
| `OpenApiError`           | API call failures (e.g., network errors, unexpected API responses, chain mismatch).                               |
| `BalanceNotEnough`       | Insufficient token balance for an operation.                                                                      |
| `NoPositionsToRedeem`    | Attempting to redeem from a market with no redeemable positions.                                                  |
| `InsufficientGasBalance` | Signer wallet does not have enough gas (BNB) for a blockchain transaction.                                        |
| `ValidationException`    | Order validation failed during the order building process.                                                        |

### Error Handling Example

```typescript
import {
  Client,
  InvalidParamError,
  OpenApiError,
  BalanceNotEnough,
} from '@opinion-labs/opinion-clob-sdk';

try {
  const result = await client.placeOrder(orderData);
} catch (error) {
  if (error instanceof InvalidParamError) {
    console.error('Invalid parameter:', error.message);
  } else if (error instanceof BalanceNotEnough) {
    console.error('Insufficient balance:', error.message);
  } else if (error instanceof OpenApiError) {
    console.error('API error:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}
```


# Methods

Complete API reference for the TypeScript SDK (`@opinion-labs/opinion-clob-sdk` v0.6.1). All methods are async and use camelCase naming.

***

## Market Data Methods

### getLabels

Retrieve the list of market labels available for filtering.

```typescript
const result = await client.getLabels();
result.result.list.forEach(label => {
  console.log(label.labelId, label.labelName);
});
```

Use the returned `labelId` with `getMarkets({ labelId })`. Treat `labelId` as the stable key; `labelName` may be renamed.

**Response:** `ApiResponse<LabelListResult>` containing `{ list }`. Each `LabelData` has `labelId`, `labelName`, `imageUrl?`, and `imageMobileUrl?`.

### getMarkets

Get a paginated list of markets available for trading.

```typescript
import { TopicType, TopicStatusFilter, TopicSortType } from '@opinion-labs/opinion-clob-sdk';

const result = await client.getMarkets({
  topicType: TopicType.ALL,               // Optional: BINARY, CATEGORICAL, ALL (default: ALL)
  labelId: 0, // Optional: label ID; omit or use 0 to disable filtering
  page: 1,                                 // Optional: page number (default: 1)
  limit: 20,                               // Optional: items per page (default: 20, max: 20)
  status: TopicStatusFilter.ACTIVATED,      // Optional: ALL, ACTIVATED, RESOLVED
  sortBy: TopicSortType.BY_VOLUME_DESC,     // Optional: sorting method (default: BY_TIME_DESC)
});

console.log(result.result.total);           // Total number of matching markets
console.log(result.result.list);            // Array of market objects
```

Parameters:

| Parameter   | Type              | Required | Description                                        |
| ----------- | ----------------- | -------- | -------------------------------------------------- |
| `topicType` | TopicType         | No       | Filter by market type (default: `ALL`)             |
| `page`      | number            | No       | Page number, must be >= 1 (default: 1)             |
| `limit`     | number            | No       | Items per page, 1-20 (default: 20)                 |
| `status`    | TopicStatusFilter | No       | Filter by market status                            |
| `sortBy`    | TopicSortType     | No       | Sorting method (default: `BY_TIME_DESC`)           |
| labelId     | number            | No       | Filter by label ID; omit or use 0 for no filtering |

Response: `ApiResponse<MarketListResult>` containing `{ total, list }`.

***

### getMarket

Get detailed information about a binary market.

```typescript
const result = await client.getMarket(67);          // market_id as number
const resultNoCache = await client.getMarket(67, false);  // bypass cache

console.log(result.result.data.marketTitle);
console.log(result.result.data.yesTokenId);
console.log(result.result.data.noTokenId);
console.log(result.result.data.conditionId);
```

Parameters:

| Parameter  | Type    | Required | Description                                  |
| ---------- | ------- | -------- | -------------------------------------------- |
| `marketId` | number  | Yes      | Market ID                                    |
| `useCache` | boolean | No       | Use cached data if available (default: true) |

Response: `ApiResponse<MarketDetailResult>` containing `{ data }` with market details.

***

### getCategoricalMarket

Get detailed information about a categorical market, including all child markets.

```typescript
const result = await client.getCategoricalMarket(10);

console.log(result.result.data.marketTitle);
console.log(result.result.data.childMarkets);  // Array of child market objects
```

Parameters:

| Parameter  | Type   | Required | Description           |
| ---------- | ------ | -------- | --------------------- |
| `marketId` | number | Yes      | Categorical market ID |

Response: `ApiResponse<MarketDetailResult>` containing `{ data }` with market details and `childMarkets` array.

***

### getQuoteTokens

Retrieve the list of supported quote tokens (currencies) on the platform.

```typescript
const result = await client.getQuoteTokens();          // with cache
const resultNoCache = await client.getQuoteTokens(false);  // bypass cache

for (const token of result.result.list) {
  console.log(token.quoteTokenName);       // e.g., "Base USDC"
  console.log(token.quoteTokenAddress);    // Contract address
  console.log(token.ctfExchangeAddress);   // Exchange contract address
  console.log(token.decimal);              // Token decimals (e.g., 6)
  console.log(token.symbol);              // e.g., "USDC"
}
```

Parameters:

| Parameter  | Type    | Required | Description                                  |
| ---------- | ------- | -------- | -------------------------------------------- |
| `useCache` | boolean | No       | Use cached data if available (default: true) |

Response: `ApiResponse<QuoteTokenListResult>` containing `{ total, list }`.

***

### getOrderbook

Get the orderbook (market depth) for a specific token.

```typescript
const result = await client.getOrderbook('34780524119202758863327378805823157249587136341915838311361711249158835002904');

console.log(result.result.bids);       // Array of [price, shares] buy orders
console.log(result.result.asks);       // Array of [price, shares] sell orders
console.log(result.result.lastPrice);  // Latest trade price
```

Parameters:

| Parameter | Type   | Required | Description                           |
| --------- | ------ | -------- | ------------------------------------- |
| `tokenId` | string | Yes      | Token ID (the outcome token to query) |

Response: `ApiResponse<OrderbookResponse>` with `bids`, `asks`, and `lastPrice`.

Response fields:

| Field       | Description                                |
| ----------- | ------------------------------------------ |
| `symbol`    | Token ID                                   |
| `ts`        | Timestamp in milliseconds                  |
| `bids`      | Array of `[price, shares]` for buy orders  |
| `asks`      | Array of `[price, shares]` for sell orders |
| `lastPrice` | Latest price from the last trade           |

***

### getLatestPrice

Get the latest price for a specific token.

```typescript
const result = await client.getLatestPrice('YOUR_TOKEN_ID');

console.log(result.result);  // Latest price data
```

Parameters:

| Parameter | Type   | Required | Description |
| --------- | ------ | -------- | ----------- |
| `tokenId` | string | Yes      | Token ID    |

Response: `ApiResponse<LatestPriceResponse>`.

***

### getPriceHistory

Get price history for a specific token.

```typescript
const result = await client.getPriceHistory('YOUR_TOKEN_ID', {
  interval: '1h',     // '1m', '1h', '1d', '1w', or 'max'
  startAt: 1700000000,  // Optional: start timestamp (Unix)
  endAt: 1700100000,    // Optional: end timestamp (Unix)
});

console.log(result.result);  // Array of price history data points
```

Parameters:

| Parameter  | Type   | Required | Description                                                              |
| ---------- | ------ | -------- | ------------------------------------------------------------------------ |
| `tokenId`  | string | Yes      | Token ID                                                                 |
| `interval` | string | No       | Time interval: `'1m'`, `'1h'`, `'1d'`, `'1w'`, `'max'` (default: `'1h'`) |
| `startAt`  | number | No       | Start timestamp (Unix seconds)                                           |
| `endAt`    | number | No       | End timestamp (Unix seconds)                                             |

Response: `ApiResponse<PriceHistoryResponse>`.

***

### getFeeRates

Get fee rates from the on-chain FeeManager contract for a specific token.

```typescript
const feeRates = await client.getFeeRates('YOUR_TOKEN_ID');

console.log(feeRates);  // FeeRateSettings object
```

Parameters:

| Parameter | Type   | Required | Description |
| --------- | ------ | -------- | ----------- |
| `tokenId` | string | Yes      | Token ID    |

Response: `FeeRateSettings` object (not wrapped in ApiResponse since this is an on-chain call).

***

## Trading Methods

### placeOrder

Place a market or limit order.

Limit Buy Order (by quote token amount):

```typescript
import { OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';
import type { PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';

const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInQuoteToken: '100',  // 100 USDT
  price: '0.6',
  orderType: OrderType.LIMIT_ORDER,
  side: OrderSide.BUY,
};

const result = await client.placeOrder(orderData, false);
console.log(result);
```

Limit Buy Order (by shares):

```typescript
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInBaseToken: '50',  // 50 Yes tokens
  price: '0.6',
  orderType: OrderType.LIMIT_ORDER,
  side: OrderSide.BUY,
};

const result = await client.placeOrder(orderData, false);
```

Market Buy Order:

```typescript
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInQuoteToken: '100',  // 100 USDT worth
  orderType: OrderType.MARKET_ORDER,
  side: OrderSide.BUY,
  price: '0',  // Price is ignored for market orders
};

const result = await client.placeOrder(orderData, false);
```

Limit Sell Order:

```typescript
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInBaseToken: '10',  // 10 Yes tokens
  price: '0.6',
  orderType: OrderType.LIMIT_ORDER,
  side: OrderSide.SELL,
};

const result = await client.placeOrder(orderData, false);
```

**Post-Only Limit Order:**

```typescript
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'YOUR_TOKEN_ID',
  makerAmountInQuoteToken: '100',
  price: '0.6',
  orderType: OrderType.LIMIT_ORDER,
  side: OrderSide.BUY,
  postOnly: true,
};

const result = await client.placeOrder(orderData, false);
```

Post-only is supported only for limit orders. Market orders with `postOnly` throw `InvalidParamError`. If a post-only order would cross the spread, the system cancels it with the comment: `post-only order would cross the spread`.

Parameters:

| Parameter       | Type                | Required | Description                                                                           |
| --------------- | ------------------- | -------- | ------------------------------------------------------------------------------------- |
| `data`          | PlaceOrderDataInput | Yes      | Order details (see [Models](broken://pages/f02726b8147af96802941231fb941b729461a2ea)) |
| `checkApproval` | boolean             | No       | Check and enable trading first (default: false)                                       |

Response: `ApiResponse<CreateOrderResponse>` with order data including `trans_no` (order ID).

Successful response example:

```json
{
  "errno": 0,
  "errmsg": "",
  "result": {
    "order_data": {
      "trans_no": "504d7580-cd59-11ef-887f-0a58a9feac02",
      "topic_title": "example",
      "side": 1,
      "outcome": "Yes",
      "price": "0.60000002400000096",
      "filled": "0/0.05",
      "status": 1
    }
  }
}
```

***

### placeOrdersBatch

Place multiple orders in batch. Orders are submitted sequentially. If one fails, the rest continue.

```typescript
const orders: PlaceOrderDataInput[] = [
  {
    marketId: 57,
    tokenId: 'TOKEN_1',
    makerAmountInQuoteToken: '100',
    price: '0.5',
    orderType: OrderType.LIMIT_ORDER,
    side: OrderSide.BUY,
  },
  {
    marketId: 57,
    tokenId: 'TOKEN_2',
    makerAmountInQuoteToken: '100',
    price: '0.6',
    orderType: OrderType.LIMIT_ORDER,
    side: OrderSide.BUY,
  },
];

const results = await client.placeOrdersBatch(orders, true);

for (const r of results) {
  if (r.success) {
    console.log(`Order ${r.index} placed:`, r.result);
  } else {
    console.log(`Order ${r.index} failed:`, r.error);
  }
}
```

Parameters:

| Parameter       | Type                   | Required | Description                                     |
| --------------- | ---------------------- | -------- | ----------------------------------------------- |
| `orders`        | PlaceOrderDataInput\[] | Yes      | Array of order inputs                           |
| `checkApproval` | boolean                | No       | Check and enable trading first (default: false) |

Response: `Array<{ index: number; success: boolean; result?: unknown; error?: string }>`.

***

### cancelOrder

Cancel a single pending order.

```typescript
const result = await client.cancelOrder('504d7580-cd59-11ef-887f-0a58a9feac02');
console.log(result);
```

Parameters:

| Parameter | Type   | Required | Description                                     |
| --------- | ------ | -------- | ----------------------------------------------- |
| `orderId` | string | Yes      | Order ID (`trans_no` from place order response) |

Response: `ApiResponse<CancelOrderResponse>`.

***

### cancelOrdersBatch

Cancel multiple orders at once.

```typescript
const orderIds = ['order_id_1', 'order_id_2', 'order_id_3'];
const results = await client.cancelOrdersBatch(orderIds);

for (const r of results) {
  if (r.success) {
    console.log(`Order ${r.index} cancelled`);
  } else {
    console.log(`Order ${r.index} failed:`, r.error);
  }
}
```

Parameters:

| Parameter  | Type      | Required | Description                  |
| ---------- | --------- | -------- | ---------------------------- |
| `orderIds` | string\[] | Yes      | Array of order IDs to cancel |

Response: `Array<{ index: number; success: boolean; result?: unknown; error?: string }>`.

***

### cancelAllOrders

Cancel all open orders, optionally filtered by market and/or side. Automatically paginates through all open orders.

```typescript
import { OrderSide } from '@opinion-labs/opinion-clob-sdk';

// Cancel all open orders
const result = await client.cancelAllOrders();

// Cancel all open orders for a specific market
const result2 = await client.cancelAllOrders({ marketId: 57 });

// Cancel all open BUY orders
const result3 = await client.cancelAllOrders({ side: OrderSide.BUY });

// Cancel all open SELL orders for a specific market
const result4 = await client.cancelAllOrders({ marketId: 57, side: OrderSide.SELL });

console.log(result.totalOrders);   // Total orders found
console.log(result.cancelled);     // Successfully cancelled
console.log(result.failed);        // Failed to cancel
console.log(result.results);       // Detailed results for each order
```

Parameters:

| Parameter  | Type      | Required | Description                        |
| ---------- | --------- | -------- | ---------------------------------- |
| `marketId` | number    | No       | Filter by market ID                |
| `side`     | OrderSide | No       | Filter by order side (BUY or SELL) |

Response:

```typescript
{
  totalOrders: number;
  cancelled: number;
  failed: number;
  results: Array<{ index: number; success: boolean; result?: unknown; error?: string }>;
}
```

***

## User Data Methods

### getMyOrders

Query your orders with optional filters.

```typescript
// Get all orders
const result = await client.getMyOrders();

// Filter by market_id and status
const result2 = await client.getMyOrders({
  marketId: 57,
  status: '1',    // 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed, 6=on chain failed
  page: 1,
  limit: 10,
});

console.log(result.result.total);
console.log(result.result.list);
```

Parameters:

| Parameter  | Type   | Required | Description                                                                                                       |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `marketId` | number | No       | Filter by market ID                                                                                               |
| `status`   | string | No       | Filter by status: `'1'`=pending, `'2'`=filled, `'3'`=canceled, `'4'`=expired, `'5'`=failed, `'6'`=on chain failed |
| `page`     | number | No       | Page number (default: 1)                                                                                          |
| `limit`    | number | No       | Items per page (default: 10)                                                                                      |

Response: `ApiResponse<OrderListResponse>` with `{ total, list }`.

Response fields per order:

| Field            | Description                                                              |
| ---------------- | ------------------------------------------------------------------------ |
| `trans_no`       | Order ID (use to cancel)                                                 |
| `side`           | 0 = buy, 1 = sell                                                        |
| `status`         | 1=pending, 2=filled, 3=cancelled, 4=expired, 5=failed, 6=on chain failed |
| `trading_method` | 1=market order, 2=limit order                                            |
| `filled`         | Amount filled / Total amount                                             |
| `total_price`    | Total value of the order                                                 |

***

### getOrderById

Get detailed information about a specific order.

```typescript
const result = await client.getOrderById('504d7580-cd59-11ef-887f-0a58a9feac02');
console.log(result.result);
```

Parameters:

| Parameter | Type   | Required | Description           |
| --------- | ------ | -------- | --------------------- |
| `orderId` | string | Yes      | Order ID (`trans_no`) |

Response: `ApiResponse<OrderDetailResponse>`.

***

### getMyBalances

Get ERC20 balances for the authenticated user.

```typescript
const result = await client.getMyBalances();

for (const balance of result.result) {
  console.log(balance.totalBalance);       // Total in-app balance
  console.log(balance.balance);            // Available balance
  console.log(balance.netWorth);           // Total net worth
  console.log(balance.currencyAddress);    // Token contract address
}
```

Parameters: None.

Response: `ApiResponse<UserBalanceResponse>` (array of balance objects).

Response fields per balance:

| Field             | Description                  |
| ----------------- | ---------------------------- |
| `totalBalance`    | All in-app balance           |
| `balance`         | Current available balance    |
| `netWorth`        | Total net worth              |
| `currencyAddress` | ERC20 token contract address |

***

### getMyPositions

Query your positions.

```typescript
// Get all positions
const result = await client.getMyPositions();

// With pagination and market filter
const result2 = await client.getMyPositions({
  marketId: 23,
  page: 1,
  limit: 10,
});

console.log(result.result.total);
console.log(result.result.list);
```

Parameters:

| Parameter  | Type   | Required | Description                  |
| ---------- | ------ | -------- | ---------------------------- |
| `marketId` | number | No       | Filter by market ID          |
| `page`     | number | No       | Page number (default: 1)     |
| `limit`    | number | No       | Items per page (default: 10) |

Response: `ApiResponse<PositionsResponse>` with `{ total, list }`.

Response fields per position:

| Field               | Description                                |
| ------------------- | ------------------------------------------ |
| `topicId`           | Market ID                                  |
| `tokenId`           | Token ID of the position                   |
| `outcome`           | Position outcome label (e.g., "Yes", "No") |
| `tokenAmount`       | Amount of tokens held                      |
| `tokenFrozenAmount` | Amount frozen in open orders               |
| `profit`            | Unrealized profit                          |
| `positionAvgPrice`  | Average price of the position              |

***

### getMyTrades

Query your trade history (matched orders become trades).

```typescript
// Get all trades
const result = await client.getMyTrades();

// Filter by market with pagination
const result2 = await client.getMyTrades({
  marketId: 57,
  page: 1,
  limit: 10,
});

console.log(result.result.total);
console.log(result.result.list);
```

Parameters:

| Parameter  | Type   | Required | Description                  |
| ---------- | ------ | -------- | ---------------------------- |
| `marketId` | number | No       | Filter by market ID          |
| `page`     | number | No       | Page number (default: 1)     |
| `limit`    | number | No       | Items per page (default: 10) |

Response: `ApiResponse<TradeListResponse>` with `{ total, list }`.

Response fields per trade:

| Field         | Description                                                             |
| ------------- | ----------------------------------------------------------------------- |
| `transNo`     | Trade ID                                                                |
| `txHash`      | Blockchain transaction hash                                             |
| `side`        | "Buy" or "Sell"                                                         |
| `outcome`     | Outcome label (e.g., "Yes", "No")                                       |
| `shares`      | Number of tokens traded                                                 |
| `amount`      | Quote token amount                                                      |
| `profit`      | Realized profit from the trade                                          |
| `status`      | 1=pending, 2=filled, 3=canceled, 4=expired, 5=failed, 6=on chain failed |
| `outcomeSide` | 1=yes token, 2=no token                                                 |
| `tradeType`   | 1=taker, 2=maker                                                        |
| `fee`         | Trading fee                                                             |

***

### getUserAuth

Get authenticated user information.

```typescript
const result = await client.getUserAuth();
console.log(result.result);
```

Parameters: None.

Response: `ApiResponse<UserAuthResponse>`.

***

## Smart Contract Methods

These methods interact directly with the blockchain and require gas (BNB on BNB Chain).

### enableTrading

Authorize the O.LAB operator to process your orders on chain. You only need to call this once per trading account.

```typescript
const result = await client.enableTrading();

console.log(result.success);   // true if successful
console.log(result.txHash);    // Transaction hash (undefined if already enabled)
```

Parameters: None.

Response: `TransactionResult` with `{ txHash?, success }`.

Notes:

* Must be called before your first order can be filled.
* If trading is already enabled, the method returns successfully without submitting a transaction.
* Requires BNB in the signer wallet for gas.

***

### split

Convert collateral (e.g., USDC) into equal amounts of Yes and No outcome tokens.

```typescript
const result = await client.split(
  57,                          // marketId
  BigInt(100 * 10**18),        // amount in wei (100 tokens with 18 decimals)
  true                         // checkApproval (default: true)
);

// For USDC with 6 decimals:
const result2 = await client.split(57, BigInt(100 * 10**6));

console.log(result.success);
console.log(result.txHash);
```

Parameters:

| Parameter       | Type    | Required | Description                                    |
| --------------- | ------- | -------- | ---------------------------------------------- |
| `marketId`      | number  | Yes      | Market ID                                      |
| `amount`        | bigint  | Yes      | Amount of collateral to split (in wei)         |
| `checkApproval` | boolean | No       | Check and enable trading first (default: true) |

Response: `TransactionResult` with `{ txHash, success }`.

Notes:

* Requires gas (BNB).
* After splitting, you receive equal amounts of Yes and No tokens.
* Market must be in ACTIVATED, RESOLVING, or RESOLVED status.

***

### merge

Merge outcome tokens (Yes + No) back into collateral.

```typescript
const result = await client.merge(
  23,                          // marketId
  BigInt(100 * 10**18),        // amount in wei
  true                         // checkApproval (default: true)
);

console.log(result.success);
console.log(result.txHash);
```

Parameters:

| Parameter       | Type    | Required | Description                                    |
| --------------- | ------- | -------- | ---------------------------------------------- |
| `marketId`      | number  | Yes      | Market ID                                      |
| `amount`        | bigint  | Yes      | Amount of outcome tokens to merge (in wei)     |
| `checkApproval` | boolean | No       | Check and enable trading first (default: true) |

Response: `TransactionResult` with `{ txHash, success }`.

Notes:

* Requires gas (BNB).
* You need equal amounts of Yes and No tokens to merge.
* Market must be in ACTIVATED, RESOLVING, or RESOLVED status.
* After merging, you receive the original collateral tokens back.

***

### redeem

Claim collateral from a resolved market by redeeming winning outcome tokens.

```typescript
const result = await client.redeem(23);      // marketId
const result2 = await client.redeem(23, false);  // skip approval check

console.log(result.success);
console.log(result.txHash);
```

Parameters:

| Parameter       | Type    | Required | Description                                    |
| --------------- | ------- | -------- | ---------------------------------------------- |
| `marketId`      | number  | Yes      | Market ID                                      |
| `checkApproval` | boolean | No       | Check and enable trading first (default: true) |

Response: `TransactionResult` with `{ txHash, success }`.

Notes:

* Requires gas (BNB).
* Market must be in RESOLVED status.
* Only winning outcome tokens can be redeemed.
* Losing tokens have no redemption value.

***

## WebSocket

### createWebSocketClient

Create a WebSocket client for real-time market data. The client is pre-configured with the API key and wallet address from the `Client` instance.

```typescript
const wsClient = client.createWebSocketClient({
  heartbeatInterval: 30,   // Seconds between heartbeats (default: 30)
  onMessage: (msg) => console.log('Received:', msg),
  onOpen: () => console.log('Connected'),
  onClose: () => console.log('Disconnected'),
  onError: (error) => console.error('Error:', error),
});

// Connect
await wsClient.connect();

// Check connection status
console.log(wsClient.isConnected);
```

Configuration options:

| Parameter           | Type     | Required | Description                                              |
| ------------------- | -------- | -------- | -------------------------------------------------------- |
| `heartbeatInterval` | number   | No       | Seconds between heartbeat messages (default: 30)         |
| `wsUrl`             | string   | No       | WebSocket server URL (default: `wss://ws.opinion.trade`) |
| `onMessage`         | function | No       | Callback for all incoming messages                       |
| `onOpen`            | function | No       | Callback when connection opens                           |
| `onClose`           | function | No       | Callback when connection closes                          |
| `onError`           | function | No       | Callback on connection error                             |

### Subscription Channels

#### subscribeMarketDepthDiff

Subscribe to real-time orderbook depth updates for a specific market.

```typescript
wsClient.subscribeMarketDepthDiff(1274, (msg) => {
  console.log(`Market ${msg.marketId}: ${msg.side} ${msg.price} @ ${msg.size}`);
  console.log(`Token: ${msg.tokenId}, Outcome: ${msg.outcomeSide}`);
});

// Unsubscribe
wsClient.unsubscribeMarketDepthDiff(1274);
```

Message fields:

| Field          | Type   | Description                              |
| -------------- | ------ | ---------------------------------------- |
| `marketId`     | number | Market ID                                |
| `rootMarketId` | number | Root market ID (for categorical markets) |
| `tokenId`      | string | Token ID of the updated token            |
| `outcomeSide`  | number | 1 = yes, 2 = no                          |
| `side`         | string | `"bids"` or `"asks"`                     |
| `price`        | string | Price level                              |
| `size`         | string | Size in shares                           |

#### subscribeMarketLastPrice

Subscribe to last price updates.

```typescript
// For binary market
wsClient.subscribeMarketLastPrice({ marketId: 1274 }, (msg) => {
  console.log(`Market ${msg.marketId}: Last price ${msg.price}`);
});

// For categorical market
wsClient.subscribeMarketLastPrice({ rootMarketId: 61 }, (msg) => {
  console.log(`Market ${msg.marketId}: Last price ${msg.price}`);
});

// Unsubscribe
wsClient.unsubscribeMarketLastPrice({ marketId: 1274 });
```

#### subscribeMarketLastTrade

Subscribe to last trade updates.

```typescript
wsClient.subscribeMarketLastTrade({ marketId: 1274 }, (msg) => {
  console.log(`Market ${msg.marketId}: ${msg.side} ${msg.shares} shares @ ${msg.price}`);
});

// Unsubscribe
wsClient.unsubscribeMarketLastTrade({ marketId: 1274 });
```

#### subscribeTradeOrderUpdate

Subscribe to order status updates for your account.

```typescript
wsClient.subscribeTradeOrderUpdate({ marketId: 1274 }, (msg) => {
  console.log(`Order ${msg.orderId}: ${msg.orderUpdateType}, Status: ${msg.status}`);
  console.log(`Filled: ${msg.filledShares} shares, ${msg.filledAmount} amount`);
});

// Unsubscribe
wsClient.unsubscribeTradeOrderUpdate({ marketId: 1274 });
```

Message fields:

| Field             | Type   | Description                                                    |
| ----------------- | ------ | -------------------------------------------------------------- |
| `orderUpdateType` | string | `"orderNew"`, `"orderFill"`, `"orderCancel"`, `"orderConfirm"` |
| `orderId`         | string | Order ID                                                       |
| `side`            | number | 1 = buy, 2 = sell                                              |
| `status`          | number | 1=pending, 2=finished, 3=canceled, 4=expired, 5=failed         |
| `tradingMethod`   | number | 1 = market, 2 = limit                                          |
| `filledShares`    | string | Filled shares (updated after orderConfirm)                     |
| `filledAmount`    | string | Filled amount (updated after orderConfirm)                     |

#### subscribeTradeRecordNew

Subscribe to new trade record notifications.

```typescript
wsClient.subscribeTradeRecordNew({ marketId: 1274 }, (msg) => {
  console.log(`Trade ${msg.tradeNo}: ${msg.side} ${msg.shares} shares @ ${msg.price}`);
  console.log(`Fee: ${msg.fee}, Profit: ${msg.profit}`);
});

// Unsubscribe
wsClient.unsubscribeTradeRecordNew({ marketId: 1274 });
```

### Close Connection

```typescript
wsClient.close();
```

### Full WebSocket Example

```typescript
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  host: DEFAULT_API_HOST,
  apiKey: 'YOUR_API_KEY',
  chainId: CHAIN_ID_BNB_MAINNET,
  rpcUrl: 'YOUR_RPC_URL',
  privateKey: '0xYOUR_PRIVATE_KEY',
  multiSigAddress: '0xYOUR_MULTI_SIG_ADDRESS',
});

const wsClient = client.createWebSocketClient({
  onOpen: () => console.log('WebSocket connected'),
  onClose: () => console.log('WebSocket disconnected'),
  onError: (error) => console.error('WebSocket error:', error),
});

await wsClient.connect();

// Subscribe to depth updates
wsClient.subscribeMarketDepthDiff(1274, (msg) => {
  console.log(`Depth: ${msg.side} ${msg.price} @ ${msg.size}`);
});

// Subscribe to order updates
wsClient.subscribeTradeOrderUpdate({ marketId: 1274 }, (msg) => {
  console.log(`Order update: ${msg.orderUpdateType} - ${msg.orderId}`);
});

// Keep running...
// When done:
// wsClient.close();
```

***


# Support


# FAQ

Frequently asked questions about the TypeScript SDK (`@opinion-labs/opinion-clob-sdk` v0.5.2).

***

## Installation and Setup

<details>

<summary>How do I install the SDK?</summary>

```bash
npm install @opinion-labs/opinion-clob-sdk
```

The package is published on npm as `@opinion-labs/opinion-clob-sdk`.

</details>

<details>

<summary>What are the minimum requirements?</summary>

* **Node.js**: 18 or later
* **TypeScript**: 5.3 or later (if using TypeScript)
* The SDK is an ESM package and requires an ESM-compatible project setup.

</details>

<details>

<summary>How do I use the SDK in a CommonJS project?</summary>

The SDK is published as ESM. If your project uses CommonJS (`require`), use dynamic `import()`:

```javascript
async function main() {
  const { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } = await import(
    '@opinion-labs/opinion-clob-sdk'
  );

  const client = new Client({
    host: DEFAULT_API_HOST,
    apiKey: 'YOUR_API_KEY',
    chainId: CHAIN_ID_BNB_MAINNET,
    rpcUrl: 'YOUR_RPC_URL',
    privateKey: '0xYOUR_PRIVATE_KEY',
    multiSigAddress: '0xYOUR_MULTI_SIG_ADDRESS',
  });
}

main();
```

Alternatively, add `"type": "module"` to your `package.json` to enable ESM.

</details>

<details>

<summary>What imports do I need?</summary>

```typescript
// Main client
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST } from '@opinion-labs/opinion-clob-sdk';

// Enums
import {
  TopicType,
  TopicStatus,
  TopicStatusFilter,
  TopicSortType,
  OrderSide,
  OrderType,
} from '@opinion-labs/opinion-clob-sdk';

// Types
import type { PlaceOrderDataInput } from '@opinion-labs/opinion-clob-sdk';

// Error classes
import {
  InvalidParamError,
  OpenApiError,
  BalanceNotEnough,
  NoPositionsToRedeem,
  InsufficientGasBalance,
  ValidationException,
} from '@opinion-labs/opinion-clob-sdk';

// Builder mode (optional)
import { BuilderClient, UserClient } from '@opinion-labs/opinion-clob-sdk';
```

</details>

***

## Configuration

<details>

<summary>Which chain IDs are supported?</summary>

Currently only **BNB Chain mainnet (chain ID 56)** is supported. Use the constant `CHAIN_ID_BNB_MAINNET`:

```typescript
import { CHAIN_ID_BNB_MAINNET } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  chainId: CHAIN_ID_BNB_MAINNET, // 56
  // ...
});
```

</details>

<details>

<summary>What is the difference between the private key and the multi-sig address?</summary>

* **Private Key (`privateKey`)**: The key of your **login wallet** (EOA). Used for signing orders and transactions. This wallet needs BNB for gas fees on blockchain operations.
* **Multi-Sig Address (`multiSigAddress`)**: Your **asset wallet** (Gnosis Safe). This is where your trading funds are held. It is the address shown in "My Portfolio" on the platform.

The signer (login wallet) authorizes trades on behalf of the multi-sig (asset wallet).

</details>

<details>

<summary>How do I get an API key?</summary>

Contact the Opinion Labs team to obtain your API key for SDK access. The API key is passed in the `apikey` HTTP header for all API requests.

</details>

<details>

<summary>How do I find my asset wallet (multi-sig) address?</summary>

1. Log in at [app.olab.xyz](https://app.olab.xyz/).
2. Click the **Add Fund** button on the navigation bar.
3. Click **Create Your Asset Wallet** if you do not have one.
4. Wait a few minutes and reopen the popup.
5. Copy the address displayed.

</details>

<details>

<summary>How do I configure a proxy?</summary>

Pass the `proxyUrl` option during client initialization:

```typescript
const client = new Client({
  // ...
  proxyUrl: 'http://127.0.0.1:7890',
});
```

The proxy applies to both HTTP API calls and WebSocket connections.

</details>

<details>

<summary>Can I override the default contract addresses?</summary>

Yes, but only do so if instructed by the Opinion Labs team:

```typescript
const client = new Client({
  // ...
  conditionalTokensAddress: '0x...',
  multiSendAddress: '0x...',
  feeManagerAddress: '0x...',
  ctfExchangeAddress: '0x...',
});
```

</details>

***

## Trading

<details>

<summary>What is the difference between market orders and limit orders?</summary>

* **Market order** (`OrderType.MARKET_ORDER`): Executes immediately at the best available price. You specify how much to spend (buy) or how many shares to sell. Price is ignored.
* **Limit order** (`OrderType.LIMIT_ORDER`): Executes at your specified price or better. If the market price does not reach your limit, the order remains open until filled, canceled, or expired.

</details>

<details>

<summary>How do I specify order amounts?</summary>

There are two ways:

* **By quote token** (`makerAmountInQuoteToken`): Specify how much USDC to spend. Example: `"100"` means 100 USDC.
* **By shares** (`makerAmountInBaseToken`): Specify how many outcome tokens (shares). Example: `"50"` means 50 Yes tokens.

Rules:

| Order Type | Side | Allowed Amount Field           |
| ---------- | ---- | ------------------------------ |
| Market     | Buy  | `makerAmountInQuoteToken` only |
| Market     | Sell | `makerAmountInBaseToken` only  |
| Limit      | Buy  | Either                         |
| Limit      | Sell | Either                         |

</details>

<details>

<summary>Do I need to call enableTrading() before every order?</summary>

No. You only need to call `enableTrading()` once per trading account. After that, your orders can be filled on-chain.

Alternatively, pass `checkApproval: true` to `placeOrder()`, which will check and enable trading if needed. However, for performance, it is better to call `enableTrading()` once at startup.

</details>

<details>

<summary>How do I cancel orders?</summary>

```typescript
// Cancel a single order
await client.cancelOrder('order_id');

// Cancel multiple orders
await client.cancelOrdersBatch(['id_1', 'id_2', 'id_3']);

// Cancel all open orders
await client.cancelAllOrders();

// Cancel all BUY orders for a specific market
await client.cancelAllOrders({ marketId: 57, side: OrderSide.BUY });
```

</details>

<details>

<summary>What are the order status codes?</summary>

| Status | Meaning         |
| ------ | --------------- |
| 1      | Pending (open)  |
| 2      | Filled          |
| 3      | Canceled        |
| 4      | Expired         |
| 5      | Failed          |
| 6      | On-chain failed |

</details>

***

## Smart Contracts

<details>

<summary>Which operations require gas (BNB)?</summary>

| Operation          | Gas Required | Description                               |
| ------------------ | ------------ | ----------------------------------------- |
| `enableTrading()`  | Yes          | Approve token spending on-chain           |
| `split()`          | Yes          | Convert collateral to outcome tokens      |
| `merge()`          | Yes          | Convert outcome tokens back to collateral |
| `redeem()`         | Yes          | Claim winnings from resolved markets      |
| `placeOrder()`     | No           | Off-chain signed order                    |
| `cancelOrder()`    | No           | Off-chain cancellation                    |
| `getMarkets()`     | No           | API call                                  |
| All `get*` methods | No           | API calls                                 |

</details>

<details>

<summary>What is the split/merge/redeem workflow?</summary>

1. **Split**: Deposit USDC into a market to receive equal amounts of Yes and No tokens.
2. **Trade**: Buy or sell outcome tokens on the order book.
3. **Merge**: Convert equal amounts of Yes and No tokens back to USDC (useful to exit both sides).
4. **Redeem**: After market resolution, exchange winning tokens for USDC.

</details>

<details>

<summary>What market statuses allow split and merge?</summary>

Both `split()` and `merge()` require the market to be in one of these statuses:

* `ACTIVATED` (2)
* `RESOLVING` (3)
* `RESOLVED` (4)

</details>

<details>

<summary>What market status is required for redeem?</summary>

Only `RESOLVED` (4). The market must have a final outcome before you can redeem.

</details>

***

## Errors

<details>

<summary>Why do I get "Invalid parameter" errors?</summary>

Common causes:

* `page` is less than 1.
* `limit` is less than 1 or greater than 20.
* `marketId` is 0 or negative.
* `tokenId` is an empty string.
* `orderId` is an empty string.
* Using `makerAmountInBaseToken` for a market buy order (not allowed).
* Using `makerAmountInQuoteToken` for a market sell order (not allowed).
* Amount is less than 1 (minimum order size).

</details>

<details>

<summary>What does "Cannot split/merge/redeem on different chain" mean?</summary>

The market you are trying to interact with belongs to a different blockchain than your client is configured for. Ensure `chainId` is set to 56 and the market is on BNB Chain.

</details>

<details>

<summary>What does "Cannot redeem on non-resolved market" mean?</summary>

You can only redeem from markets that have been resolved (status = 4). Check the market status first:

```typescript
const market = await client.getMarket(marketId);
console.log(market.result.data.status);     // Should be 4 (RESOLVED)
console.log(market.result.data.statusEnum); // Should be "resolved"
```

</details>

<details>

<summary>How do I handle network errors?</summary>

Wrap your calls in try/catch and check for `OpenApiError`:

```typescript
import { OpenApiError } from '@opinion-labs/opinion-clob-sdk';

try {
  const result = await client.getMarkets();
} catch (error) {
  if (error instanceof OpenApiError) {
    console.error('API error:', error.message);
    // Retry logic here
  }
}
```

</details>

***

## Data and Precision

<details>

<summary>How are prices represented?</summary>

Prices are strings representing a value between 0 and 1. For example, `"0.55"` means 55 cents per outcome token. A price of `"1"` means the outcome is considered certain.

</details>

<details>

<summary>How are amounts represented?</summary>

* **API responses**: Amounts are returned as strings with full decimal precision (e.g., `"887306.479964285714285714"`).
* **placeOrder input**: Use human-readable strings (e.g., `"100"` for 100 USDC).
* **split/merge input**: Use `bigint` in wei (e.g., `BigInt(100 * 10**6)` for 100 USDC with 6 decimals).

</details>

<details>

<summary>What decimal precision should I use for split/merge amounts?</summary>

It depends on the collateral token's decimals. Check with `getQuoteTokens()`:

```typescript
const tokens = await client.getQuoteTokens();
const decimal = tokens.result.list[0].decimal;  // e.g., 6 for USDC, 18 for others

// For USDC (6 decimals): 100 USDC = 100 * 10^6
const amount = BigInt(100 * 10 ** decimal);
await client.split(marketId, amount);
```

</details>

<details>

<summary>Why are there floating-point precision differences in prices?</summary>

The SDK uses string-based arithmetic internally to minimize floating-point issues. Prices and amounts in API responses are always strings. When performing your own calculations, avoid native floating-point math; use a library like `decimal.js` for precise calculations.

</details>

***

## Support

<details>

<summary>Where can I find more documentation?</summary>

* SDK Documentation: [https://docs.opinion.trade](https://docs.opinion.trade/)
* API Reference: [https://openapi.opinion.trade](https://openapi.opinion.trade/)

</details>

<details>

<summary>How do I report a bug?</summary>

* Email: <support@opinion.trade>
* GitHub Issues: Open an issue on the SDK repository with:
  * SDK version (`npm list @opinion-labs/opinion-clob-sdk`)
  * Node.js version (`node --version`)
  * Full error message and stack trace
  * Minimal code to reproduce the issue

</details>

<details>

<summary>Is there a Python SDK available?</summary>

Yes. The Python SDK (`opinion_clob_sdk`) provides equivalent functionality. See the [Python SDK documentation](broken://pages/aaef683222d9e73b8acd42b7944a31af122b2ed6) for details.

</details>


# Troubleshooting

Common issues and solutions for the TypeScript SDK (`@opinion-labs/opinion-clob-sdk` v0.5.2).

***

## Installation Issues

### Cannot find module '@opinion-labs/opinion-clob-sdk'

**Problem:** TypeScript or Node.js cannot locate the SDK package.

**Solution:**

{% stepper %}
{% step %}

### Verify installation

Run:

```bash
npm list @opinion-labs/opinion-clob-sdk
```

{% endstep %}

{% step %}

### Install the package

If not installed, run:

```bash
npm install @opinion-labs/opinion-clob-sdk
```

{% endstep %}

{% step %}

### Monorepo/workspace check

If using a monorepo or workspace, ensure the dependency is in the correct `package.json`.
{% endstep %}
{% endstepper %}

***

### ERR\_REQUIRE\_ESM or "require() of ES Module not supported"

**Problem:** The SDK is an ESM (ECMAScript Module) package, but your project uses CommonJS `require()`.

**Solution:**

Option A - Switch your project to ESM:

Add `"type": "module"` to your `package.json`:

```json
{
  "type": "module"
}
```

Option B - Use dynamic import in CommonJS:

```javascript
async function main() {
  const { Client } = await import('@opinion-labs/opinion-clob-sdk');
  // Use Client normally
}
main();
```

***

### TypeScript compiler errors with viem types

**Problem:** Type errors related to `Address`, `Hex`, or other viem types.

**Solution:**

Ensure your `tsconfig.json` has the following settings:

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "node16",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  }
}
```

Key requirements:

* `target` must be `ES2020` or later (for BigInt support).
* `moduleResolution` should be `node16` or `bundler`.
* `skipLibCheck: true` helps avoid transitive type conflicts.

***

### Node.js version compatibility

**Problem:** Runtime errors or unexpected behavior.

**Solution:** The SDK requires Node.js 18 or later. Check your version:

```bash
node --version
```

If below v18, upgrade Node.js. The SDK uses features like native `fetch`, BigInt, and ES modules.

***

## Configuration Errors

### "chainId must be one of 56" (Unsupported Chain ID)

**Problem:** `InvalidParamError` thrown during `Client` initialization.

**Cause:** Only BNB Chain mainnet (chain ID 56) is currently supported.

**Solution:**

```typescript
import { Client, CHAIN_ID_BNB_MAINNET } from '@opinion-labs/opinion-clob-sdk';

const client = new Client({
  // ...
  chainId: CHAIN_ID_BNB_MAINNET,  // Always use 56
});
```

***

### Missing Environment Variables

**Problem:** Client initialization fails due to missing credentials.

**Solution:**

{% stepper %}
{% step %}

### Provide required fields to Client

```typescript
const client = new Client({
  host: process.env.API_HOST || 'https://openapi.opinion.trade/openapi',
  apiKey: process.env.API_KEY!,           // Required
  chainId: 56,
  rpcUrl: process.env.RPC_URL!,           // Required
  privateKey: process.env.PRIVATE_KEY!,   // Required (0x-prefixed)
  multiSigAddress: process.env.MULTI_SIG_ADDRESS!,  // Required
});
```

{% endstep %}

{% step %}

### Create a .env file

```
API_KEY=your_api_key
RPC_URL=https://bsc-dataseed.binance.org
PRIVATE_KEY=0xYOUR_PRIVATE_KEY
MULTI_SIG_ADDRESS=0xYOUR_MULTI_SIG_ADDRESS
```

{% endstep %}

{% step %}

### Load environment variables

Use a library like `dotenv` to load it:

```typescript
import 'dotenv/config';
```

{% endstep %}
{% endstepper %}

***

## API and Trading Errors

### Common errno Values

| errno | Meaning                  | How to Fix                                           |
| ----- | ------------------------ | ---------------------------------------------------- |
| 0     | Success                  | No action needed                                     |
| 100   | Invalid API key          | Verify API key, check for extra whitespace           |
| 200   | Market not found         | Verify market ID exists and is correct               |
| 201   | Market not active        | Market is not in ACTIVATED status                    |
| 300   | Order not found          | Verify order ID (trans\_no)                          |
| 301   | Order cannot be canceled | Order is already filled, canceled, or expired        |
| 303   | Insufficient balance     | Deposit more funds or cancel other open orders       |
| 304   | Price out of range       | Price must be between 0 and 1 for prediction markets |
| 305   | Amount too small         | Increase order amount (minimum 1 unit)               |
| 429   | Rate limit exceeded      | Reduce request frequency, implement backoff          |
| 500   | Internal server error    | Retry after a short delay                            |

***

### InvalidParamError: "makerAmountInBaseToken is not allowed for market buy"

**Problem:** Attempting a market buy order with `makerAmountInBaseToken`.

**Solution:** Market buy orders must use `makerAmountInQuoteToken`:

```typescript
// Correct - Market buy with quote token amount
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'TOKEN_ID',
  makerAmountInQuoteToken: '100',  // Use quote token for market buys
  orderType: OrderType.MARKET_ORDER,
  side: OrderSide.BUY,
  price: '0',
};
```

***

### InvalidParamError: "makerAmountInQuoteToken is not allowed for market sell"

**Problem:** Attempting a market sell order with `makerAmountInQuoteToken`.

**Solution:** Market sell orders must use `makerAmountInBaseToken`:

```typescript
// Correct - Market sell with base token (shares) amount
const orderData: PlaceOrderDataInput = {
  marketId: 57,
  tokenId: 'TOKEN_ID',
  makerAmountInBaseToken: '10',  // Use base token for market sells
  orderType: OrderType.MARKET_ORDER,
  side: OrderSide.SELL,
  price: '0',
};
```

***

### OpenApiError: "Quote token not found for this market"

**Problem:** The market's quote token is not in the list of supported quote tokens.

**Solution:**

1. Ensure you are using the correct chain ID (56).
2. Call `getQuoteTokens()` to see supported tokens.
3. Verify the market's quote token matches one of the supported tokens.

***

### OpenApiError: "Cannot place order on different chain"

**Problem:** The market belongs to a different chain than the client is configured for.

**Solution:** Ensure your `chainId` matches the market's chain. Currently only chain ID 56 (BNB Chain) is supported.

***

### Rate Limiting (errno: 429)

**Problem:** Too many requests in a short period.

**Solution:** Implement exponential backoff:

```typescript
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof OpenApiError && error.message.includes('rate limit')) {
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited, waiting ${waitTime}ms...`);
        await new Promise((resolve) => setTimeout(resolve, waitTime));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}
```

***

## Blockchain Issues

### InsufficientGasBalance

**Problem:** The signer wallet does not have enough BNB for gas fees.

**Solution:**

{% stepper %}
{% step %}

### Check signer wallet balance

Check the BNB balance of your **signer wallet** (the wallet corresponding to `privateKey`).
{% endstep %}

{% step %}

### Deposit BNB

Deposit BNB to the signer wallet address.
{% endstep %}

{% step %}

### Know which ops require gas

Gas is required for `enableTrading()`, `split()`, `merge()`, and `redeem()`.
{% endstep %}

{% step %}

### Note about orders

Placing and canceling orders does NOT require gas (they are off-chain signed operations).
{% endstep %}
{% endstepper %}

***

### Transaction Reverted

**Problem:** An on-chain transaction reverts.

**Common causes and solutions:**

* split fails: Check that you have enough collateral (USDC) in your asset wallet. The market must be in ACTIVATED, RESOLVING, or RESOLVED status.
* merge fails: You need equal amounts of Yes and No tokens. Check your position balances.
* redeem fails: Market must be in RESOLVED status. You must hold winning outcome tokens.
* enableTrading fails: Check that the signer wallet has enough BNB for gas.

***

### Wrong Contract Addresses

**Problem:** Transactions fail because of incorrect contract addresses.

**Solution:** The SDK uses default contract addresses for BNB Chain. If you need to override them:

```typescript
const client = new Client({
  // ... other options
  conditionalTokensAddress: '0x...',
  multiSendAddress: '0x...',
  feeManagerAddress: '0x...',
  ctfExchangeAddress: '0x...',
});
```

Only override these if instructed by the Opinion Labs team.

***

## WebSocket Issues

### "WebSocket is not connected. Call connect() first."

**Problem:** Attempting to subscribe or send messages before the WebSocket connection is established.

**Solution:** Always `await` the `connect()` call before subscribing:

```typescript
const wsClient = client.createWebSocketClient({
  onOpen: () => console.log('Connected'),
});

await wsClient.connect();  // Wait for connection

// Now safe to subscribe
wsClient.subscribeMarketDepthDiff(1274, (msg) => {
  console.log(msg);
});
```

***

### WebSocket connection drops

**Problem:** WebSocket disconnects unexpectedly.

**Solution:**

{% stepper %}
{% step %}

### Heartbeat

The SDK sends automatic heartbeat messages (default every 30 seconds).
{% endstep %}

{% step %}

### Reconnect on close

Use the `onClose` callback to detect disconnections and implement reconnection logic:

```typescript
const wsClient = client.createWebSocketClient({
  onClose: async () => {
    console.log('Disconnected, reconnecting...');
    await wsClient.connect();
    // Re-subscribe to channels
  },
});
```

{% endstep %}
{% endstepper %}

***

## Performance Tips

### Use Caching

The SDK caches quote tokens (1 hour) and market data (5 minutes) by default. Avoid setting TTLs to 0 unless you need real-time data:

```typescript
const client = new Client({
  // ...
  quoteTokensCacheTtl: 3600,   // 1 hour (default)
  marketCacheTtl: 300,          // 5 minutes (default)
});
```

### Batch Operations

Use batch methods for multiple orders:

```typescript
// Instead of multiple placeOrder calls:
const results = await client.placeOrdersBatch(orders);

// Instead of multiple cancelOrder calls:
const results = await client.cancelOrdersBatch(orderIds);

// Cancel all at once:
const result = await client.cancelAllOrders({ marketId: 57 });
```

### Pre-enable Trading

Call `enableTrading()` once at startup rather than passing `checkApproval: true` on every order:

```typescript
// Do this once at startup
await client.enableTrading();

// Then place orders without approval check
await client.placeOrder(orderData, false);  // checkApproval = false
```

### Use Proxy for Network Restrictions

If you are behind a firewall or in a region with network restrictions, configure a proxy:

```typescript
const client = new Client({
  // ...
  proxyUrl: 'http://127.0.0.1:7890',
});
```

This applies to both HTTP API calls and WebSocket connections.

***

## Support

If you are unable to resolve your issue with the information above:

* Documentation: <https://docs.opinion.trade>
* Email: <support@opinion.trade>
* GitHub Issues: Report bugs on the SDK repository


# Builder Mode

> Build your own trading terminal on top of Opinion's prediction market with non-custodial user management and 2,000 free gasless transactions per day.
>
> To request Builder access, Please kindly fill out this [short application form ](https://forms.gle/tqL84A5mMXjZ1xT86).&#x20;

## Overview

Builder mode lets platforms manage user sub-accounts and place orders on behalf of users. Users retain control of their funds through Gnosis Safe wallets and sign all operations with their own keys.

## Architecture

```
┌──────────────┐     ┌──────────────────┐     ┌────────────┐
│  Your App    │     │  Opinion Backend  │     │  BNB Chain  │
│  (Builder)   │────>│  (API + Relayer)  │────>│  (On-chain) │
│              │     │                   │     │             │
│  BuilderClient     │  - Order matching │     │  - Safe     │
│  + UserClient│<────│  - TX relay       │<────│  - CTF      │
│              │     │  - Safe creation  │     │  - ERC20    │
└──────────────┘     └──────────────────┘     └────────────┘
```

**Three wallets involved:**

| Wallet                  | What                               | Who Controls             |
| ----------------------- | ---------------------------------- | ------------------------ |
| **EOA** (signer)        | Signs orders and Safe transactions | User (private key)       |
| **Safe** (asset wallet) | Holds funds, executes trades       | Multi-sig (EOA is owner) |
| **Builder API key**     | Authenticates builder API calls    | Builder (your server)    |

## Quick Start

```typescript
import {
  BuilderClient,
  UserClient,
  OrderSide,
  OrderType,
} from '@opinion-labs/opinion-clob-sdk';

// 1. Initialize builder client
const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
  rpcUrl: 'https://bsc-dataseed.binance.org',
});

// 2. Create a user and save the API key
const userInfo = await builder.createUser('0xUserEOA...');
const userApiKey = userInfo.apikey; // SAVE THIS - only returned once!

// 3. Build and sign an order
const orderData = await builder.buildOrderForSigning({
  marketId: 123,
  tokenId: 'token_id',
  userWalletAddress: userInfo.multiSigWallet,
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  amount: 10,
  price: '0.5',
  signerAddress: '0xUserEOA...',
});

// 4. User signs the order hash (in production, use MetaMask/WalletConnect)
const user = new UserClient('0xUserPrivateKey...');
const signature = await user.signHash(orderData.structHash);

// 5. Place order
const result = await builder.placeOrderForUserFromBuildResult(
  orderData, signature, userInfo.multiSigWallet
);
```

## Configuration

```typescript
const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
  rpcUrl: 'https://bsc-dataseed.binance.org',  // Required for Safe operations
  proxyUrl: 'http://127.0.0.1:7890',           // Optional HTTP proxy
  quoteTokensCacheTtl: 3600,                    // Cache TTL in seconds (default: 3600)
  marketCacheTtl: 300,                          // Cache TTL in seconds (default: 300)
});
```

| Parameter             | Type    | Required | Description                                           |
| --------------------- | ------- | -------- | ----------------------------------------------------- |
| `host`                | string  | Yes      | API host URL                                          |
| `builderApiKey`       | string  | Yes      | Builder API key (uses `builder-apikey` header)        |
| `chainId`             | number  | Yes      | Blockchain chain ID (56 for BNB Chain)                |
| `rpcUrl`              | string  | No       | RPC endpoint URL (required for Safe operations)       |
| `proxyUrl`            | string  | No       | HTTP proxy URL for API requests                       |
| `useBeta`             | boolean | No       | Use beta/testnet addresses (default: false)           |
| `contractAddresses`   | object  | No       | Override specific contract addresses                  |
| `quoteTokensCacheTtl` | number  | No       | Cache TTL for quote tokens in seconds (default: 3600) |
| `marketCacheTtl`      | number  | No       | Cache TTL for market data in seconds (default: 300)   |

## API Reference

### User Management

* [Create User](/developer-guide/opinion-clob-typescript-sdk/builder-mode/create-user) -- register a sub-account and get an API key
* [Get User / Regenerate API Key](/developer-guide/opinion-clob-typescript-sdk/builder-mode/get-user) -- query user info or regenerate credentials

### Market Data

* [Get Quote Tokens](/developer-guide/opinion-clob-typescript-sdk/builder-mode/get-quote-tokens) -- list supported currencies
* [Get Market / Orderbook](/developer-guide/opinion-clob-typescript-sdk/builder-mode/get-market) -- query market details and orderbook depth

### Trading

* [Build Order](/developer-guide/opinion-clob-typescript-sdk/builder-mode/build-order) -- build an EIP-712 order structure for signing
* [Place Order](/developer-guide/opinion-clob-typescript-sdk/builder-mode/place-order) -- submit a signed order on behalf of a user
* [Cancel / Manage Orders](/developer-guide/opinion-clob-typescript-sdk/builder-mode/cancel-order) -- cancel orders and query order history

### On-chain Operations

* [Enable Trading](/developer-guide/opinion-clob-typescript-sdk/builder-mode/enable-trading) -- one-time approval setup via Safe transaction
* [Split / Merge / Redeem / Withdraw](/developer-guide/opinion-clob-typescript-sdk/builder-mode/split-merge-redeem) -- token operations via Safe transactions

### Testing

* [UserClient](/developer-guide/opinion-clob-typescript-sdk/builder-mode/userclient) -- test wallet helper for signing

## Error Handling

```typescript
import {
  BuilderError,
  BuilderInvalidParamError,
  BuilderApiError,
} from '@opinion-labs/opinion-clob-sdk';

try {
  await builder.createUser(address);
} catch (e) {
  if (e instanceof BuilderInvalidParamError) {
    // Bad input: invalid address format, missing required field
    console.error('Invalid parameter:', e.message);
  } else if (e instanceof BuilderApiError) {
    // Backend error: user already exists, rate limit, server error
    console.error('API error:', e.message);
  } else if (e instanceof BuilderError) {
    // General builder error (base class)
    console.error('Builder error:', e.message);
  }
}
```

## Signature Types

| Type               | Value | Description                             |
| ------------------ | ----- | --------------------------------------- |
| EOA                | 0     | Direct EOA signature (maker == signer)  |
| POLY\_GNOSIS\_SAFE | 2     | Gnosis Safe signature (maker != signer) |

The signature type is automatically determined based on whether `signerAddress` differs from `userWalletAddress` in `buildOrderForSigning()`.


# Create User

> Register a new user sub-account under your builder and get their API key.

## Usage

```typescript
import { BuilderClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const result = await builder.createUser('0xUserWalletAddress...');

console.log(result.walletAddress);    // User's login wallet address
console.log(result.multiSigWallet);   // User's Gnosis Safe wallet address
console.log(result.apikey);           // User's API key - SAVE THIS!
console.log(result.builderName);      // Builder name
console.log(result.enableTrading);    // Whether trading is enabled
```

## Parameters

| Parameter       | Type   | Required | Description                             |
| --------------- | ------ | -------- | --------------------------------------- |
| `walletAddress` | string | Yes      | User's EOA wallet address (0x-prefixed) |

## Response

```typescript
interface UserInfo {
  apikey: string;              // API key (only returned during creation)
  walletAddress: string;       // User's login wallet address
  multiSigWallet: string;     // User's Gnosis Safe wallet address
  builderName: string;        // Builder name
  walletCreationTxHash?: string; // Safe creation transaction hash
  enableTrading: boolean;      // Whether trading approvals are set up
}
```

## Notes

* The API key is returned **only once** during creation. Store it securely in your database.
* The Gnosis Safe wallet is created asynchronously (1-10 minutes). Poll `getUser()` until `multiSigWallet` is non-empty.
* If the user already exists, the API will return an error. Use `getUser()` to retrieve existing user info.

### Polling for Safe Wallet

```typescript
// Poll until Safe wallet is ready
let safeAddress = result.multiSigWallet;
while (!safeAddress) {
  await new Promise((r) => setTimeout(r, 30_000)); // Wait 30s
  const user = await builder.getUser('0xUserWalletAddress...');
  safeAddress = user.multiSigWallet;
}
console.log('Safe wallet ready:', safeAddress);
```


# Get User

> Query user information or regenerate API credentials.

## Get User

```typescript
import { BuilderClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const user = await builder.getUser('0xUserWalletAddress...');

console.log(user.walletAddress);    // User's login wallet address
console.log(user.multiSigWallet);   // User's Gnosis Safe wallet address
console.log(user.builderName);      // Builder name
console.log(user.enableTrading);    // Whether trading is enabled
```

### Parameters

| Parameter       | Type   | Required | Description                             |
| --------------- | ------ | -------- | --------------------------------------- |
| `walletAddress` | string | Yes      | User's EOA wallet address (0x-prefixed) |

### Response

Returns `UserInfo` without `apikey` or `walletCreationTxHash` (omitted for security).

```typescript
{
  walletAddress: string;
  multiSigWallet: string;
  builderName: string;
  enableTrading: boolean;
}
```

## Regenerate API Key

Invalidate the old API key and generate a new one.

```typescript
const result = await builder.regenerateUserApiKey('0xUserWalletAddress...');

console.log(result.apikey);  // New API key - SAVE THIS!
```

### Parameters

| Parameter       | Type   | Required | Description                             |
| --------------- | ------ | -------- | --------------------------------------- |
| `walletAddress` | string | Yes      | User's EOA wallet address (0x-prefixed) |

### Response

Returns full `UserInfo` including the new `apikey`.

## Notes

* `getUser()` does **not** return the API key for security reasons.
* The new API key from `regenerateUserApiKey()` is returned only once. Store it securely.


# Get Quote Tokens

> List supported quote tokens (currencies) for trading.

## Usage

```typescript
import { BuilderClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const tokens = await builder.getQuoteTokens();

for (const token of tokens) {
  console.log(token.quoteTokenAddress);    // ERC20 token contract address
  console.log(token.ctfExchangeAddress);   // CTF Exchange contract address
  console.log(token.decimal);              // Token decimal precision
  console.log(token.chainId);             // Chain ID
}
```

## Parameters

| Parameter  | Type    | Required | Description                                  |
| ---------- | ------- | -------- | -------------------------------------------- |
| `useCache` | boolean | No       | Use cached data if available (default: true) |

## Response

Returns an array of `QuoteTokenData`:

```typescript
interface QuoteTokenData {
  quoteTokenAddress: string;   // ERC20 token contract address (e.g., USDC)
  ctfExchangeAddress: string;  // CTF Exchange contract for this token
  chainId: string;             // Blockchain chain ID
  decimal: number;             // Token decimal precision (e.g., 6 for USDC, 18 for mUSDT)
}
```

## Notes

* Results are cached for 1 hour by default (configurable via `quoteTokensCacheTtl`).
* Pass `false` to bypass the cache: `await builder.getQuoteTokens(false)`.
* The `decimal` value is important for amount conversions (e.g., 1 USDC = `1000000` in wei with 6 decimals).


# Enable Trading

> Build, sign, and submit a one-time approval transaction for a user's Safe wallet.

## Usage

```typescript
import { BuilderClient, UserClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
  rpcUrl: 'https://bsc-dataseed.binance.org',
});

// Step 1: Build the Enable Trading transaction
const txResult = await builder.buildEnableTradingTx(safeAddress);

console.log(txResult.safeTx);       // Safe transaction parameters
console.log(txResult.eip712Data);   // EIP-712 typed data for signing
console.log(txResult.safeTxHash);   // Safe transaction hash

// Step 2: User signs the EIP-712 typed data
const user = new UserClient('0xUserPrivateKey...');
const signature = await user.signTypedData(txResult.eip712Data);

// Step 3: Submit to backend for relay
const result = await builder.submitSafeTx(userAddress, txResult, signature);
```

## Parameters

### buildEnableTradingTx

| Parameter     | Type                    | Required | Description                                                        |
| ------------- | ----------------------- | -------- | ------------------------------------------------------------------ |
| `safeAddress` | string                  | Yes      | User's Gnosis Safe wallet address                                  |
| `quoteTokens` | Record\<string, string> | No       | Map of token address to exchange address. Auto-fetched if omitted. |

### submitSafeTx

| Parameter       | Type         | Required | Description                            |
| --------------- | ------------ | -------- | -------------------------------------- |
| `walletAddress` | string       | Yes      | User's wallet address (Safe owner EOA) |
| `safeTxResult`  | SafeTxResult | Yes      | Result from `buildEnableTradingTx()`   |
| `signature`     | string       | Yes      | User's signature on the EIP-712 data   |

## Response

`buildEnableTradingTx()` returns a `SafeTxResult`:

```typescript
interface SafeTxResult {
  safeTx: SafeTxParams;          // Raw Safe transaction parameters
  eip712Data: {                  // EIP-712 data for wallet signing
    types: Record<string, Array<{ name: string; type: string }>>;
    primaryType: string;         // 'SafeTx'
    domain: Record<string, unknown>;
    message: Record<string, unknown>;
  };
  safeTxHash: string;            // Hash of the Safe transaction
  submissionDataTemplate: Record<string, unknown>;
}
```

## Notes

* Enable Trading only needs to be called **once** per user. Check `userInfo.enableTrading` before calling.
* This operation requires `rpcUrl` in the BuilderClient config (used to read the Safe nonce).
* The transaction is relayed by the backend -- no gas is required from the user.
* Safe TX signing uses `user.signTypedData(eip712Data)`. This is different from order signing which uses `user.signHash(structHash)`.
* On-chain confirmation typically takes 30-60 seconds after submission.


# Get Market

> Query market details and orderbook depth.

## Get Market

```typescript
import { BuilderClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const market = await builder.getMarket(123);

console.log(market.marketId);      // Market ID
console.log(market.conditionId);   // Condition ID (used for split/merge/redeem)
console.log(market.quoteToken);    // Quote token address
console.log(market.chainId);       // Chain ID
console.log(market.status);        // Market status
```

### Parameters

| Parameter  | Type    | Required | Description                                  |
| ---------- | ------- | -------- | -------------------------------------------- |
| `marketId` | number  | Yes      | Market ID (positive integer)                 |
| `useCache` | boolean | No       | Use cached data if available (default: true) |

### Response

Returns a `MarketData` object:

```typescript
interface MarketData {
  marketId: number;
  chainId: string;
  quoteToken: string;
  conditionId: string;
  status: number;
  [key: string]: unknown;  // Additional fields from API
}
```

## Get Orderbook

```typescript
const orderbook = await builder.getOrderbook('token_id_here');
```

### Parameters

| Parameter | Type   | Required | Description      |
| --------- | ------ | -------- | ---------------- |
| `tokenId` | string | Yes      | Outcome token ID |

### Response

Returns the raw API response containing bid/ask depth.

## Notes

* Market data is cached for 5 minutes by default (configurable via `marketCacheTtl`).
* Pass `false` to bypass the cache: `await builder.getMarket(123, false)`.
* The `conditionId` from `getMarket()` is required for split, merge, and redeem operations.
* The `quoteToken` address identifies which currency this market uses.


# Build Order

> Build an EIP-712 order structure for a user to sign.

## Usage

```typescript
import { BuilderClient, OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const orderData = await builder.buildOrderForSigning({
  marketId: 123,
  tokenId: 'outcome_token_id',
  userWalletAddress: '0xSafeAddress...',   // User's Safe wallet (maker)
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  amount: 10,
  price: '0.5',
  signerAddress: '0xEOAAddress...',        // User's EOA (signer)
});

console.log(orderData.order);            // Order struct (Record<string, string>)
console.log(orderData.structHash);       // Hash for signing
console.log(orderData.typedData);        // Full EIP-712 typed data
console.log(orderData.exchangeAddress);  // CTF Exchange address
console.log(orderData.currencyAddress);  // Quote token address
console.log(orderData.currencyDecimal);  // Token decimals
```

## Parameters

| Parameter           | Type      | Required  | Description                                                                                       |
| ------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------- |
| `marketId`          | number    | Yes       | Market ID (positive integer)                                                                      |
| `tokenId`           | string    | Yes       | Outcome token ID                                                                                  |
| `userWalletAddress` | string    | Yes       | User's Safe wallet address (maker)                                                                |
| `side`              | OrderSide | Yes       | `OrderSide.BUY` (0) or `OrderSide.SELL` (1)                                                       |
| `orderType`         | OrderType | Yes       | `OrderType.LIMIT_ORDER` (2) or `OrderType.MARKET_ORDER` (1)                                       |
| `amount`            | number    | Yes       | Amount in human-readable units (e.g., 10 for 10 USDC)                                             |
| `price`             | string    | For limit | Price per outcome token (e.g., `'0.5'`). Required for limit orders.                               |
| `amountType`        | string    | No        | `'quote'` (default) or `'base'`. Quote = ERC20 amount, Base = token amount.                       |
| `signerAddress`     | string    | No        | Signer's EOA address. If different from `userWalletAddress`, Safe mode (signatureType=2) is used. |

## Response

```typescript
interface BuildOrderResult {
  order: Record<string, string>;    // Order struct fields
  structHash: string;               // EIP-712 struct hash for signing
  typedData: {                      // Full EIP-712 typed data
    types: Record<string, Array<{ name: string; type: string }>>;
    primaryType: string;
    domain: Record<string, unknown>;
    message: Record<string, unknown>;
  };
  domain: {
    name: string;                   // 'OPINION CTF Exchange'
    version: string;                // '1'
    chainId: number;
    verifyingContract: string;
  };
  exchangeAddress: string;
  currencyAddress: string;
  currencyDecimal: number;
  marketId: number;
  orderType: number;
  price: string;
}
```

## Signing the Order

**IMPORTANT**: Order signing uses `signHash(structHash)`, **not** `signTypedData`. This is different from Safe TX signing.

```typescript
// User signs the struct hash directly
const signature = await user.signHash(orderData.structHash);
```

### Test Helper

For testing, you can sign with a private key using the static helper:

```typescript
const signature = await BuilderClient.signOrderWithPrivateKey(
  orderData.typedData,
  '0xPrivateKey...'
);
```

> **Production**: Users sign with their own wallet (MetaMask, WalletConnect). The wallet should sign the `structHash` using `personal_sign` or equivalent.

## Notes

* For **limit orders**, `price` is required. For **market orders**, `price` is ignored.
* Market BUY orders only accept `amountType: 'quote'` (how much to spend).
* Market SELL orders only accept `amountType: 'base'` (how many tokens to sell).
* When `signerAddress` differs from `userWalletAddress`, the SDK automatically sets `signatureType` to 2 (POLY\_GNOSIS\_SAFE).
* Orders expire 30 days from creation by default.


# Place Order

> Submit a signed order on behalf of a user.

## Usage (Recommended)

Use `placeOrderForUserFromBuildResult()` with the output from `buildOrderForSigning()`:

```typescript
import { BuilderClient, OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

// Step 1: Build the order
const orderData = await builder.buildOrderForSigning({
  marketId: 123,
  tokenId: 'token_id',
  userWalletAddress: safeAddress,
  side: OrderSide.BUY,
  orderType: OrderType.LIMIT_ORDER,
  amount: 10,
  price: '0.5',
  signerAddress: userEOA,
});

// Step 2: User signs the struct hash
const signature = await user.signHash(orderData.structHash);

// Step 3: Place the order (convenience method)
const result = await builder.placeOrderForUserFromBuildResult(
  orderData,
  signature,
  safeAddress,
);
```

## Convenience Method

```typescript
async placeOrderForUserFromBuildResult(
  buildResult: BuildOrderResult,
  signature: string,
  userWalletAddress: string,
  postOnly?: boolean,
): Promise<Record<string, unknown>>
```

| Parameter           | Type             | Required | Description                                                                                 |
| ------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------- |
| `buildResult`       | BuildOrderResult | Yes      | Result from `buildOrderForSigning()`                                                        |
| `signature`         | string           | Yes      | User's signature (0x-prefixed hex string)                                                   |
| `userWalletAddress` | string           | Yes      | User's Safe wallet address (must match `order.maker`)                                       |
| postOnly            | boolean          | No       | Post-only mode for limit orders; crossing orders are cancelled instead of taking liquidity. |

## Full Method

For advanced use cases, you can call `placeOrderForUser()` directly:

```typescript
const result = await builder.placeOrderForUser({
  order: orderData.order,
  signature: signature,
  userWalletAddress: safeAddress,
  marketId: orderData.marketId,
  orderType: orderData.orderType,
  price: orderData.price,
  currencyAddress: orderData.currencyAddress,
  postOnly: true,
});
```

| Parameter           | Type                    | Required | Description                                                                                 |
| ------------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `order`             | Record\<string, string> | Yes      | Order struct from `buildOrderForSigning()`                                                  |
| `signature`         | string                  | Yes      | User's signature (0x-prefixed hex string)                                                   |
| `userWalletAddress` | string                  | Yes      | User's Safe wallet address (must match `order.maker`)                                       |
| `marketId`          | number                  | No       | Market ID                                                                                   |
| `orderType`         | number                  | No       | Order type (1=market, 2=limit)                                                              |
| `price`             | string                  | No       | Order price                                                                                 |
| `currencyAddress`   | string                  | No       | Quote token address                                                                         |
| postOnly            | boolean                 | No       | Post-only mode for limit orders; crossing orders are cancelled instead of taking liquidity. |

## Notes

* The convenience method `placeOrderForUserFromBuildResult()` is recommended. It automatically extracts `marketId`, `orderType`, `price`, and `currencyAddress` from the build result.
* The `order.maker` must match `userWalletAddress` or the request will be rejected.
* The signature must be a hex string starting with `0x`.
* Order placement is gasless (counts toward the 2,000/day limit).
* Post-only is supported only for limit orders; orders that would cross the spread are cancelled instead of taking liquidity.


# Cancel Order

> Cancel orders and query order history for a user.

## Cancel Single Order

```typescript
import { BuilderClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
});

const result = await builder.cancelOrderForUser(userApiKey, 'order_id_here');
```

| Parameter    | Type   | Required | Description        |
| ------------ | ------ | -------- | ------------------ |
| `userApiKey` | string | Yes      | User's API key     |
| `orderId`    | string | Yes      | Order ID to cancel |

## Cancel Batch

Cancel multiple orders at once:

```typescript
const results = await builder.cancelOrdersBatchForUser(userApiKey, [
  'order_id_1',
  'order_id_2',
  'order_id_3',
]);

for (const r of results) {
  if (r.success) {
    console.log(`Order ${r.orderId} cancelled`);
  } else {
    console.log(`Order ${r.orderId} failed: ${r.error}`);
  }
}
```

| Parameter    | Type      | Required | Description                  |
| ------------ | --------- | -------- | ---------------------------- |
| `userApiKey` | string    | Yes      | User's API key               |
| `orderIds`   | string\[] | Yes      | Array of order IDs to cancel |

### Batch Response

```typescript
Array<{
  index: number;      // Position in the input array
  success: boolean;   // Whether cancellation succeeded
  result?: unknown;   // Result on success
  error?: string;     // Error message on failure
  orderId: string;    // The order ID
}>
```

## Cancel All Orders

Cancel all open orders with optional filters:

```typescript
import { OrderSide } from '@opinion-labs/opinion-clob-sdk';

// Cancel all open orders
const result = await builder.cancelAllOrdersForUser(userApiKey);

// Cancel all open orders for a specific market
const result = await builder.cancelAllOrdersForUser(userApiKey, { marketId: 123 });

// Cancel all open BUY orders
const result = await builder.cancelAllOrdersForUser(userApiKey, { side: OrderSide.BUY });
```

| Parameter          | Type      | Required | Description                  |
| ------------------ | --------- | -------- | ---------------------------- |
| `userApiKey`       | string    | Yes      | User's API key               |
| `options.marketId` | number    | No       | Filter by market ID          |
| `options.side`     | OrderSide | No       | Filter by side (BUY or SELL) |

### Cancel All Response

```typescript
{
  totalOrders: number;   // Total orders found matching filters
  cancelled: number;     // Successfully cancelled
  failed: number;        // Failed to cancel
  results: Array<{ index: number; success: boolean; result?: unknown; error?: string; orderId: string }>
}
```

## Get User Orders

Query a user's orders:

```typescript
const orders = await builder.getUserOrders(userApiKey, {
  marketId: 123,
  status: '1',    // 1 = open/pending
  limit: 20,      // Max 20 per page
  page: 1,
});
```

| Parameter          | Type   | Required | Description                             |
| ------------------ | ------ | -------- | --------------------------------------- |
| `userApiKey`       | string | Yes      | User's API key                          |
| `options.marketId` | number | No       | Filter by market ID                     |
| `options.status`   | string | No       | Filter by status (e.g., `'1'` for open) |
| `options.limit`    | number | No       | Results per page (default: 10, max: 20) |
| `options.page`     | number | No       | Page number (default: 1)                |

## Notes

* Order cancellation uses the **user's API key**, not the builder API key.
* Cancellation is gasless (counts toward the 2,000/day limit).
* `cancelAllOrdersForUser()` automatically paginates through all open orders before cancelling.


# Split / Merge / Redeem

> Build, sign, and submit Safe transactions for on-chain token operations.

All four operations follow the same pattern: build the transaction, have the user sign it, then submit.

## Common Signing Pattern

```typescript
import { BuilderClient, UserClient } from '@opinion-labs/opinion-clob-sdk';

const builder = new BuilderClient({
  host: 'https://openapi.opinion.trade/openapi',
  builderApiKey: 'YOUR_BUILDER_KEY',
  chainId: 56,
  rpcUrl: 'https://bsc-dataseed.binance.org',
});

// Build any Safe TX (split, merge, redeem, or withdraw)
const txResult = await builder.buildSplitTx(safeAddress, collateral, conditionId, amount);

// User signs the EIP-712 typed data
const user = new UserClient('0xUserPrivateKey...');
const signature = await user.signTypedData(txResult.eip712Data);

// Submit to backend for relay
const result = await builder.submitSafeTx(userAddress, txResult, signature);
```

## Split Position

Convert quote tokens (e.g., USDC) into Yes and No outcome tokens.

```typescript
const txResult = await builder.buildSplitTx(
  safeAddress,          // User's Safe wallet address
  collateralToken,      // Quote token address (e.g., USDC)
  conditionId,          // Market condition ID (from getMarket())
  1000000n,             // Amount in wei (e.g., 1 USDC = 1000000 with 6 decimals)
);
```

| Parameter         | Type      | Required | Description                                              |
| ----------------- | --------- | -------- | -------------------------------------------------------- |
| `safeAddress`     | string    | Yes      | User's Safe wallet address                               |
| `collateralToken` | string    | Yes      | Quote token contract address                             |
| `conditionId`     | string    | Yes      | Market condition ID                                      |
| `amount`          | bigint    | Yes      | Amount in wei                                            |
| `partition`       | number\[] | No       | Outcome partition (default: `[1, 2]` for binary markets) |

## Merge Position

Convert equal amounts of Yes and No tokens back into quote tokens.

```typescript
const txResult = await builder.buildMergeTx(
  safeAddress,
  collateralToken,
  conditionId,
  1000000n,
);
```

| Parameter         | Type      | Required | Description                           |
| ----------------- | --------- | -------- | ------------------------------------- |
| `safeAddress`     | string    | Yes      | User's Safe wallet address            |
| `collateralToken` | string    | Yes      | Quote token contract address          |
| `conditionId`     | string    | Yes      | Market condition ID                   |
| `amount`          | bigint    | Yes      | Amount in wei                         |
| `partition`       | number\[] | No       | Outcome partition (default: `[1, 2]`) |

## Redeem Position

Claim winnings from a resolved market. Converts winning outcome tokens back to quote tokens.

```typescript
const txResult = await builder.buildRedeemTx(
  safeAddress,
  collateralToken,
  conditionId,
);
```

| Parameter         | Type      | Required | Description                           |
| ----------------- | --------- | -------- | ------------------------------------- |
| `safeAddress`     | string    | Yes      | User's Safe wallet address            |
| `collateralToken` | string    | Yes      | Quote token contract address          |
| `conditionId`     | string    | Yes      | Market condition ID                   |
| `partition`       | number\[] | No       | Outcome partition (default: `[1, 2]`) |

## Withdraw Tokens

Transfer ERC20 tokens from the Safe wallet to any address.

```typescript
const txResult = await builder.buildWithdrawTx(
  safeAddress,          // From: user's Safe wallet
  tokenAddress,         // ERC20 token to withdraw
  1000000n,             // Amount in wei
  recipientAddress,     // To: destination address
);
```

| Parameter      | Type   | Required | Description                  |
| -------------- | ------ | -------- | ---------------------------- |
| `safeAddress`  | string | Yes      | User's Safe wallet address   |
| `tokenAddress` | string | Yes      | ERC20 token contract address |
| `amount`       | bigint | Yes      | Amount in wei                |
| `toAddress`    | string | Yes      | Recipient address            |

## Submit Safe Transaction

All build methods return a `SafeTxResult`. Submit it with a user signature:

```typescript
const result = await builder.submitSafeTx(userAddress, txResult, signature);
```

| Parameter       | Type         | Required | Description                            |
| --------------- | ------------ | -------- | -------------------------------------- |
| `walletAddress` | string       | Yes      | User's wallet address (Safe owner EOA) |
| `safeTxResult`  | SafeTxResult | Yes      | Result from any `build*Tx()` method    |
| `signature`     | string       | Yes      | User's signature on the EIP-712 data   |

## Test Helper

For testing, sign Safe transactions with a private key:

```typescript
const signature = await BuilderClient.signSafeTxWithPrivateKey(
  txResult.eip712Data,
  '0xPrivateKey...',
);
```

## Notes

* All Safe TX operations require `rpcUrl` in the BuilderClient config.
* Transactions are relayed by the backend -- no gas required from the user.
* Safe TX signing uses `user.signTypedData(eip712Data)`, not `signHash()`.
* For amount conversions, use `safeAmountToWei()` from the SDK:

  ```typescript
  import { safeAmountToWei } from '@opinion-labs/opinion-clob-sdk';
  const weiAmount = safeAmountToWei(5.0, 6); // 5 USDC -> 5000000n
  ```
* The `conditionId` for split, merge, and redeem is available from `getMarket()`.
* Redeem only works on resolved markets with winning outcome tokens.


# UserClient

> Test wallet helper for simulating user signing in Builder mode.

`UserClient` holds a private key and can sign EIP-712 typed data and order hashes. It is a **testing utility** -- in production, users sign with their own wallets (MetaMask, WalletConnect, etc.).

## Create a Random Test Wallet

```typescript
import { UserClient } from '@opinion-labs/opinion-clob-sdk';

const user = UserClient.createRandom();
console.log(user.address);  // Random EOA address
```

## Create from Private Key

```typescript
const user = new UserClient('0xYourPrivateKey...');
console.log(user.address);  // Derived EOA address
```

## Sign Safe Transactions

Use `signTypedData()` for Safe transaction signing (enable trading, split, merge, redeem, withdraw):

```typescript
const txResult = await builder.buildEnableTradingTx(safeAddress);
const signature = await user.signTypedData(txResult.eip712Data);
await builder.submitSafeTx(userAddress, txResult, signature);
```

### Parameters

| Parameter   | Type      | Description                                                             |
| ----------- | --------- | ----------------------------------------------------------------------- |
| `typedData` | TypedData | EIP-712 typed data with `domain`, `types`, `primaryType`, and `message` |

Returns a hex signature string (`0x`-prefixed).

## Sign Order Hashes

Use `signHash()` for order signing (place order):

```typescript
const orderData = await builder.buildOrderForSigning({ ... });
const signature = await user.signHash(orderData.structHash);
await builder.placeOrderForUserFromBuildResult(orderData, signature, safeAddress);
```

### Parameters

| Parameter    | Type   | Description                                            |
| ------------ | ------ | ------------------------------------------------------ |
| `hashToSign` | string | Hash to sign (hex string, with or without `0x` prefix) |

Returns a hex signature string (`0x`-prefixed).

## File Persistence

For demo scripts, `UserClient` supports saving and loading from JSON files:

```typescript
// Save to file
user.saveToFile('.test_user.json');

// Load from file
const user = UserClient.loadFromFile('.test_user.json');

// Load or create (creates new if file doesn't exist)
const user = UserClient.loadOrCreate('.test_user.json');
```

## Notes

* **Order signing** uses `signHash(structHash)` -- signs the hash directly.
* **Safe TX signing** uses `signTypedData(eip712Data)` -- signs EIP-712 typed data.
* These are two distinct signing patterns. Do not mix them up.
* In production, replace `UserClient` with wallet integration (MetaMask `eth_signTypedData_v4` for Safe TXs, `personal_sign` for order hashes).


# Audit Report

Conditional Tokens

* ScaleBit: <https://static.opinion.trade/opinion-v1-scalebit.pdf>
* Zellic: <https://static.opinion.trade/opinion-v1-zellic.pdf>

Exchange Engine & Fee Manager

* ScaleBit: <https://static.opinion.trade/opinion-v2-scalebit.pdf>

OPN Token&#x20;

* ScaleBit: <https://static.opinion.trade/opn-token-scalebit.pdf>
* Pashov: <https://static.opinion.trade/opn-token-pashov.pdf>


# Privacy Policy

*Last Updated: 19 Oct 2025*

### **INTRODUCTION.**

Superchillclub Limited (the “**Company**,” “**we**,” “**us**” or “**our**”) respect your privacy and are committed to protecting it through our compliance with this Policy. This Privacy Policy (the “**Policy**”) describes how we processes personal information that we collect through our digital or online properties or services that link to this Policy (including, as applicable, the Opinion platform, our website opinion.trade , social media pages, marketing activities, live events, etc.) and other activities described in this Policy (collectively, the “**Services**”). Capitalized terms not defined in this Policy are defined in our Terms of Use.

This Privacy Policy does not apply to personal data about the Company’s employees and candidates, and certain contractors and agents acting in similar roles.

Please read carefully and fully this Policy before using or continuing to use our Services and, if necessary, make appropriate choices in accordance with the guidelines in this Policy. If you do not provide certain personal data to us, we may not be able to provide the Services to you, or the use of the Services may be restricted, or the Services may not function to achieve the results we intend to provide.

#### **Changes.**&#x20;

We may update this Policy from time-to-time to reflect changes in legal, regulatory, operational requirements, our practices, and other factors. Please check this Policy periodically for updates. **If any of the changes are unacceptable to you, you should cease interacting with us.** When required under applicable law, we will notify you of any changes to this Policy. Any modifications to this Policy will be effective upon our posting the modified version (or as otherwise indicated at the time of posting). **In all cases, your use of the Services after the effective date of any modified Privacy Policy indicates you’re acknowledging that the modified Privacy Policy applies to your interactions with the Services and our business.**

### INFORMATION WE COLLECT.

#### **Information you provide to us.**&#x20;

Certain features of our Services may necessitate the provision of specific information. While you have the option not to provide this information, doing so might restrict your ability to use or access these features. The types of information you may directly provide to us through the Services or other means include:

* **Contact information**: such as your first and last name, email address, billing and mailing addresses, and phone number.
* **Demographic information**: such as your city, state, country of residence, postal code, and age.
* **Profile information**: such as the username and password you create to register an Account on our Services, the date of Account registration, redemption codes, biographical details, photographs or pictures, links to your social media profiles, interests, preferences, information about your participation in our contests, promotions, or surveys, and any other details you add to your Account profile.
* **Communication information**: based on our exchanges with you, including when you contact us through the Services or communicate with us via social media or otherwise.
* **Marketing data**: such as your preferences for receiving our marketing communications and details about your engagement with them.
* **Networks and connections**: such as information about the people and accounts you are connected to and how you interact with them on the Services, such as the accounts of other users that you follow.
* **Payment and subscription information**: necessary to complete transactions and process your subscription, such as your payment card or bank account information (collected by our payment processors on our behalf) and transaction history.
* **Financial Information**. If applicable to your use of the Services, in order for us to process payments of any fees owed to us in connection with your use of the Services, you will be required to provide certain bank account online login information, bank account and routing numbers, credit card information (e.g., card brand, expiration date, and credit-card numbers) and other data related to your payment method. You authorize our third-party payment vendors and wallet providers to collect, process, and store your financial information in accordance with their respective privacy policies. For the purposes of this Policy, an “account connection” with such a third party is a connection you authorize or enable between your account and a payment instrument, or platform that you lawfully control or own. When you authorize such a connection, we may exchange your personal information and other information directly with such third-party. Examples of account connections include, without limitation: linking your account to a social media account or social messaging Services; connecting your account to a third-party data aggregation or financial Services company, if you provide such company with your account log-in credentials; or using your account to make payments to a merchant or allowing a merchant to charge your account. If you connect your account to other financial accounts, directly or through a third-party Services provider, we may have access to your account balance and account and transactional information, such as purchases and funds transfers. If you choose to create an account connection, we may receive information from the third party about you and your use of the third-party’s Services. For example, if you connect your account to a social media account, we will receive personal information from the social media provider via the account connection. We will use all such information that we receive from a third-party via an account connection in a manner consistent with this Policy.
* **Other information**: any additional data you provide to us, which we will use as described in this Policy or as otherwise disclosed at the time of collection.

#### **Third-party sources.**&#x20;

We may collect or receive personal information from sources other than you directly, including:

* **Marketing and event partners**: such as joint marketing partners and event co-sponsors.
* **Login integration partners**: when you choose to access our Services through third-party platforms, these partners may provide us with data like your name, username, email address, profile picture, and other publicly accessible account information.
* **Other sources**: such as social media services, public records, service providers, and other publicly available sources.

#### **Automatic data collection**.&#x20;

We, along with our service providers and business partners, may automatically gather information about you, your computer or mobile device, and your interactions over time with our Services, communications, and other online services. This includes:

* **Device information**: This includes details about your computer or mobile device, such as the operating system type and version, manufacturer and model, browser type, screen resolution, RAM and disk size, CPU usage, device type (e.g., phone, tablet), IP address, unique identifiers, language settings, mobile device carrier, radio/network information (e.g., Wi-Fi, LTE, 3G/4G/5G), and approximate location information like city, state, or geographic area.
* **Online activity information**: This includes information about your interactions with our Services, such as the pages or screens you viewed, the duration of your visits to each page or screen, the website you visited before coming to our Services, navigation paths between pages or screens, details about your activities on a page or screen, access times and duration, and whether you have opened our emails or clicked on links within them.
* **Communication interaction data**: This includes information about your interactions with our emails, texts, or other communications, such as whether you open and/or forward emails.

#### **Cookies and similar technologies.**&#x20;

Some of the automatic collections described above are facilitated by the following technologies:

* **Cookies**: These are small text files that websites store on user devices. They allow web servers to record users’ web browsing activities and remember their submissions, preferences, and login status as they navigate a site. The cookies used on our sites include: “session cookies” that are deleted when a session ends, “persistent cookies” that remain longer, “first party” cookies that we place and “third party” cookies that our third-party business partners and service providers place.
* **Local storage technologies**: Technologies like HTML5 provide functionality similar to cookies but can store larger amounts of data on your device outside of your browser, in connection with specific applications.
* **Web beacons**: Also known as pixel tags or clear GIFs, these are used to demonstrate that a webpage or email was accessed or opened, or that certain content was viewed or clicked.

#### **Data about others.**&#x20;

We may provide features that allow users to invite others to use the Services. In such cases, we may collect contact information about the invitees in order to send them the invitations. Please ensure that you have obtained the invitee’s permission before referring them to us or sharing their contact details with us.

### HOW WE USE YOUR PERSONAL INFORMATION.

We may use your personal information for the following purposes or as otherwise described at the time of collection:

#### Services delivery and operations.&#x20;

We may use your personal information to:

* Compare information for accuracy and verify it with third parties.
* Compile anonymous statistical data and analysis for our use internally or with third parties.
* Provide, operate, and improve the Services (including training our artificial intelligence models) and our business.
* Personalize our products and services, such as remembering the devices you have previously logged in from and retaining your selections and preferences as you navigate the Services.
* Establish and maintain your user profile on the Services.
* Enable user-to-user communications.
* Facilitate your invitations to contacts you wish to invite to join the Services and enable you to connect with and interact with content posted by other users.
* Communicate with you about the Services, including sending service-related announcements, updates, security alerts, and support and administrative messages.
* Enable security features, for example, by sending you security codes via email or SMS and remembering devices from which you have previously logged in.
* Inform you about events or contests in which you participate.
* Provide support for the Services and respond to your requests, questions, and feedback.

#### Research and development.&#x20;

We may use your personal information for research and development purposes, including to analyze and improve the Services and our business and to develop new products and services.

#### Marketing and advertising.&#x20;

We, our service providers, and our third-party advertising partners may collect, receive and use your personal information for marketing and advertising purposes.

* **Direct marketing**. We may send you direct marketing communications, which may be personalized based on your needs and interests. You have the option to opt-out of our marketing communications, as described in the Opt-out of communications section below.
* **Interest-based advertising**. Our third-party advertising partners may use cookies and similar technologies to collect information about your interactions with the Services, our communications, and other online services over time (including the data described in the Automatic data collection section). They use this information to serve online ads that they believe will interest you. This practice is known as interest-based or targeted advertising. Additionally, we may share information about our users with these companies to facilitate interest-based advertising to those or similar users on other online platforms.

#### Improvement and analytics.&#x20;

We may utilize your personal information to conduct analyses on your usage of the Services. This helps us enhance the Services and other aspects of our business. Specifically, it enables us to gain insights into user activity, such as identifying which pages are most and least visited, understanding how visitors navigate through the Services, and examining user interactions with our emails. Additionally, this information is instrumental in aiding us to develop new products and services.

#### Compliance and protection.&#x20;

We may use your personal information to:

* **Comply with applicable laws, lawful requests, and legal processes**: This includes responding to subpoenas, investigations, or requests from government authorities.
* **Protect rights, privacy, safety, or property**: This extends to safeguarding the rights, privacy, safety, or property of ourselves, you, or others (including by making and defending legal claims).
* **Audit internal processes**: To ensure compliance with legal and contractual requirements or our internal policies.
* **Enforce terms and conditions**: To enforce the terms and conditions that govern the Services.
* **Prevent, identify, investigate, and deter fraudulent or harmful activities**: This includes addressing unauthorized, unethical, or illegal activities, such as cyberattacks and identity theft.

#### To create aggregated, de-identified and/or anonymized data.&#x20;

We may also aggregate, de-identify and/or anonymize your personal information so that it no longer identifies you and use this information for the purposes described above, such as to analyze the way our Services are being used, to improve and add features to them, to train our proprietary algorithms and other machine learning purposes, to promote our business, and to conduct research. We will maintain and use de-identified information in de-identified form and not attempt to reidentify the information, unless required by law.

#### Cookies and similar technologies.&#x20;

In addition to the other uses included in this section, we may use the Cookies and similar technologies described above for the following purposes:

* **Technical operation**: To facilitate the technical operation of the Services, such as remembering your selections and preferences as you navigate the site, and to determine whether you are logged in when you visit password-protected areas of the Services.
* **Functionality**: To enhance the performance and functionality of our Services.
* **Advertising**: To assist our third-party advertising partners in showing you ads on other online services that they believe will interest you, and to measure and analyze the performance of these ads.
* **Analytics**: To help us understand user activity, including which pages are most and least visited, how visitors move around the Services, and user interactions with our emails.

#### Retention.&#x20;

We generally retain personal information for as long as necessary to fulfill the purposes for which it was collected. This includes meeting any legal, accounting, or reporting requirements, establishing or defending legal claims, and preventing fraud. When determining the appropriate retention period for personal information, we consider several factors, such as: (i) the amount, nature, and sensitivity of the personal information; (ii) the potential risk of harm from unauthorized use or disclosure of your personal information; (iii) the purposes for which we process your personal information and whether we can achieve those purposes through other means; (iv) the applicable legal requirements. We may store aggregated, de-identified, and/or anonymized data for as long as needed to support our lawful business purposes. Regarding biometric data, we will retain it only for as long as necessary to fulfill the initial purpose for which it was collected or until three years after the deletion of your account, whichever comes first.

### HOW WE DISCLOSE YOUR PERSONAL INFORMATION.

We may disclose your personal information with the following parties and as otherwise described in this Policy, in other applicable notices, or at the time of collection.

#### Affiliates.&#x20;

Our corporate parent, subsidiaries, and affiliates.

#### Services providers.&#x20;

Third parties that provide services on our behalf or help us operate the Services or our business (such as artificial intelligence providers, hosting, information technology, customer support, email delivery, marketing, consumer research, cookie providers and website analytics).

#### Payment processors.&#x20;

Any payment card information you use to make a purchase on the Services is collected and processed directly by our payment processors.

#### Advertising partners.&#x20;

Third-party advertising companies for the interest-based advertising purposes described above.

#### Third parties designated by you.&#x20;

Where you have instructed us or provided your consent or authorization to do so, or as otherwise necessary to provide the Services. We will disclose personal information that is needed for these third parties to provide the services that you have requested.

#### Business and marketing partners.&#x20;

Third parties with whom we co-sponsor events or promotions, with whom we jointly offer products or services, or whose products or services may be of interest to you.

#### Professional advisors.&#x20;

Professional advisors, such as lawyers, auditors, bankers and insurers, where necessary in the course of the professional services that they render to us.

#### Authorities and others for legal, compliance and security purposes.&#x20;

Law enforcement, government authorities, and private parties, as we believe in good faith to be necessary or appropriate for the compliance and protection purposes described above. We may also disclose information for fraud detection or prevention.

#### Business transferees.&#x20;

In the context of actual or prospective business transactions (e.g., investments in or financings of the Company, public stock offerings, the sale, acquisition, transfer or merger of all or part of our business, assets or shares, or similar transactions) and/or in the event of an insolvency, bankruptcy, or receivership in which personal information is transferred to one or more third parties as one of our business assets). For example, we may need to share certain personal information with prospective counterparties and their advisers, an acquirer, successor, or assignee of the Company.

### OTHER SITES AND SERVICES.

Our Services may include links to websites, mobile applications, and other online services operated by third parties. Additionally, our content may be integrated into web pages or other online services that are not associated with us. These links and integrations do not imply endorsement or affiliation with any third party.

We do not control third-party websites, mobile applications, or online services, and we are not responsible for their actions or content. We encourage you to review the privacy policies of any third-party websites, mobile applications, or online services you use to understand their data practices and how they protect your information.

### SECURITY.

We implement a variety of commercially reasonable technical, administrative, and organizational measures designed to protect personal information we collect from loss, misuse, and unauthorized access, disclosure, alteration, or destruction. However, no Internet or electronic storage is ever fully secure or error free. Therefore, you should take special care in deciding what information you provide to the Services. While we strive to use commercially acceptable means to protect your personal information, we cannot guarantee its absolute security. In addition, we are not responsible for circumvention of any privacy settings or security measures contained on the Services, or third-party websites.

### INTERNATIONAL DATA TRANSFER.

Your personal information may be transferred to locations where privacy laws may not be as protective as those in your state, province, or country.

### CHILDREN.

Our Services does not address anyone under the age of 18. If you are under the age of 18, please do not use any of the Services and do not provide any information, including without limitation, your name, address, contact information, email address, user name, etc., to us.

We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your child has provided us with personal data in a manner prohibited by law, please contact us. If we learn that we have unknowingly collected personal information through the Services from a minor under the age of 18, we will comply with applicable legal requirements and make commercially reasonable efforts to delete their personal information.

If we need to rely on consent as a legal basis for processing your information and your country requires consent from a parent, we may require your parent’s consent before we collect and use that information.

### RESIDENTS OF CERTAIN U.S. STATES.

This section applies if you are a resident of California, Colorado or another U.S. state that has passed a privacy law similar to the California Consumer Privacy Act, as amended (“**CCPA**”) and requires specific privacy notice disclosures. For purposes of this section, references to “personal information” shall also include “sensitive personal information” as those terms are defined under the CCPA.

We collect, and, in the preceding 12 months we collected, the following categories of personal information and sensitive personal information (denoted by \*): (1) identifiers, such as name, email address, IP address, other device identifiers; (2) personal information listed in the California Customer Records statute such as name and billing address; (3) commercial information, such as subscription status and history; (4) Internet and similar network activity information, such as information regarding your interaction with the Services; (5) geolocation information, such as IP address and billing address; (6) visual information such as images that you upload to the Services; and (7) Account access credentials, such as username and password\*.&#x20;

For details on the purposes for collecting and disclosing your information, and the sources from which we collect it, refer to the “How we use your personal information” and “How we disclose your personal information” sections. Our personal information retention practices are outlined in the “Retention” section. We handle sensitive personal information as specified by the CCPA or with your consent.

We do not “sell” or “share” (as those terms are defined under the CCPA) personal information, nor have we done so in the preceding 12 months. Further, we do not have actual knowledge that we sell or share personal information of residents under 18 years of age. California residents under the age of 18 who have registered and posted content to the Services can request its removal by contacting us. They must verify their authorship of the content and specify where it is posted. We will make reasonably good faith efforts to remove or anonymize the content so the minor is not individually identifiable in relation to the content, but we cannot guarantee complete or comprehensive removal. For instance, third parties may have copied or republished the content (and any user output generated as a result), and it may remain in archives beyond our control.

### YOUR CHOICES.

‍In this section, we describe the choices available to all users of the Services.

* **Opt-out of communications**. You can opt-out of marketing-related emails by following the opt-out or unsubscribe instructions provided at the bottom of the email, or by contacting us directly. Please be aware that opting out of marketing emails will not affect the receipt of service-related and other non-marketing communications.
* **Cookies and web beacons.** Most browsers are set to accept cookies by default, but you can remove or reject cookies by following the instructions in your browser settings. However, please note that disabling cookies may affect the proper functioning of the Services. For more information on cookies, including how to view, manage, and delete them, you can visit [www.allaboutcookies.org](http://www.allaboutcookies.org). Additionally, you can configure your device to prevent images from loading, which can help prevent web beacons from functioning. Some internet browsers allow you to send “Do Not Track” signals to the online services you visit. Please be aware that we currently do not respond to “Do Not Track” signals.
* **Advertising choices.** You may be able to limit the use of your information for interest-based advertising through the following settings, options, and tools:
  * **Browser Settings and Privacy Browsers/Plug-ins**: Block third-party cookies via your web browser settings or by using privacy-focused browsers or ad-blocking plugins.
  * **Mobile Settings**: Adjust your mobile device settings to restrict the use of your advertising ID.
  * You will need to apply these opt-out settings on each device and browser from which you wish to limit the use of your information for interest-based advertising purposes. We cannot offer any assurances as to whether the companies we work with participate in the opt-out programs described above.
* **Linked third-party platforms**. If you choose to connect to the Services through your social media account or other third-party platform, you may be able to use your settings in your account with that platform to limit the information we receive from it. If you revoke our ability to access information from a third-party platform, that choice will not apply to information that we have already received from that third party.
* **Close your account**. If you wish to request to close your account, please contact us.

### YOUR RIGHTS.

Depending on your location, you may have certain rights in relation to your personal information. These rights may not be absolute and may only apply in specific circumstances. The rights you may have include:

* **Right to Know/Access Your Personal Information**: You may have the right to request access to the personal information we hold about you. This includes details about how we use and share your personal information, as well as how it has been or may be used or disclosed in the past.
* **Right to Delete Your Personal Information**: You may have the right to request that we delete the personal information we maintain about you.
* **Right to Correct Your Personal Information**: You may have the right to request that we correct any inaccurate personal information we hold about you.
* **Right to Object to Targeted Advertising**: You have the right to opt-out of the processing of your personal information for the purposes of targeted advertising, as defined under applicable law.

To exercise any of these rights, you can email us or delete your Account through the platform. We will not discriminate against you for exercising these rights.

#### Verification and Authorized Agents

To protect your privacy and security, we may need additional information to verify your identity before we can process your request. This may include the email addresses used for Account registration on our platform.

You may designate an authorized agent to make requests on your behalf. To do this, you must provide written authorization or a power of attorney document. Before we accept a request from an agent, we will require proof that the agent is authorized to act on your behalf. We may also need you to verify your identity directly with us.

#### Appeals

If we deny your request, you have the right to appeal our decision. You can contact us to initiate the appeal process.

### CHANGES TO THIS PRIVACY POLICY.

We may update this Policy from time to time to reflect changes in our practices or for other operational, legal, or regulatory reasons. When we make updates, we will post the revised Privacy Policy on this page and update the “Last updated” date at the top of the policy.

We may also notify you of material changes via email and/or a prominent notice on our Services, prior to the change becoming effective. We encourage you to review this Policy periodically to stay informed about how we are protecting the personal information we collect.

Changes to this Policy are effective when they are posted on this page or otherwise indicated in this Policy.

### HOW TO CONTACT US.

If you have any questions about our practices or this Policy, please email us at <info@opinionlabs.xyz>


# Terms of Use

*Last Updated: October 19, 2025*

### **1. Introduction**

These Terms of Use provide the terms and conditions under which you, whether personally or on behalf of an entity (“**you**” or “**your**”), are permitted to use, interact with or otherwise access the Interfaces or Features provided by Superchillclub Limited (the “**Company**,” “**we**,” “**us**,” or “**our**”).  These Terms of Use, together with any documents and additional terms or policies that are appended hereto or that expressly incorporate these Terms of Use by reference as well as our Privacy Policy (collectively, the “**Terms**”), constitute a binding agreement between you and us.

These Terms are applicable to (i) all content, informational functionality, and information features (the “**Content Features**”) available on opinion.trade (the “**Site**”) and any other site to which the Terms are posted (each, as applicable, an “**Interface**”) and (ii) software, including but not limited to the cryptocurrency-powered predictions market known as opinion.trade (hereinafter, the “**Platform**”), that may be available to users by connecting their self-hosted wallets via an Interface, including but not limited to the Site (the “**Technology Features**” and together with the Content Features, the “**Features**”).

The Site primarily functions to provide the Content Features — that is, news and information about global current events.  **If you are in a Restricted Jurisdiction (as defined below), you are only permitted to use the Content Features on the Site or any other Interface and may not use the Site or any other Interface for any other purpose and you may not access the Technology Features, including and in particular the Platform.**&#x20;

PLEASE REVIEW THE TERMS CAREFULLY. BY ACCESSING, INTERACTING WITH OR USING THE SITE OR ANY OTHER INTERFACE (INCLUDING BY LINKING YOUR WALLET, OR OTHERWISE CREATING AN IDENTIFIER ON THE SITE), ANY INTERFACE OR ANY FEATURE, YOU AGREE THAT YOU ARE ABLE TO ENTER INTO A BINDING AGREEMENT AND, AS SUCH, HAVE READ, UNDERSTOOD, AND AGREE TO BE BOUND BY THE TERMS, INCLUDING THE BINDING ARBITRATION AGREEMENT AND CLASS ACTION WAIVER BELOW. IF YOU DO NOT AGREE TO ALL OF THE TERMS, YOU ARE NOT AUTHORIZED TO INTERACT WITH, ACCESS OR USE ANY INTERFACE OR FEATURE.

USE OF THE SITE, PLATFORM OR TECHNOLOGY FEATURES IS NOT PERMITTED BY PERSONS OR ENTITIES WHO RESIDE IN, ARE LOCATED IN, ARE INCORPORATED IN, HAVE A REGISTERED OFFICE IN, OR HAVE THEIR PRINCIPAL PLACE OF BUSINESS IN ANY RESTRICTED JURISDICTIONS AS DEFINED BELOW (ANY SUCH PERSON OR ENTITY FROM THESE JURISDICTIONS, A “**RESTRICTED PERSON**”).  ADDITIONALLY, USE OF THE SITE, PLATFORM OR TECHNOLOGY FEATURES FOR TRADING IS NOT PERMITTED BY PERSONS OR ENTITIES (I) ON BEHALF OF ANY RESTRICTED PERSON(S) OR (II) DIRECTED, COORDINATED OR CONTROLLED BY ANY RESTRICTED PERSON (ANY SUCH PERSON OR ENTITY ALSO SHALL BE CONSIDERED A RESTRICTED PERSON).

THERE ARE NO EXCEPTIONS; THEREFORE, IF YOU ARE A RESTRICTED PERSON, THEN DO NOT ATTEMPT TO USE THE SITE, PLATFORM OR ANY OF THE TECHNOLOGY FEATURES TO TRADE.  USE OF A VIRTUAL PRIVATE NETWORK (“**VPN**”) OR ANY SIMILAR TOOL TO ATTEMPT TO OR TO CIRCUMVENT THE RESTRICTIONS SET FORTH HEREIN IS STRICTLY PROHIBITED.  ANY PERSON IN VIOLATION OF THESE TERMS MAY HAVE THEIR WALLETS PLACED IN CLOSE-ONLY MODE AND BE PROHIBITED FROM ACCESSING THE TECHNOLOGY FEATURES IN OUR SOLE DISCRETION.

### **2. The Site and Features**

#### Description of the Site and Features

The Platform enables you to participate in prediction markets by placing wagers on the outcomes of future events (“**Prediction Markets**”). You may create or participate in Prediction Markets by staking cryptocurrency on potential outcomes. All Prediction Markets must be based on objectively verifiable events and outcomes that can be determined through reliable third-party data sources (“**Oracle Sources**”).

The Company reserves the right to determine which Oracle Sources will be used to verify outcomes. The Company will clearly indicate the designated Oracle Source(s) for each Prediction Market at the time of market creation. Once an outcome has been determined based on data from the designated Oracle Source(s), it shall be considered final and binding. The Company maintains sole discretion over which types of events may be the subject of Prediction Markets and may remove or suspend any markets that violate these Terms or Applicable Laws (as defined below). You may not create or participate in markets related to illegal activities, personal violence, terrorism, or other prohibited subjects as determined by the Company.

The pricing information provided on the Site relating to Prediction Markets does not represent an offer, a solicitation of an offer, or any advice regarding, or recommendation to enter, a transaction with the Company.

Even when the Site appears to be dynamic (*e.g.*, updating or providing new displays when you – on your own accord – provide certain information), at no time is the Company acting directed by you or on your behalf. In addition, if you connect wallet on the Site such that your self-hosted cryptocurrency wallet (“**Wallet**”) is able to provide information to be transmitted to a blockchain network or other blockchain-based application, you should note that the Company (i) is *not* involved in providing or transmitting any such information to networks, (ii) *cannot* transmit any information to networks or otherwise assist in any transaction, (iii) *never* has access to and cannot control or provide guarantees relating to your Wallet and (iv) has *no authority* over and does *not* take possession or custody of your cryptoassets at any time, except as otherwise discussed herein.  This also means that the Company is unable to assist with transactions: please be vigilant in interacting with any immutable blockchain technology.

You are solely responsible for familiarizing yourself with your Wallet and its safety and security features, including any private keys and passwords associated therewith.  We will not and cannot access your private key, password, or any cryptoassets held within your Wallet nor can it reverse any transactions you initiate with your Wallet (or otherwise). We cannot be responsible or liable in any way for how you use your Wallet.

You should also familiarize yourself with the risks associated with transacting on blockchain networks, including but not limited to smart contract vulnerabilities, front end vulnerabilities, hacks, phishing attacks, social engineering attacks, cryptoasset volatility and transaction irreversibility.

The Company is not responsible for the operation of any blockchain network, and the Company does not make any guarantee of the network’s functionality, security, or availability.  The Company is not responsible for the activities of persons or entities who develop or use applications or who validate or verify transactions or other operations related to blockchain networks operated by third parties.  The Company cannot control how blockchain networks operated by third parties market their blockchain networks and users should not assume any blockchain networks operated by third parties are affiliated with the Company. All transactions broadcast to the applicable blockchain network via your Wallet may require the payment of non-refundable network transaction fees, which shall be borne entirely by you.

We do not effectuate, facilitate or control any transactions initiated via the Platform, and the Company will not be responsible for the result of any transactions, including but not limited to failed, inadvertent, or fraudulent transactions that may result in loss of funds or transaction fees or any other loss or harm to you.

#### Your Acknowledgement Relating to the Site and Information on the Site

You hereby acknowledge and agree that all information provided as part of the Content Features in connection with your access and use of the Site is intended for informational purposes only. The Site strives to provide accurate information, but there is no guarantee or warranty that the information is updated, complete, or timely.  For this reason, you acknowledge and agree that you are not relying on any of the information on the Site or any other Interface for any purpose and expressly (i) disclaim any reliance on any information on the Site or within the Features, and (ii) acknowledge that the Company will not be liable for any such information provided.

From time to time the Site, any other Interface or the Features may be inaccessible or inoperable for any reason, including, without limitation: (A) equipment malfunctions; (B) periodic maintenance procedures or repairs that the Company or any of its suppliers or contractors may undertake from time to time; (C) causes beyond the Company’s control or that the Company could not reasonably foresee; (D) disruptions and temporary or permanent unavailability of underlying blockchain infrastructure; or (E) unavailability of third-party service providers or external partners for any reason.

You should take all steps to independently verify any information on the Site and any Interface on which you intend to rely and should not take action based solely on any information contained on any Interfaces, including blog posts, data, articles, links to third-party content, social media content, news feeds, tutorials and videos.

None of the information provided on the Site, any other Interface, or through the Features should be construed as professional or investment advice, and the Company does not owe any duties and does not have any obligations to you based on the information provided on the Site, any other Interface, or through the Features.  You acknowledge and agree that all information provided in connection with your access and use of the Site, any other Interface, and the Features is for informational purposes only and should not be construed as professional advice.  You should not take, or refrain from taking, any action based on any information contained on the Site or any other Interface, or any other information that we make available at any time, including, without limitation, blog posts, articles, links to third-party content, discord content, news feeds, tutorials, social media content, and videos. Before you make any financial, legal, or other decisions involving the Features, you should seek independent professional advice from an individual who is licensed and qualified in the area for which such advice would be appropriate. The Terms are not intended to, and do not, create or impose any fiduciary duties on us. You further agree that the only duties and obligations that we owe you are those set out expressly in these Terms.

None of the information provided on the Site, any other Interface, or through any of the Features shall be interpreted as an invitation or inducement to (i) exercise any rights to acquire, dispose of, underwrite, or convert any cryptoassets or digital assets or (ii) buy, sell, or induce a user to buy or sell any cryptoassets or digital assets.

The Company is not acting as an investment adviser, trading, tax, legal or other adviser to any person or entity.

### 3. Modifications

#### To The Terms

We reserve the right, in our sole discretion, to modify the Terms at any time or from time to time. The modified Terms will be posted on the Site and any other Interface and will provide the last updated date at the top. Any modified Terms will become effective upon posting. By continuing to access, use or otherwise interact with any Interface or Feature after the effective date of any modification to the Terms, you are providing your explicit agreement to be bound by the Terms as modified. If you do not agree to be bound by any updated Terms, you are prohibited from using, accessing, or otherwise interacting with the Interfaces or Features. It is your responsibility to check any Interface you use regularly for modifications to the Terms.

#### To the Site, any other Interface, or the Features

We reserve the right, in our sole discretion, to modify, substitute, eliminate, restrict access to, or add to the Site, any other Interface, or any Feature at any time and from time to time, with or without notice to you, including deleting or otherwise materially modifying content and information.

We may, at our sole discretion, from time to time and with or without prior notice to you, modify, suspend or disable (temporarily or permanently) the Site, any other Interface, or the Features, in whole or in part, for any reason whatsoever, including, without limitation, to only allow open contracts to be closed. Upon termination of your access, your right to use the Site, any other Interface, or the Features will immediately cease. We will not be liable for any losses suffered by you resulting from any modification to any Site, any other Interface, or Features or from any modification, suspension, or termination, for any reason, of your access to all or any portion of the Site, any other Interface, or the Features. The Site, any other Interface, and the Features may evolve, which means the Company may apply changes, replace, or discontinue (temporarily or permanently) the Site, any other Interface, or the Features at any time in its sole discretion.

The following sections of these Terms will survive any termination of your access to the Site, any other Interface, or the Features, regardless of the reasons for its expiration or termination, in addition to any other provision which by law or by its nature should survive: Sections 5, 7-10.

### 4. Your Responsibilities, Representations & Prohibited Conduct

#### Your Representations

As a condition to accessing or using the Site or the Features, you represent and warrant to the Company the following:

*Of Age and Legal Authority.* The Site, any other Interface, and Features are intended only for users who are 18 years of age or older. If you are entering into the Terms on behalf of an entity, such as the company you work for, you represent to us that you have the legal authority to bind such an entity. If you do not meet these requirements, you are prohibited from accessing, using or otherwise interacting with the Site, any other Interface, or Features.

*Sanctions.* You represent and warrant that you are not, and for the duration of the time you use the Site, any other Interface, and Features, will not be (i) the subject of economic or trade sanctions administered or enforced by any governmental authority or otherwise designated on any list of prohibited or restricted parties; (ii) in contravention of any laws and regulations pertaining to anti-money laundering or terrorist financing; (iii) included on the List of Specially Designated Nationals and Blocked Persons maintained by the US Treasury Department’s Office of Foreign Assets Control (OFAC) or on any list pursuant to European Union (EU) and/or United Kingdom (UK) regulations (as the latter are extended to Panama by statutory instrument); or (iv) operationally based or domiciled in a country or territory in which sanctions imposed by the United Nations (whether through the Security Council or otherwise), OFAC, the EU and/or the UK apply, or otherwise pursuant to sanctions imposed by the United Nations, OFAC, EU, or UK.  If at any point the above is no longer true, then you must immediately cease using the Site, any other Interface, and Features.

*Restricted Jurisdictions.*  You acknowledge and agree that you are not permitted to access, use or trade with the Prediction Markets on the Platform if you are residing in, a citizen of, organized in or located in the following jurisdictions (collectively, the “**Restricted Jurisdictions**”): a jurisdiction or territory that is the subject of comprehensive country-wide, territory-wide, or regional economic sanctions by the United States, including but not limited to Iran, Syria, Cuba, North Korea, and the Crimea, Donetsk and Luhansk regions of Ukraine, the United States, United Kingdom, France, Ontario, Singapore, Poland, Thailand, South Korea, People’s Republic of China, Taiwan Province or Hong Kong.

*Wallet Configuration.*  You represent and warrant that you – and only you – are responsible for properly configuring, as applicable, and using the Site, any other Interface, or the Features or incorporating the Features into your applications or Wallet and for taking appropriate action to secure your data, including without limitation, financial or token information and private keys.  &#x20;

*No VPN To Circumvent or Attempt to Circumvent.*  You do not, and will not, use VPN software or any other privacy or anonymization tools or techniques to circumvent, or attempt to circumvent, any restrictions that apply to the Site, any other Interface, or the Features.

*Sophistication.*  You represent and warrant that you possess sufficient knowledge, market sophistication, professional advice, experience and skills to engage with the Site, any other Interface, and the Features, including the Platform if you are permitted to use it, and that you have the requisite understanding of blockchain technology, cryptoassets and cryptography to be able to engage with the Features. &#x20;

*Applicable Law.*  Your access to the Site, any other Interface, and Features is not (i) prohibited by and does not otherwise violate or assist you to violate any domestic or foreign law, rule, statute, regulation, by-law, order, protocol, code, decree, or another directive, requirement, or guideline, published or in force that applies to or is otherwise intended to govern or regulate any person, property, transaction, activity, event or other matter, including any rule, order, judgment, directive or other requirement or guideline issued by any domestic or foreign federal, provincial or state, municipal, local or other governmental, regulatory, judicial or administrative authority having jurisdiction over the Company, you, the Site, any other Interface, or the Features, or as otherwise duly enacted, enforceable by law, the common law or equity (collectively, “**Applicable Laws**”); or (ii) contribute to or facilitate any illegal activity.  You represent and warrant that you will comply with all Applicable Laws, and you will not use the Site, any other Interface, or the Features if the laws of your country, or any Applicable Law, prohibit you from doing so.  ​​&#x20;

*Financial Risks.*  Use of the Features, in particular entering into Prediction Markets on the Platform, may carry financial risk. You acknowledge and understand that Prediction Markets are inherently risky by their nature and participation in Prediction Markets could result in the loss of the full amount supplied, and you are solely responsible for conducting your own due diligence and assessment of risks before participating in any prediction market or placing any wager.  Prediction Markets such as the Prediction Markets available on the Platform are highly experimental, risky, and volatile. Transactions entered into in connection with the Prediction Markets are irreversible, final and there are no refunds. You acknowledge and agree that you will access and use the Site, any other Interface, and the Features, including the Prediction Markets and the Platform, at your own risk. The risk of loss in transacting in cryptoassets using Prediction Markets can be substantial. You should, therefore, carefully consider whether such transactions are suitable for you in light of your circumstances and financial resources. Further, such risks and adverse outcomes may be exacerbated when leverage and/or derivative products are used and we may, at any time and in our sole discretion, elect to suspend or terminate our support of any or all supported Prediction Markets.  BY USING THE PLATFORM TO TRADE AND ENTER INTO CONTRACTS, YOU CAN LOSE UP TO THE ENTIRE AMOUNT OF THE CRYPTOASSETS SUPPLIED TO THE PREDICTION MARKETS.

*Prediction Markets Resolution.*  You acknowledge and understand that the Company is not involved in nor responsible for the resolution of any Prediction Markets displayed on the Platform.  You acknowledge and agree that the Company is not responsible for any disputes related to the resolution of any Prediction Markets.

### 5. Your Responsibilities & Prohibited Conduct

You agree to access, use or otherwise interact with the Site, any other Interface, and Features only in an authorized, proper and appropriate manner and in accordance with these Terms and with all Applicable Laws.

With respect to the Prediction Markets, you acknowledge that: (a) they are wagering actual cryptocurrency with real value; (b) all wagers are final once confirmed on the blockchain; (c) the Company does not guarantee the accuracy of Oracle Sources or prediction outcomes; (d) technical delays or blockchain network issues may affect the timing of market resolution; and

(e) the Company may suspend or cancel any Prediction Market that becomes impossible to resolve definitively using the designated Oracle Sources.

You agree that you will not:

* Violate any applicable laws or regulations through your access to or use of the Site, any other Interface, or the Features;
* Violate the Terms;
* Engage in any activity that violates Applicable Laws;
* Exploit the Site, any other Interface, or Features for any unauthorized purpose;
* Circumvent or attempt to circumvent any content-filtering techniques, security measures or access controls that the Company employs on the Site, including, without limitation, through the use of a VPN or similar measures;
* Provide false, inaccurate, or misleading information while using the Site, any other Interface, or the Features or engage in activity that operates to defraud the Company, other users of the Features, or any other person;
* Harvest or otherwise collect information from the Site, any other Interface, or the Features for any unauthorized purpose;
* Engage in activity that violates any Applicable Laws, rule, or regulation concerning the integrity of the Site, any other Interface, and the Features, including (but not limited to): (i) any fraudulent act or scheme to defraud, deceive, trick or mislead; (ii) front-running; (iii) fraudulent trading; (iv) fictitious transactions; (v) pre-arranged or non-competitive transactions; (vi) cornering, or attempted cornering; (g) violations of bids or offers; (vii) wash trading (e.g., placing or accepting buy and sell orders in the same contract, where you know or reasonably should know that the purpose of the orders is to avoid taking a bona fide market position exposed to market risk); (viii) manipulation; (ix) spoofing (i.e., placing buy or sell orders without a bona fide intent to transact and with the intent to cancel before execution); (x) knowingly making any bid or offer for the purpose of making a market price that does not reflect the true state of the market; or (xi) any other trading activity that, in the reasonable judgment of the Company, is abusive, improper or disruptive;
* Use the Site, any other Interface, or Features in any manner that could disable, overburden, damage, or impair the Site, any other Interface, or Features or interfere with any other party’s use or enjoyment or the Site, any other Interface, or Features;
* Use the Site, any other Interface, or the Features, in any way that is, in our sole discretion, libelous, defamatory, profane, obscene, pornographic, sexually explicit, indecent, lewd, vulgar, suggestive, harassing, stalking, hateful, threatening, offensive, discriminatory, bigoted, abusive, inflammatory, fraudulent, deceptive, or otherwise objectionable or likely or intended to incite, threaten, facilitate, promote, or encourage hate, racial intolerance, or violent acts against others;
* Use the Site, any other Interface, or the Features for or on behalf of any person residing in a jurisdiction that we have, in our sole discretion, determined is a jurisdiction where the use of the Site, any other Interface, or the Features is prohibited, including all Restricted Jurisdictions;
* Reverse engineer, disassemble, or decompile the Interfaces or Features or apply any other process or procedure to derive the source code of any software included in the Interfaces or Features except to the extent applicable law does not allow this restriction or such rights have been expressly granted to you under a separate license;&#x20;
* Sublicense, sell, or otherwise distribute the Interfaces or Features, or any portion thereof;
* Use any data mining tools, robots, crawlers, or similar data gathering and extraction tools to scrape or otherwise remove data from the Site, any other Interface, or Features;
* Use any manual process to monitor or copy any of the material on the Site, any other Interface, or Features or for any other unauthorized purpose without our prior written consent;
* Introduce any viruses, trojan horses, worms, logic bombs, or other material which is malicious or technologically harmful to the Site, any other Interface, or Features;
* Attempt to gain unauthorized access to, interfere with, damage, or disrupt any parts of the Site, any other Interface, or Features, the server(s) on which the Site, any other Interface, or Features are stored, or any server, computer or database connected to the Site, any other Interface, or Features; or
* Attack the Site, any other Interface, or Features via a denial-of-service attack or a distributed denial-of-service attack or otherwise attempt to interfere with the proper working of the Site, any other Interface, or Features.

You acknowledge and agree that in the event that you use the Site, any other Interface, or Feature in a potentially prohibited manner, we may investigate and we reserve the right, in our sole discretion, to (i) terminate your access to the Site, any other Interface, and/or Features, (ii) prohibit you from participating in any reward or incentive programs or product launches and (iii) take any other action the Company deems reasonable or necessary, including cooperating with law enforcement or bringing claims against you if they result in harm or damage to the Company, to rectify the prohibited conduct or any consequences resulting therefrom. You hereby acknowledge and agree that using the Site, any other Interface, and/or Features may result in tax consequences. It is your sole responsibility to determine whether there are any tax consequences from any transactions you initiate using the Site, any other Interface, or Features, and you are solely responsible for ensuring compliance with applicable tax laws in your tax resident jurisdiction.

### 6. Additional Information

The Company or a third party acting on behalf of the Company may, from time to time, request additional information from you, including, but not limited to, information to confirm that you are not a Restricted Person. If you do not provide such information within the time period set by the Company or if the Company determines, in its sole discretion, that such information is not adequate, the Company may, in its sole discretion, (i) terminate your access to the Site, any other Interface, and/or Features, (ii) prohibit you from participating in any reward or incentive programs or product launches and (iii) take any other action the Company deems reasonable or necessary in its sole discretion.

### 7. Your Feedback

You may provide feedback to us or otherwise submit questions and inquiries through some of the Interfaces (“**Feedback**”).  We welcome Feedback relating to improvements or updates to the Interfaces or Features, or inquiries about the same.  We will try to review your Feedback but are not obligated to do so nor are we obligated to release any modifications or improvements you submit to us based on your Feedback.&#x20;

You acknowledge and agree that we will own all right, title, and interest in and to all Feedback you submit. You represent and warrant that (i) you and your licensors own all right, title, and interest in and to your Feedback; and (ii) you will not violate any intellectual property or other rights of third parties in providing Feedback to us.

### 8. Intellectual Property Rights

#### Ownership & License

The Company or its licensors own all right, title, and interest, including all intellectual property rights, in and to the Site, any other Interface, and Features, including any related content and technology, unless otherwise indicated. Subject to the Terms, the Company hereby grants you a personal, limited, revocable, non-exclusive, non-sublicensable, non-transferable license to use, copy, and distribute in connection with such use the Site, any other Interface, and Features. This license is solely intended to allow you to access, use or otherwise interact with the Site, any other Interface, and Features.

You acknowledge and agree that you do not receive any other rights to the Site, any other Interface, or Features other than those specified in the Terms. Certain Features may be provided to you under a separate license; third party features or applications integrated into the Site or Features may be subject to other or additional intellectual property licenses and thus, you must review any terms relevant to those third party features or applications to determine the relevant license applicable thereto.  You agree you will not violate the terms of any such separate license.

#### Reciprocal License

By using the Site, any other Interface, or any Feature, you grant us a limited, non-exclusive, sublicensable, worldwide, royalty free license to use, copy, modify and display any content or Feedback you provide to us or that you post on or through the Site, any other Interface, or any Features solely for our business purposes, including but not limited to the purpose of providing the Site, any other Interface, or Features for so long as is necessary to do so.

By providing any Feedback or providing any information on or through the Site, any other Interface, or via the Features (collectively, the “**Content**”), you hereby grant to us a royalty-free, fully paid-up, sublicensable, transferable, perpetual, irrevocable, non-exclusive, worldwide license to use, copy, modify, create derivative works of, display, perform, publish and distribute, in any form, medium, or manner, any Content, including, without limitation, for promoting the Platform, its affiliates, the Features, the Site or any other Interface. You represent and warrant that (a) you own your Content or have the right to grant the rights and licenses in these Terms; and (b) your Content and our use of your Content, as licensed herein, does not and will not violate, misappropriate or infringe on any third party’s rights.

#### Third Party Information or Services

As discussed throughout the Terms, the Site, any other Interface, and Features may be integrated with or otherwise give access to applications, services, sites, technology, data, operations, features and resources that are provided or otherwise made available by third parties (“**Third Party Services**”).

If the Site, any other Interface, or Features may contain links to Third Party Services, then they are provided for your convenience only. We have no control over the contents of those sites or resources, and accept no responsibility for them or for any loss or damage that may arise from your use of them. If you decide to access a Third Party Service integrated with or linked to any Interface or Feature, you do so entirely at your own risk and subject to the terms and conditions of use for such websites. We reserve the right to withdraw linking permission without notice.

As further noted throughout these Terms, your access and use of Third Party Services may be subject to additional terms and conditions, privacy policies, or other agreements with those third parties, which we do not control and otherwise may have no relationship with. The Company also has no control over and is not responsible for such Third Party Services, including for the accuracy, availability, reliability, verification, or completeness of information or content shared by or available through Third Party Services, or the privacy practices of Third Party Services.

Your use of any Third Party Services is directly between you and that third party, and you acknowledge and agree that we will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any Third Party Services. You, and not we, will be responsible for any and all costs and charges associated with your use of any Third Party Services.

You acknowledge and agree that the Company is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products, or other materials on or available from such sites or resources. You further acknowledge and agree that Company shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods, or services available on or through any such site or resource.

Please review any applicable terms, privacy policies or agreements of Third Party Services prior to using such services. The integration or inclusion of such Third Party Services does not imply an endorsement or recommendation of such Third Party Services.

### 9. Indemnification

You agree to defend, indemnify, and hold harmless us and our licensors, and each of their respective employees, officers, directors, and representatives (collectively, the “**Company Parties**”) from and against all liability for monetary damages, contractual claims of any nature, economic loss (including direct, incidental or consequential damages), loss of income or profits, fines, penalties, exemplary or punitive damages, and any other injury, damage, or harm, including reasonable attorney’s fees (“**Damages**”) that relate in any way to any demand, claim, regulatory action, proceeding or lawsuit, regardless of the cause or alleged cause, whether the allegations are groundless, fraudulent, false, or lack merit and regardless of the theory of recovery (“**Claim(s)**”) arising out of or relating to: (i) your use of the Interfaces or Features (including any use by your customers, users, employees, and other personnel); (ii) breach of the Terms or violation of applicable law by you, your customers, users, employees and other personnel; (iii) a dispute between you and any third party; (iv) your alleged or actual infringement or misappropriation of any third party’s intellectual property or other rights; and (v) your Feedback. In the event we receive any third party subpoena or other compulsory legal order or process associated with Claims described in (i) through (v) above, then in addition to the indemnification set forth above, you will reimburse us for our employees’ and contractors’ time and materials spent responding to such matters at our then-current hourly rates as well as our reasonable attorneys’ fees.

If you are obligated to indemnify us, then you agree that we will have the right, in our sole discretion, to control any action or proceeding and to determine whether we wish to settle, and if so, on what terms, and you agree to fully cooperate with us in the defense or settlement of such Claim.

### 10. Disclaimers and Limitations of Liability

#### Site, Interfaces and Features

By accessing the Site, any other Interface, or Features, you hereby acknowledge and agree that the Company cannot and does not guarantee the functionality, security, or availability of the Site, any other Interface, or Features. The technologies on which the Site, any other Interface, or Features rely may be subject to sudden changes and we cannot and do not guarantee that your access to the Site, any other Interface, or Features or the ability to transact thereon will be uninterrupted or error free or that your cryptoassets will be secure at all times. You assume all risks related thereto.

#### No Representations or Warranties

THE SITE, ANY OTHER INTERFACE, AND FEATURES ARE PROVIDED “AS IS.” EXCEPT TO THE EXTENT PROHIBITED BY LAW, OR TO THE EXTENT ANY STATUTORY RIGHTS APPLY THAT CANNOT BE EXCLUDED, LIMITED OR WAIVED, NEITHER WE NOR ANY OTHER RELATED PARTY MAKES ANY REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE REGARDING THE INTERFACES OR FEATURES, AND THE COMPANY EXPRESSLY DISCLAIMS ALL WARRANTIES, INCLUDING ANY IMPLIED OR EXPRESS WARRANTIES (i) OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR QUIET ENJOYMENT, (ii) ARISING OUT OF ANY COURSE OF DEALING OR USAGE OR TRADE, (iii) THAT THE SITE, ANY OTHER INTERFACE, OR FEATURES WILL BE ACCURATE, UNINTERRUPTED, ERROR FREE OR FREE OF HARMFUL COMPONENTS, AND (iv) THAT ANY CONTENT OR ASSETS WILL BE SECURE OR NOT OTHERWISE LOST OR ALTERED.

#### Limitations of Liability

TO THE EXTENT PERMITTED BY APPLICABLE LAWS, NEITHER THE COMPANY NOR ANY OF ITS SERVICE PROVIDERS WILL BE LIABLE TO YOU FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES (INCLUDING DAMAGES FOR LOSS OF PROFITS, REVENUES, CUSTOMERS OR USERS, OPPORTUNITIES, GOODWILL, USE, DATA, CONTENT OR OTHER ASSETS), EVEN IF THE COMPANY OR SERVICE PROVIDERS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. YOU AGREE THAT THESE LIMITATIONS WILL SURVIVE AND APPLY EVEN IN THE EVENT ANY LIMITED REMEDY IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. FURTHER, THE COMPANY WILL NOT BE RESPONSIBLE FOR ANY COMPENSATION, REIMBURSEMENT, OR DAMAGES ARISING IN CONNECTION WITH (i) YOUR INABILITY TO USE, OR ANY DELAY IN THE USE OF, THE INTERFACES OR FEATURES, INCLUDING AS A RESULT OF ANY (A) TERMINATION OF THE TERMS OR YOUR USE OF OR ACCESS TO THE INTERFACES OR FEATURES, (B) OUR SUSPENSION OR DISCONTINUATION OF ANY OR ALL OF THE INTERFACES OR FEATURES, OR, (C) ANY UNANTICIPATED OR UNSCHEDULED DOWNTIME OF ALL OR A PORTION OF THE SITE, ANY INTERFACES OR FEATURES FOR ANY REASON; (ii) THE COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; (iii) ANY INVESTMENTS, EXPENDITURES, OR COMMITMENTS BY YOU IN CONNECTION WITH THE TERMS OR YOUR USE OF OR ACCESS TO THE INTERFACES OR FEATURES; (iv) ANY UNAUTHORIZED ACCESS TO, ALTERATION OF, OR THE DELETION, DESTRUCTION, DAMAGE, LOSS OR FAILURE TO STORE ANY OF YOUR DATA; (v) ANY CHANGE IN VALUE OF ANY CRYPTOASSET; OR (vi) ANY DAMAGE, LOSS, OR INJURY RESULTING FROM HACKING, TAMPERING, OR OTHER UNAUTHORIZED ACCESS OR USE OF THE INTERFACES OR FEATURES. IN ANY CASE, THE COMPANY’S AGGREGATE LIABILITY UNDER THESE TERMS WILL NOT EXCEED $100.

### 11. Governing Law, Dispute Resolution and Class Action Waiver

#### Governing Law

These Terms and any action related thereto will be governed by the laws of Panama, without regard to its conflict of law provisions. Except as otherwise expressly set forth herein, the exclusive jurisdiction for all Disputes that you and the Company are not required to arbitrate will be the courts located in Panama, and you and the Company each waive any objection to jurisdiction and venue in such courts.

#### Dispute Resolution

Prior to commencing any legal proceeding against us of any kind, including an arbitration, you and we agree that we will attempt to resolve any Claim by engaging in good faith negotiations. Such negotiations require that the aggrieved party provide a written notice to the other party specifying the nature and details of the dispute (the “**Initial Notice**”). The party receiving such notice shall have twenty days to respond, and within forty-five days after the Initial Notice was sent, the parties shall meet and confer in good faith to try and resolve the Claim. If the parties are unable to do so within ninety days of the Initial Notice, the parties may agree to mediate their dispute or either party may submit to arbitration according to these Terms.

#### Mandatory Arbitration

Any dispute, claim or controversy arising out of or relating to the Terms, Interfaces or Features, or the breach, termination, enforcement, interpretation or validity of the Terms, including the determination of the scope or applicability of this agreement to arbitrate, will be determined by arbitration in Panama before one arbitrator. This clause will not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction.

You and we agree that the arbitrator shall have exclusive authority to decide all issues relating to the interpretation, applicability, enforceability and scope of this arbitration agreement. &#x20;

Except as otherwise provided in these Terms, the arbitrator shall determine all issues of liability on the merits of any claim asserted by either party and may award declaratory or injunctive relief only in favor of the individual party seeking relief and only to the extent necessary to provide relief warranted by that party’s individual claim. To the extent that you or we prevail on a claim and seek public injunctive relief (that is, injunctive relief that has the primary purpose and effect of prohibiting unlawful acts that threaten future injury to the public), the entitlement to and extent of such relief must be litigated in a civil court of competent jurisdiction and not in arbitration. The parties agree that litigation of any issues of public injunctive relief shall be stayed pending the outcome of the merits of any individual claims in arbitration.

YOU UNDERSTAND THAT BY AGREEING TO THE TERMS, THE PARTIES ARE EACH WAIVING THE RIGHT TO TRIAL BY JURY OR TO PARTICIPATE IN A CLASS ACTION OR CLASS ARBITRATION.

#### Class Action / Representative Claim Waiver

Any arbitration under the Terms will take place on an individual basis – class arbitrations and class actions are not permitted.

To the fullest extent permitted by applicable law, you agree that any proceeding to resolve any dispute, claim or controversy will be brought and conducted only in your individual capacity and not as a party (plaintiff or otherwise) or member of any class (or purported class), consolidated proceeding, multi-plaintiff proceeding or representative action or proceeding.

Any arbitration will not be permitted to be consolidated or aggregated with any other arbitration and the arbitrator will not have any authority to do so and will not have the authority to make an award to any person or entity not a part of the individual arbitration in which you are a party.  You further agree that any arbitrator may not preside over any form of class action involving you and us.

### 12. General Terms

#### Entire Agreement

The Terms, including any policies that expressly incorporate the Terms by reference, constitute the entire agreement between you and us regarding the subject matter herein. The Terms supersede all prior or contemporaneous representations, understandings, agreements, or communications between you and us, if any, whether written or verbal, regarding the subject matter of the Terms.

#### No Relationships or Assignments

Nothing in the Terms shall be construed to create any relationship between you and us other than as defined herein.  Neither you nor we are an agent of each other under these Terms or otherwise, and you shall have no right to hold yourself out as in any way having a relationship with us other than as someone using, accessing or otherwise interfacing with the Interface and/or Features.

You agree that you are not permitted to assign or otherwise transfer any of your rights and obligations under the Terms, but the Company may assign or transfer the Terms, in whole or in part, without restriction. Any assignment or transfer in violation of this Section will be void.  Subject to the foregoing, the Terms will be binding upon, and inure to the benefit of, the parties and their respective permitted successors and assigns.

#### Waiver

The failure by us to enforce any provision of the Terms will not constitute a present or future waiver of such provision nor limit our right to enforce such provision at a later time. All waivers by us must be in writing to be effective.

#### Severability

If any portion of the Terms are held to be invalid or unenforceable, the remaining portions of the Terms will remain in full force and effect. Any invalid or unenforceable portions will be interpreted to effectuate the intent of the original portion. If such construction is not possible, the invalid or unenforceable portion will be severed from the Terms but the rest of the Terms will remain in full force and effect.

#### Remedies

Any right or remedy of the Company set forth in these Terms is in addition to, and not in lieu of, any other right or remedy whether described in these Terms, under Applicable Law, at law, or in equity. The failure or delay of the Company in exercising or enforcing any right, power, or privilege under these Terms shall not operate as a waiver thereof.

#### Contact Us

You may also contact us with questions, complaints, or claims concerning the Features at <info@opinionlabs.xyz>




---

[Next Page](/llms-full.txt/1)

