``models`` — Data models
========================

.. module:: o2_sdk.models
   :synopsis: Data models for O2 Exchange API request/response types.

All API response types are implemented as Python dataclasses with
``from_dict()`` class methods for JSON parsing.


Enums
-----

.. class:: OrderSide

   Side of an order.

   .. attribute:: BUY
      :value: "Buy"

   .. attribute:: SELL
      :value: "Sell"

.. class:: OrderType

   Type of an order (simple types without additional parameters).

   For order types that require additional parameters, use the dedicated
   dataclasses instead: :class:`LimitOrder` (for limit orders with
   price + timestamp) or :class:`BoundedMarketOrder` (for bounded market
   orders with max_price + min_price).

   .. attribute:: SPOT
      :value: "Spot"

   .. attribute:: MARKET
      :value: "Market"

   .. attribute:: FILL_OR_KILL
      :value: "FillOrKill"

   .. attribute:: POST_ONLY
      :value: "PostOnly"


Order type parameter classes
----------------------------

.. class:: LimitOrder(price, timestamp=None)

   Limit order with expiry. In :meth:`~o2_sdk.client.O2Client.create_order`,
   *price* is human-readable and auto-scaled. In
   :class:`CreateOrderAction`, *price* should be the pre-scaled chain
   integer.

   .. attribute:: price
      :type: float

   .. attribute:: timestamp
      :type: int | None

      Unix timestamp for time-in-force. ``None`` defaults to the current
      time.

.. class:: BoundedMarketOrder(max_price, min_price)

   Bounded market order with price bounds. In
   :meth:`~o2_sdk.client.O2Client.create_order`, prices are
   human-readable and auto-scaled. In :class:`CreateOrderAction`, prices
   should be pre-scaled chain integers.

   .. attribute:: max_price
      :type: float

   .. attribute:: min_price
      :type: float


Scalar types
------------

.. class:: Id(value)

   A hex identifier that always displays with a ``0x`` prefix.

   The O2 API returns hex identifiers inconsistently — sometimes with the
   ``0x`` prefix and sometimes without. ``Id`` normalizes the value on
   construction so that ``str(id)`` always starts with ``0x``, and
   compares case-insensitively.

   ``Id`` is a :class:`str` subclass, so it works transparently in
   f-strings, comparisons, dict keys, and ``is not None`` checks.

   :param value: The hex string, with or without ``0x`` prefix.
   :type value: str

   .. code-block:: pycon

      >>> from o2_sdk import Id
      >>> Id("97edbbf5")
      Id('0x97edbbf5')
      >>> Id("0x97EDBBF5")
      Id('0x97edbbf5')


Market models
-------------

.. class:: MarketAsset

   An asset within a market (base or quote).

   .. attribute:: symbol
      :type: str

      The human-readable asset symbol (e.g., ``"FUEL"``, ``"USDC"``).

   .. attribute:: asset
      :type: str

      The on-chain asset ID (``0x``-prefixed hex).

   .. attribute:: decimals
      :type: int

      The number of decimal places for this asset.

   .. attribute:: max_precision
      :type: int

      Maximum price precision (number of significant digits).

