> ## 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.

# Filling Offers: Full, Partial, and Slippage-Protected

Two entry points: `fill` for simple fills and `fillWithLimits` for full slippage control. Both support partial fills with cumulative pricing consistency.

## Prerequisites

Before calling `fill` or `fillWithLimits`, you must grant the venue contract an ERC-20 allowance over your quote tokens for at least the expected cost plus fee. Use the view function `quoteCostFor(offerId, assetUnits)` to preview the exact quote cost:

```solidity theme={null}
uint256 quoteCost = venue.quoteCostFor(offerId, assetUnits);
uint256 fee = quoteCost * offer.feeBps / 10000;
IERC20(quote).approve(VENUE_ADDRESS, quoteCost + fee);
```

## Function Signatures

The venue provides two entry points for filling offers:

### Simple Fill

```solidity theme={null}
function fill(uint256 offerId, uint256 assetUnits) external;
```

This convenience wrapper applies default slippage protection: `minAssetReceived = 1`, `minSellerQuoteReceived = 1` (or `0` for self-fills), and `maxBuyerQuoteDebited = type(uint256).max`. Use this when filling standard ERC-20 offers where slippage is not a concern.

### Fill with Explicit Limits

```solidity theme={null}
function fillWithLimits(
    uint256 offerId,
    uint256 assetUnits,
    uint256 minAssetReceived,
    uint256 minSellerQuoteReceived,
    uint256 maxBuyerQuoteDebited
) external;
```

## Parameters

<ParamField path="offerId" type="uint256" required>
  The sequential identifier of the offer you want to fill. Obtain this from the `OfferCreated` event emitted when the seller called `createOffer`, or from an off-chain order book.
</ParamField>

<ParamField path="assetUnits" type="uint256" required>
  The quantity of asset tokens you want to buy in this fill, in the asset's native base units. Must be greater than zero and must not exceed the offer's `assetRemaining`. Partial fills are fully supported and do not affect the offer's price.
</ParamField>

<ParamField path="minAssetReceived" type="uint256" required>
  The minimum net asset tokens that must arrive in your wallet (for spot offers) or be credited as claim tokens (for deferred offers). If fees or any other factor reduce the delivered amount below this threshold, the transaction reverts. Set this equal to `assetUnits` for standard ERC-20 tokens, or apply a fee tolerance for fee-on-transfer asset tokens.
</ParamField>

<ParamField path="minSellerQuoteReceived" type="uint256" required>
  The minimum net quote tokens the seller must receive after any transfer fees. If the seller would receive less than this value, the transaction reverts. Set to `0` for self-fills (buyer == seller), since the measured receipt of a self-transfer is zero.
</ParamField>

<ParamField path="maxBuyerQuoteDebited" type="uint256" required>
  The maximum total quote tokens debited from the buyer's wallet, including the base cost, protocol fee, and any transfer taxes. If the buyer's total outlay would exceed this ceiling, the transaction reverts. This is your primary slippage protection.
</ParamField>

## Settlement Behavior

### Spot Offers (`unlockTime == 0`)

When you fill a spot offer, settlement is atomic and immediate in the same transaction:

1. Your approved quote tokens are transferred from your wallet to the seller.
2. If a fee applies, an additional quote transfer goes from your wallet to the fee recipient.
3. The seller's escrowed asset tokens are transferred from the venue to your wallet.

### Deferred Forward Offers (`unlockTime > 0`)

When you fill a deferred offer, quote tokens flow to the seller immediately, but the underlying collateral stays locked in escrow until the unlock time:

1. Your approved quote tokens are transferred from your wallet to the seller (and fee recipient).
2. The venue mints ERC-20 claim tokens equal to `assetUnits` and transfers them to your wallet.
3. The series' `backing` counter increases by `assetUnits`.
4. After `block.timestamp >= unlockTime`, you call `redeem` to burn your claim tokens and receive the underlying collateral.

<Note>
  For deferred fills, the claim token contract is deployed lazily — the first fill of a novel `(asset, unlockTime)` series triggers deployment. A `SeriesCreated` event is emitted when this happens.
</Note>

## Partial Fill Behavior

`assetUnits` can be any value from `1` up to the offer's `assetRemaining`. The contract maintains cumulative fill tracking (`assetFilled` and `quotePaid`) so that every partial fill uses the same consistent pricing. You can call `fill` on the same `offerId` multiple times until the offer is fully filled, expired, or cancelled.

