Skip to main content
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:
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

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

function fillWithLimits(
    uint256 offerId,
    uint256 assetUnits,
    uint256 minAssetReceived,
    uint256 minSellerQuoteReceived,
    uint256 maxBuyerQuoteDebited
) external;

Parameters

offerId
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.
assetUnits
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.
minAssetReceived
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.
minSellerQuoteReceived
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.
maxBuyerQuoteDebited
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.

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

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

Revert Conditions

The fill functions will revert if any of the following are true:
ConditionError
Entry is pausedEntryPaused()
Offer is no longer activeOfferInactive()
block.timestamp >= expiryOfferExpired()
block.timestamp >= unlockTime (deferred only)OfferMatured()
allowedBuyer != 0 and msg.sender != allowedBuyerNotAllowedBuyer()
assetUnits == 0 or assetUnits > assetRemainingFillTooLarge()
Computed quoteCost == 0 (dust amount)ZeroCost()
Net seller proceeds < minSellerQuoteReceivedSlippageExceeded()
Buyer’s total debit > maxBuyerQuoteDebitedSlippageExceeded()
Net assets received < minAssetReceivedSlippageExceeded()

Example: Filling a Spot Offer

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);
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
);
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
);
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.
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.