> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canopy.deal/llms.txt
> Use this file to discover all available pages before exploring further.

# Query Canopy Markets, Offers, and Series State Onchain

Canopy exposes every aspect of its market state directly on-chain, meaning you can query offers, series metadata, and complete event history from any standard EVM JSON-RPC node without relying on a proprietary API or off-chain data provider. Every offer creation, fill, cancellation, and claim issuance is recorded either as contract storage or as an indexed event log, giving you a fully auditable and permissionless view of market activity. This guide walks you through the key read patterns — from fetching a single offer to reconstructing a live order book — using `ethers.js` v6.

## Reading a Specific Offer

If you already know an offer ID, you can fetch its current state directly from contract storage with a single `offers` call. The returned struct contains the seller address, total assets remaining, price, expiry, and fill history.

```typescript theme={null}
const offer = await venue.offers(offerId);
console.log(offer.seller, offer.assetRemaining, offer.pricePerUnit, offer.expiry);
```

<Note>
  Offer IDs are sequential unsigned integers starting from `0`. You can enumerate all offers by iterating from `0` to `nextOfferId - 1`, or by reading the `nextOfferId` public variable to know the total count.
</Note>

## Reading All Offers for a Trading Pair

To discover all offers for a given asset without knowing individual offer IDs, filter the `OfferCreated` event log by asset address. This returns every offer ever created for that asset. Cross-reference the results against `OfferFilled` and `OfferCancelled` events to determine which offers are still active.

```typescript theme={null}
const filter = venue.filters.OfferCreated(null, null, ASSET_ADDRESS);
const events = await venue.queryFilter(filter, 0, "latest");
// Then cross-reference with OfferFilled and OfferCancelled to get current state
```

<Tip>
  On public RPC endpoints, querying from block `0` to `"latest"` in a single call can time out or be rate-limited. Use a sliding block range window — for example, chunks of 10,000 blocks — to paginate through the full history reliably.
</Tip>

## Getting Series Info for Deferred Markets

Canopy forward series are identified by a deterministic series ID derived from the asset address and unlock time. Compute the series ID using the venue's helper function or locally, then read the series data:

```typescript theme={null}
// Using the venue's helper function
const seriesId = await venue.seriesIdFor(assetAddress, unlockTime);

// Or compute locally (must match abi.encode, not abi.encodePacked)
const seriesId = ethers.keccak256(
  ethers.AbiCoder.defaultAbiCoder().encode(
    ["address", "uint64"],
    [assetAddress, unlockTime]
  )
);

const seriesData = await venue.series(seriesId);
console.log(seriesData.claimToken, seriesData.backing, seriesData.unlockTime);
```

<Note>
  The series key is `(asset, unlockTime)` — the quote token is **not** part of the series identifier. All deferred fills for the same asset with the same unlock time share a single claim token, regardless of which quote token was used.
</Note>

## Checking Claim Token Balances

Each series deploys its own ERC-20 claim token at the address stored in `seriesData.claimToken`. Once you have that address, interact with it using any standard ERC-20 ABI — no Canopy-specific interface is required for balance and transfer reads.

```typescript theme={null}
const claimToken = new ethers.Contract(seriesData.claimToken, ERC20_ABI, provider);
const balance = await claimToken.balanceOf(userAddress);
```

## Previewing Fill Cost

The venue provides a view function to compute the exact quote cost for a prospective fill without sending a transaction:

```typescript theme={null}
const quoteCost = await venue.quoteCostFor(offerId, assetUnits);
const offer = await venue.offers(offerId);
const fee = quoteCost * BigInt(offer.feeBps) / 10000n;
console.log(`Base cost: ${quoteCost}, Fee: ${fee}, Total: ${quoteCost + fee}`);
```

## Building a Live Order Book

Reconstructing a full order book for a trading pair requires combining event history with simple state-machine logic. Follow this pattern to build an accurate, always-current view of available liquidity:

<Steps>
  <Step title="Index OfferCreated from the deployment block">
    Query all `OfferCreated` events from the contract's deployment block to the current head. Store each event's `offerId`, `asset`, `quote`, `escrowedAssetAmount`, `pricePerUnit`, `expiry`, `unlockTime`, and `allowedBuyer` fields. Set `assetFilled = 0` and `status = OPEN` for every new record.
  </Step>

  <Step title="Apply OfferFilled deltas">
    For each `OfferFilled` event, locate the matching offer by `offerId` and increment `assetFilled` by the event's `assetUnits`. When `assetFilled` equals the original escrowed amount, mark the offer `FILLED`.
  </Step>

  <Step title="Remove cancelled offers">
    For each `OfferCancelled` event, mark the corresponding offer `CANCELLED`. Cancelled offers should be excluded from any live depth calculation.
  </Step>

  <Step title="Filter by asset and quote">
    To render a pair's order book, filter your local state to offers where `asset == targetAsset` and `quote == targetQuote` and `status == OPEN` and `expiry > now`. Compute `assetRemaining = escrowedAssetAmount - assetFilled` for each, then sort by price to produce bid and ask ladders.
  </Step>
</Steps>

<Note>
  All the information needed for full state reconstruction is encoded in the events themselves. You do not need to make additional contract storage reads during initial sync — the events are self-contained.
</Note>
