> For the complete documentation index, see [llms.txt](https://docs.anchored.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.anchored.finance/trading-api/market-data/stock-oracle.md).

# Stock Oracle

## Contract Overview

`StockOracle` stores one latest price payload per supported `bytes32 priceId`. The same oracle proxy can serve all stock symbols on a chain; it does not deploy one feed contract per symbol.

The oracle supports:

* single and batch price reads
* freshness-checked reads
* session-aware payloads
* bid and ask values alongside the main oracle price

All price fields use `8` decimals.

## Deployment and Backend Enablement

The integration draft records the intended mainnet proxy address as the same deterministic address on Ethereum, Monad, and Base:

| Network  | Chain ID | StockOracle proxy                            |
| -------- | -------: | -------------------------------------------- |
| Ethereum |      `1` | `0x037848af338c38e1e0ab722be80bf4c2e612a1f7` |
| Monad    |    `143` | `0x037848af338c38e1e0ab722be80bf4c2e612a1f7` |
| Base     |   `8453` | `0x037848af338c38e1e0ab722be80bf4c2e612a1f7` |

The draft also records implementation address `0x8745912828473e9bef9d843ce8629bee3dcb9fec` for reference only. Consumers should integrate with the proxy address, not the implementation address.

## Price IDs

Each supported asset is identified by `bytes32 priceId`.

The standard derived ID is:

```
priceId = keccak256(uppercase(trim(symbol)))
```

For example, the backend derives `AAPL` from the uppercase symbol bytes. Deployment scripts may also accept an explicit `bytes32 priceId`; if omitted, they use the same uppercase-symbol derivation.

Before reading a price, consumers can check:

```solidity
function isSupportedPriceId(bytes32 priceId) external view returns (bool);
function hasPrice(bytes32 priceId) external view returns (bool);
function description(bytes32 priceId) external view returns (string memory);
```

`isSupportedPriceId=true` means the ID is registered. `hasPrice=true` means at least one price has been published.

## Price Payload

Oracle reads return:

```solidity
enum Session {
    UNKNOWN,
    PRE_MARKET,
    REGULAR,
    POST_MARKET,
    OVERNIGHT,
    CLOSED
}

struct PricePayload {
    int128 price;
    int128 bestBid;
    int128 bestAsk;
    uint64 feedUpdateTimestamp;
    uint64 publishedAt;
    uint80 roundId;
    Session session;
}
```

| Field                 | Meaning                                                                 |
| --------------------- | ----------------------------------------------------------------------- |
| `price`               | Main oracle price, 8 decimals. Backend currently uses bid/ask midpoint. |
| `bestBid`             | Best bid price, 8 decimals.                                             |
| `bestAsk`             | Best ask price, 8 decimals.                                             |
| `feedUpdateTimestamp` | Source quote timestamp, in Unix seconds. Use this for freshness.        |
| `publishedAt`         | On-chain publish timestamp for this oracle round, in Unix seconds.      |
| `roundId`             | Monotonic oracle update round for this `priceId`.                       |
| `session`             | Market session associated with the source quote.                        |

Consumers should use `feedUpdateTimestamp` for staleness checks. `publishedAt` only says when the payload was written on-chain.

## Read APIs

Single-asset reads:

```solidity
function latestPrice(bytes32 priceId)
    external
    view
    returns (PricePayload memory);

function latestPriceNoOlderThan(bytes32 priceId, uint64 maxAge)
    external
    view
    returns (PricePayload memory);
```

Batch reads:

```solidity
function latestPrices(bytes32[] calldata priceIds)
    external
    view
    returns (PricePayload[] memory);

function latestPricesNoOlderThan(bytes32[] calldata priceIds, uint64 maxAge)
    external
    view
    returns (PricePayload[] memory);
```

Prefer `latestPriceNoOlderThan` or `latestPricesNoOlderThan` when the consuming contract must fail closed on stale source data. `maxAge` is denominated in seconds.

Current backend feeder comments note that the source quote can be about 15 minutes delayed. With the default 10-minute heartbeat, a consumer using `latestPriceNoOlderThan(maxAge)` should choose a `maxAge` that accounts for feed delay plus heartbeat and integration-specific risk tolerance; 25 minutes / 1500 seconds is a practical lower-bound example before adding any product-specific buffer.

## Read-Side Reverts

| Error               | Meaning                                                      |
| ------------------- | ------------------------------------------------------------ |
| `PriceIdNotFound`   | The `priceId` is not registered.                             |
| `PriceNotAvailable` | The `priceId` is registered but no price has been published. |
| `PriceTooOld`       | `block.timestamp - feedUpdateTimestamp > maxAge`.            |

## Write-Side Constraints

The backend feeder and deployment scripts are expected to satisfy these constraints before publishing:

| Constraint       | Contract behavior                                                                                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Registering IDs  | `registerPriceId` and `batchRegisterPriceIds` require `DEFAULT_ADMIN_ROLE`.                                                                                          |
| Updating prices  | `updatePrice` and `batchUpdatePrice` require `OPERATOR_ROLE`.                                                                                                        |
| Price validity   | `price`, `bestBid`, and `bestAsk` must be positive and fit into `int128`.                                                                                            |
| Spread validity  | `bestBid` cannot exceed `bestAsk`.                                                                                                                                   |
| Source timestamp | `feedUpdateTimestamp` cannot be in the future and cannot roll back below the current on-chain value.                                                                 |
| Same timestamp   | If `feedUpdateTimestamp` equals the current on-chain timestamp, the update is allowed only for a `CLOSED` carried-forward update with unchanged price, bid, and ask. |

These details are mainly useful for debugging feeder reverts and operational incidents. Normal readers do not need write permissions.

## Session Handling

Consumers should treat `session` as part of the risk model, not just display metadata.

Regular-session prices are generally the most liquid. Pre-market, post-market, and overnight prices may have wider spreads or lower coverage. `CLOSED` means the oracle carried forward the latest on-chain price into a closed-market state; it should not be treated the same as an active quote unless the integration explicitly supports that behavior.

For lending or risk-sensitive integrations, define per-asset and per-session controls for:

* maximum accepted age
* accepted spread or spread-derived haircut
* whether `CLOSED` prices are allowed
* fallback behavior when `PriceIdNotFound`, `PriceNotAvailable`, or `PriceTooOld` occurs
