Skip to main content
The core contract — OTCVenue — is a singleton deployed on Robinhood Chain (Chain ID 4663, Arbitrum Orbit). A companion ClaimToken (one per series) is deployed lazily by the venue on first deferred fill.

Trade Types

Spot trades (unlockTime == 0) execute as atomic swaps at fill time. When a buyer calls fill on an active spot offer, the contract simultaneously transfers the agreed asset amount from escrow to the buyer and routes the quote token payment directly to the seller. No claim tokens are issued, and no unlock time check is required. The entire exchange settles in a single transaction. Deferred (forward) trades (unlockTime > 0) use an escrow-and-claim model. The seller deposits collateral upfront; when a buyer fills the offer, the contract mints ERC-20 claim tokens to the buyer rather than immediately releasing the collateral. Quote token payment flows directly to the seller at fill time — the protocol never custodies quote tokens. After the unlockTime timestamp passes, any claim token holder can call the redeem function to burn their tokens and receive the underlying collateral on a strict 1:1 basis. This design lets buyers trade their future claim freely as a standard ERC-20 in the interim.

Series Model

Every deferred offer belongs to a series defined by the pair (asset, unlockTime). When the first deferred offer is filled for a unique pair, the OTCVenue automatically deploys a new ERC-20 ClaimToken contract for that series. All subsequent fills for offers sharing the same (asset, unlockTime) mint claim tokens from that same contract, creating a single fungible instrument backed by pooled escrow. Each series maintains a backing counter that tracks the total collateral currently backing outstanding claim tokens. Because series are isolated, a liquidity or accounting event in one series can never affect another. The series identifier (seriesId) is derived deterministically:
bytes32 seriesId = keccak256(abi.encode(asset, unlockTime));
The series key is (asset, unlockTime)not (asset, quoteToken, unlockTime). This means all deferred offers for the same asset with the same unlock time share a single claim token, regardless of which quote token is used for payment. This is what makes claim tokens fungible across different offers.

Offer Lifecycle

Every offer has an expiry timestamp (the deadline after which no fills are accepted) and an optional unlock time (zero for spot, non-zero for deferred). Key constraints at creation:
  • expiry must be in the future (expiry > block.timestamp)
  • For deferred offers, unlockTime must also be in the future and expiry <= unlockTime
This means a deferred offer always stops accepting fills before its collateral unlocks for redemption.

Key Design Invariants

The Canopy protocol is built around invariants that hold for every offer and series at all times:
  1. Collateral-first creation. Offers can only be created with collateral already deposited in the same transaction. The escrow transfer and offer registration happen atomically — there is no promise-based path.
  2. Series solvency. For every series, claimToken.totalSupply() == series.backing, and the venue’s balance of the asset covers all backing and all active offer escrow. This is enforced by delta-measured deposits, which correctly handle fee-on-transfer tokens.
  3. No double spend of escrow. assetRemaining is decremented before any transfer or mint (checks-effects-interactions). Cancel zeroes it before returning collateral.
  4. Quote tokens are never custodied. When a buyer fills an offer, quote tokens are transferred directly to the seller and fee recipient in the same transaction. The venue holds no quote token balance at any point.
  5. Redemption is permissionless and unpausable. Once block.timestamp >= unlockTime, any address holding claim tokens can redeem them. No whitelist, no operator approval, and no seller action is required. The admin has no power over redemption or escrow.
  6. Decimal-correct pricing. Quote cost uses cumulative full-precision mulDiv, so split fills settle to the same aggregate price as a single fill.
OTCVenue inherits ReentrancyGuard and Ownable2Step. ClaimToken restricts mint and burn to the venue (onlyVenue) but is otherwise a standard ERC-20. Integrators can use any EVM library (ethers.js, viem, web3.py).

Next Steps

Data Structures

Explore the Offer and Series structs, field semantics, and on-chain storage layout.

Events Reference

Browse every emitted event with parameter docs for indexers and frontends.

Creating Offers

Step-by-step guide to creating spot and deferred offers.

Security Model

Understand the trust assumptions, access controls, and invariant proofs.