.. class:: Market

   A trading market (order book) on the O2 Exchange.

   .. attribute:: contract_id
      :type: Id

      The on-chain contract ID managing this order book.

   .. attribute:: market_id
      :type: Id

      The unique market identifier within the contract.

   .. attribute:: maker_fee
      :type: str

      Maker fee rate (string representation).

   .. attribute:: taker_fee
      :type: str

      Taker fee rate (string representation).

   .. attribute:: min_order
      :type: str

      Minimum order size (in quote-scaled units).

   .. attribute:: dust
      :type: str

      Dust threshold.

   .. attribute:: price_window
      :type: int

      Price window constraint.

   .. attribute:: base
      :type: MarketAsset

      The base asset definition.

   .. attribute:: quote
      :type: MarketAsset

      The quote asset definition.

   .. property:: pair
      :type: str

      The human-readable pair string (e.g., ``"FUEL/USDC"``).

   .. method:: scale_price(human_value)

      Convert a human-readable price to an on-chain integer, truncated
      to the market's ``max_precision``.

      :param human_value: The price as a float (e.g., ``0.025``).
      :type human_value: float
      :returns: The scaled price integer.
      :rtype: int

   .. method:: format_price(chain_value)

      Convert an on-chain integer price back to a human-readable float.

      :param chain_value: The on-chain price integer.
      :type chain_value: int
      :returns: The human-readable price.
      :rtype: float

   .. method:: scale_quantity(human_value)

      Convert a human-readable quantity to an on-chain integer.

      :param human_value: The quantity as a float (e.g., ``100.0``).
      :type human_value: float
      :returns: The scaled quantity integer.
      :rtype: int

   .. method:: format_quantity(chain_value)

      Convert an on-chain integer quantity to a human-readable float.

      :param chain_value: The on-chain quantity integer.
      :type chain_value: int
      :returns: The human-readable quantity.
      :rtype: float

   .. method:: validate_order(price, quantity)

      Validate scaled price/quantity against on-chain constraints.

      Checks:

      - **PricePrecision**: price must be a multiple of the truncation factor.
      - **FractionalPrice**: ``(price * quantity) % 10^base_decimals == 0``.
      - **min_order**: forwarded quote amount must meet the minimum.

      :param price: The scaled price (on-chain integer).
      :type price: int
      :param quantity: The scaled quantity (on-chain integer).
      :type quantity: int
      :raises ValueError: If any constraint is violated.

   .. method:: adjust_quantity(price, quantity)

      Adjust a quantity to satisfy the FractionalPrice constraint.

      Returns the largest quantity ``<=`` the input such that
      ``(price * quantity) % 10^base_decimals == 0``.

      :param price: The scaled price.
      :type price: int
      :param quantity: The scaled quantity.
      :type quantity: int
      :returns: The adjusted quantity.
      :rtype: int


.. class:: MarketsResponse

   Response from ``GET /v1/markets``.

   .. attribute:: books_registry_id
      :type: Id

   .. attribute:: accounts_registry_id
      :type: Id

   .. attribute:: trade_account_oracle_id
      :type: Id

   .. attribute:: chain_id
      :type: str

   .. attribute:: base_asset_id
      :type: Id

   .. attribute:: markets
      :type: list[Market]

   .. property:: chain_id_int
      :type: int

      The chain ID as a plain integer.


Account models
--------------

.. class:: Identity

   Base identity type — either an ``Address`` or a ``ContractId``. Use
   :class:`AddressIdentity` or :class:`ContractIdentity` to construct
   instances directly, or :meth:`from_dict` to parse from API responses.

   .. attribute:: value
      :type: str

      The ``0x``-prefixed hex string.

   .. classmethod:: from_dict(d)

      Factory method that returns the appropriate subclass.

      :param d: A dict like ``{"Address": "0x..."}`` or
          ``{"ContractId": "0x..."}``.
      :type d: dict
      :returns: An :class:`AddressIdentity` or :class:`ContractIdentity`.
      :rtype: Identity
      :raises ValueError: If the dict format is not recognized.

   .. property:: address_bytes
      :type: bytes

      The address as raw bytes (32 bytes).

   .. method:: to_dict()

      Serialize to the API's JSON format. Implemented by subclasses.

      :rtype: dict

   .. property:: discriminant
      :type: int

      ``0`` for Address, ``1`` for ContractId. Implemented by subclasses.

.. class:: AddressIdentity(value)

   Identity for a Fuel Address. Subclass of :class:`Identity`.

   .. code-block:: python

      addr = AddressIdentity("0xabc...")
      addr.to_dict()      # {"Address": "0xabc..."}
      addr.discriminant    # 0

.. class:: ContractIdentity(value)

   Identity for a Fuel ContractId. Subclass of :class:`Identity`.

   .. code-block:: python

      contract = ContractIdentity("0xdef...")
      contract.to_dict()      # {"ContractId": "0xdef..."}
      contract.discriminant    # 1

.. class:: AccountInfo

   Trading account information.

   .. attribute:: trade_account_id
      :type: Id | None

      The trading account contract ID, or ``None`` if the account does
      not exist.

   .. attribute:: trade_account
      :type: TradeAccount | None

      Detailed account data (nonce, owner, etc.), or ``None``.

   .. attribute:: session
      :type: dict | None

      Active session data, if any.

   .. property:: exists
      :type: bool

      ``True`` if the account exists (i.e., ``trade_account_id`` is not
      ``None``).

   .. property:: nonce
      :type: int

      The current nonce for this account. Returns ``0`` if the account
      does not exist.