<Note>
  Fills are also blocked if `block.timestamp >= expiry` (the offer has expired) or if `block.timestamp >= unlockTime` for deferred offers (the offer has matured — no new fills after unlock).
</Note>

## Revert Conditions

The fill functions will revert if any of the following are true:

| Condition                                            | Error                |
| ---------------------------------------------------- | -------------------- |
| Entry is paused                                      | `EntryPaused()`      |
| Offer is no longer active                            | `OfferInactive()`    |
| `block.timestamp >= expiry`                          | `OfferExpired()`     |
| `block.timestamp >= unlockTime` (deferred only)      | `OfferMatured()`     |
| `allowedBuyer != 0` and `msg.sender != allowedBuyer` | `NotAllowedBuyer()`  |
| `assetUnits == 0` or `assetUnits > assetRemaining`   | `FillTooLarge()`     |
| Computed `quoteCost == 0` (dust amount)              | `ZeroCost()`         |
| Net seller proceeds \< `minSellerQuoteReceived`      | `SlippageExceeded()` |
| Buyer's total debit > `maxBuyerQuoteDebited`         | `SlippageExceeded()` |
| Net assets received \< `minAssetReceived`            | `SlippageExceeded()` |

## Example: Filling a Spot Offer

<CodeGroup>
  ```typescript ethers.js v6 — Simple Fill theme={null}
  import { ethers } from "ethers";

  const VENUE_ADDRESS = "0xYourVenueAddress";
  const fillAmount    = ethers.parseUnits("100000", 18);

  // Preview cost
  const quoteCost = await venue.quoteCostFor(offerId, fillAmount);
  const offer = await venue.offers(offerId);
  const fee = quoteCost * BigInt(offer.feeBps) / 10000n;
  const totalNeeded = quoteCost + fee;

  // Step 1: Approve quote token spend
  const quoteToken = new ethers.Contract(QUOTE_ADDRESS, ERC20_ABI, signer);
  await quoteToken.approve(VENUE_ADDRESS, totalNeeded);

  // Step 2: Simple fill (default slippage protection)
  const venue = new ethers.Contract(VENUE_ADDRESS, VENUE_ABI, signer);
  await venue.fill(offerId, fillAmount);
  ```

  ```typescript ethers.js v6 — With Slippage Limits theme={null}
  import { ethers } from "ethers";

  const fillAmount = ethers.parseUnits("100000", 18);
  const quoteCost  = await venue.quoteCostFor(offerId, fillAmount);
  const offer      = await venue.offers(offerId);
  const fee        = quoteCost * BigInt(offer.feeBps) / 10000n;

  await quoteToken.approve(VENUE_ADDRESS, quoteCost + fee);

  await venue.fillWithLimits(
    offerId,
    fillAmount,
    fillAmount,                        // minAssetReceived: exact for standard ERC-20
    quoteCost,                         // minSellerQuoteReceived: expect full quote
    quoteCost + fee                    // maxBuyerQuoteDebited: exact total outlay
  );
  ```

  ```typescript Fee-on-Transfer Tokens theme={null}
  import { ethers } from "ethers";

  const fillAmount = ethers.parseUnits("100000", 18);
  const quoteCost  = await venue.quoteCostFor(offerId, fillAmount);
  const offer      = await venue.offers(offerId);
  const fee        = quoteCost * BigInt(offer.feeBps) / 10000n;

  await quoteToken.approve(VENUE_ADDRESS, quoteCost + fee);

  await venue.fillWithLimits(
    offerId,
    fillAmount,
    fillAmount * 99n / 100n,           // minAssetReceived: allow 1% fee
    quoteCost  * 99n / 100n,           // minSellerQuoteReceived: allow 1% fee
    (quoteCost + fee) * 101n / 100n    // maxBuyerQuoteDebited: 1% tolerance
  );
  ```
</CodeGroup>

<Note>
  When filling offers that involve fee-on-transfer tokens, always set the slippage parameters with tolerance for the transfer fee. If you set them equal to the nominal values and a fee is deducted in transit, the fill will revert even though you have sufficient balance.
</Note>

<Tip>
  Use the `quoteCostFor(offerId, assetUnits)` view function to preview the exact cost before filling. This is especially useful for offers with cumulative partial fills, where the marginal cost depends on the current `assetFilled` and `quotePaid` state.
</Tip>