.. class:: AccountCreateResponse

   Response from ``POST /v1/accounts``.

   .. attribute:: trade_account_id
      :type: Id

   .. attribute:: nonce
      :type: str


Session models
--------------

.. class:: SessionInfo

   An active trading session, returned by
   :meth:`~o2_sdk.client.O2Client.create_session`.

   .. attribute:: session_id
      :type: Identity

      The session identity (the session wallet's address).

   .. attribute:: trade_account_id
      :type: Id

      The trading account this session operates on.

   .. attribute:: contract_ids
      :type: list[Id]

      The market contracts this session is authorised for.

   .. attribute:: session_expiry
      :type: str

      Unix timestamp (as string) when the session expires.

   .. attribute:: session_private_key
      :type: bytes | None

      The session wallet's private key (used internally for signing
      actions).

   .. attribute:: owner_address
      :type: str | None

      The owner wallet's B256 address.

   .. attribute:: nonce
      :type: int

      The current nonce, updated after each action.

.. class:: SessionResponse

   Raw response from ``PUT /v1/session``.

   .. attribute:: tx_id
      :type: Id

   .. attribute:: trade_account_id
      :type: Id

   .. attribute:: contract_ids
      :type: list[Id]

   .. attribute:: session_id
      :type: Identity

   .. attribute:: session_expiry
      :type: str


Order models
------------

.. class:: Order

   An order on the O2 Exchange.

   .. attribute:: order_id
      :type: Id

      The unique order identifier.

   .. attribute:: side
      :type: str

      ``"Buy"`` or ``"Sell"``.

   .. attribute:: order_type
      :type: Any

      The order type (string or dict for Limit/BoundedMarket).

   .. attribute:: quantity
      :type: str

      The order quantity (on-chain scaled, as string).

   .. attribute:: quantity_fill
      :type: str

      The filled quantity (on-chain scaled, as string).

   .. attribute:: price
      :type: str

      The order price (on-chain scaled, as string).

   .. attribute:: price_fill
      :type: str

      The fill price (on-chain scaled, as string).

   .. attribute:: timestamp
      :type: str

      The order creation timestamp.

   .. attribute:: close
      :type: bool

      ``True`` if the order is closed (fully filled or canceled).

   .. attribute:: partially_filled
      :type: bool

      ``True`` if the order has been partially filled.

   .. attribute:: cancel
      :type: bool

      ``True`` if the order was canceled.

   .. property:: is_open
      :type: bool

      ``True`` if the order is still active (not closed).

.. class:: OrdersResponse

   Response from ``GET /v1/orders``.

   .. attribute:: identity
      :type: Identity | None

   .. attribute:: market_id
      :type: Id

   .. attribute:: orders
      :type: list[Order]


Trade models
------------

.. class:: Trade

   A completed trade on the exchange.

   .. attribute:: trade_id
      :type: str

      The unique trade identifier.

   .. attribute:: side
      :type: str

      The taker's side (``"Buy"`` or ``"Sell"``).

   .. attribute:: total
      :type: str

      The total quote value of the trade.

   .. attribute:: quantity
      :type: str

      The traded quantity (on-chain scaled).

   .. attribute:: price
      :type: str

      The trade price (on-chain scaled).

   .. attribute:: timestamp
      :type: str

      The trade timestamp.

   .. attribute:: maker
      :type: Identity | None

   .. attribute:: taker
      :type: Identity | None

   .. attribute:: market_id
      :type: Id | None


Balance models
--------------

.. class:: Balance

   Asset balance information for a trading account.

   .. attribute:: order_books
      :type: dict[str, OrderBookBalance]

      Per-order-book breakdown of locked and unlocked amounts.

   .. attribute:: total_locked
      :type: str

      Total amount locked in open orders (on-chain scaled).

   .. attribute:: total_unlocked
      :type: str

      Total unlocked (settled) amount (on-chain scaled).

   .. attribute:: trading_account_balance
      :type: str

      Total balance in the trading account (on-chain scaled).

   .. property:: available
      :type: int

      The trading account balance as an integer.

.. class:: OrderBookBalance

   Balance breakdown within a single order book.

   .. attribute:: locked
      :type: str

      Amount locked in open orders.

   .. attribute:: unlocked
      :type: str

      Amount unlocked (settled).


Depth models
------------

.. class:: DepthLevel

   A single price level in the order book.

   .. attribute:: price
      :type: str

      The price at this level (on-chain scaled).

   .. attribute:: quantity
      :type: str

      The total quantity at this level (on-chain scaled).

.. class:: DepthSnapshot

   A snapshot of order book depth.

   .. attribute:: buys
      :type: list[DepthLevel]

      Bid (buy) price levels, best first.

   .. attribute:: sells
      :type: list[DepthLevel]

      Ask (sell) price levels, best first.

   .. attribute:: market_id
      :type: Id | None

   .. property:: best_bid
      :type: DepthLevel | None

      The best (highest) bid price level, or ``None`` if the book is
      empty.

   .. property:: best_ask
      :type: DepthLevel | None

      The best (lowest) ask price level, or ``None`` if the book is
      empty.

.. class:: DepthUpdate

   A real-time depth update from the WebSocket stream.

   .. attribute:: changes
      :type: DepthSnapshot

      The depth changes (or full snapshot for the initial message).

   .. attribute:: market_id
      :type: Id

   .. attribute:: onchain_timestamp
      :type: str | None

   .. attribute:: seen_timestamp
      :type: str | None

   .. attribute:: is_snapshot
      :type: bool

      ``True`` if this is the initial full snapshot.


Bar models
----------

.. class:: Bar

   An OHLCV candlestick bar.

   .. attribute:: time
      :type: int

      The bar's opening time (Unix seconds).

   .. attribute:: open
      :type: str

      Opening price.

   .. attribute:: high
      :type: str

      Highest price.

   .. attribute:: low
      :type: str

      Lowest price.

   .. attribute:: close
      :type: str

      Closing price.

   .. attribute:: volume
      :type: str

      Trading volume.


Action response models
----------------------

.. class:: ActionsResponse

   Response from ``POST /v1/session/actions``.

   This is the primary result type returned by trading methods such as
   :meth:`~o2_sdk.client.O2Client.create_order`,
   :meth:`~o2_sdk.client.O2Client.cancel_order`, and
   :meth:`~o2_sdk.client.O2Client.batch_actions`.

   .. attribute:: tx_id
      :type: Id | None

      The on-chain transaction ID, or ``None`` on failure.

   .. attribute:: orders
      :type: list[Order] | None

      Created order details (only if ``collect_orders=True``).

   .. attribute:: message
      :type: str | None

      Error or status message.

   .. attribute:: reason
      :type: str | None

      On-chain revert reason, if applicable.

   .. attribute:: receipts
      :type: list | None

      Raw transaction receipts.

   .. attribute:: code
      :type: int | None

      Error code (see :doc:`../api/errors`).

   .. property:: success
      :type: bool

      ``True`` if the action succeeded (``tx_id`` is not ``None``).


Action input models
-------------------

These dataclasses are the typed inputs for
:meth:`~o2_sdk.client.O2Client.batch_actions`. Each has a ``to_dict()``
method that serializes to the wire format expected by the API.

.. class:: CreateOrderAction(side, price, quantity, order_type=OrderType.SPOT)

   Create a new order. Prices and quantities must be **pre-scaled on-chain
   integers** passed as strings.

   .. attribute:: side
      :type: OrderSide

   .. attribute:: price
      :type: str

      Pre-scaled chain integer as string.

   .. attribute:: quantity
      :type: str

      Pre-scaled chain integer as string.

   .. attribute:: order_type
      :type: OrderType | LimitOrder | BoundedMarketOrder

      Defaults to ``OrderType.SPOT``.

.. class:: CancelOrderAction(order_id)

   Cancel an existing order.

   .. attribute:: order_id
      :type: Id

      The normalised hex order ID.

.. class:: SettleBalanceAction(to)

   Settle balance to an identity. Accepts an :class:`Identity` subclass
   (e.g. :class:`ContractIdentity`) or an :class:`Id`, which is
   auto-wrapped as :class:`ContractIdentity` during serialisation.

   .. attribute:: to
      :type: Identity | Id

   .. code-block:: python

      # Pass session.trade_account_id directly (an Id):
      SettleBalanceAction(to=session.trade_account_id)

      # Or pass an explicit Identity:
      SettleBalanceAction(to=ContractIdentity("0xabc..."))

.. class:: RegisterRefererAction(to)

   Register a referer. Same auto-wrapping behaviour as
   :class:`SettleBalanceAction`.

   .. attribute:: to
      :type: Identity | Id

.. data:: Action

   Type alias: ``CreateOrderAction | CancelOrderAction | SettleBalanceAction | RegisterRefererAction``

.. class:: MarketActions(market_id, actions)

   Groups a list of actions for a specific market. This is the input
   type for :meth:`~o2_sdk.client.O2Client.batch_actions`.

   .. attribute:: market_id
      :type: str

      The market's hex ID.

   .. attribute:: actions
      :type: list[Action]

      The actions to execute on this market (max 5 per request).

   .. code-block:: python

      from o2_sdk import (
          CancelOrderAction, CreateOrderAction, SettleBalanceAction,
          MarketActions, OrderSide, OrderType,
      )

      batch = MarketActions(
          market_id=market.market_id,
          actions=[
              SettleBalanceAction(to=session.trade_account_id),
              CreateOrderAction(
                  side=OrderSide.BUY,
                  price=str(scaled_price),
                  quantity=str(scaled_qty),
              ),
          ],
      )
      result = await client.batch_actions([batch], collect_orders=True)


WebSocket update models
-----------------------

.. class:: OrderUpdate

   Order update from the WebSocket stream.

   .. attribute:: orders
      :type: list[Order]

   .. attribute:: onchain_timestamp
      :type: str | None

   .. attribute:: seen_timestamp
      :type: str | None

.. class:: TradeUpdate

   Trade update from the WebSocket stream.

   .. attribute:: trades
      :type: list[Trade]

   .. attribute:: market_id
      :type: Id

   .. attribute:: onchain_timestamp
      :type: str | None

   .. attribute:: seen_timestamp
      :type: str | None

.. class:: BalanceUpdate

   Balance update from the WebSocket stream.

   .. attribute:: balance
      :type: list[dict]

   .. attribute:: onchain_timestamp
      :type: str | None

   .. attribute:: seen_timestamp
      :type: str | None

.. class:: NonceUpdate

   Nonce update from the WebSocket stream.

   .. attribute:: contract_id
      :type: Id

   .. attribute:: nonce
      :type: str

   .. attribute:: onchain_timestamp
      :type: str | None

   .. attribute:: seen_timestamp
      :type: str | None


Withdrawal models
-----------------

.. class:: WithdrawResponse

   Response from ``POST /v1/accounts/withdraw``.

   .. attribute:: tx_id
      :type: Id | None

   .. attribute:: message
      :type: str | None

   .. property:: success
      :type: bool

      ``True`` if the withdrawal succeeded.


Whitelist & faucet models
-------------------------

.. class:: WhitelistResponse

   Response from ``POST /analytics/v1/whitelist``.

   .. attribute:: success
      :type: bool

   .. attribute:: trade_account
      :type: str

   .. attribute:: already_whitelisted
      :type: bool

.. class:: FaucetResponse

   Response from the testnet/devnet faucet.

   .. attribute:: message
      :type: str | None

   .. attribute:: error
      :type: str | None

   .. property:: success
      :type: bool

      ``True`` if the mint succeeded.

.. class:: ReferralInfo

   Referral code information.

   .. attribute:: valid
      :type: bool

   .. attribute:: owner_address
      :type: str | None

   .. attribute:: is_active
      :type: bool | None


Aggregated models
-----------------

.. class:: AggregatedAsset

   An asset from the aggregated endpoints.

   .. attribute:: id
      :type: Id

   .. attribute:: symbol
      :type: str

   .. attribute:: name
      :type: str

.. class:: MarketSummary

   Market summary from the aggregated endpoints.

   .. attribute:: market_id
      :type: Id

   .. attribute:: data
      :type: dict

.. class:: MarketTicker

   Market ticker from the aggregated endpoints.

   .. attribute:: market_id
      :type: Id

   .. attribute:: data
      :type: dict
