Skip to main content

Callbacks

Callbacks provide a permissionless way to extend the functionality of the Axis system for a particular auction lot.

Configuration

The auction owner (seller) can specify a callbacks contract to be used for the auction by setting the address (address type) and parameters (bytes type) values in the RoutingParams parameter of the AuctionHouse.auction() function.

    struct RoutingParams {
Keycode auctionType;
address baseToken;
address quoteToken;
address curator;
ICallback callbacks;
bytes callbackData;
Keycode derivativeType;
bytes derivativeParams;
bool wrapDerivative;
}

function auction(
RoutingParams calldata routing_,
IAuction.AuctionParams calldata params_,
string calldata infoHash_
) external returns (uint96 lotId);

Validation of the contract will be performed at the time of auction creation, ensuring that an invalid callbacks contract is not supplied.

Features

The callbacks contract system is designed to provide the following features:

  • A callbacks contract can be configured for each auction lot, at the discretion of the seller.
  • Callbacks contracts are external to the Axis system, and can be developed by sellers or third-parties.
  • Each callbacks contract has configured "permissions" that define which callback functions (e.g. onBid) are called. The permissions determine the contract's address prefix, and validation is performed by the AuctionHouse by checking that prefix. This design is based heavily on the design UniswapV4 hooks.
  • A callbacks contract (instead of the seller) can supply payout (base tokens) to the AuctionHouse.
  • A callbacks contract (instead of the seller) can receive proceeds (quote tokens) from the AuctionHouse.
  • When the callbacks contract is expected to provide tokens, validation is performed that the required amount was transferred.
  • The callbacks contract is configured at deployment-time to be called at certain points during the lifecycle of the auction lot.
  • A callbacks contract can be designed to be single-use (for a particular auction lot) or multi-use (for multiple auction lots, potentially for different sellers).
  • Each callback function is passed arbitrary data (via the callbackData_ parameter) that can be used by the callback.

Lifecycle

Axis implements callback functions at the following points:

  • onCreate: when an auction is created
  • onCancel: when an auction is cancelled
  • onCurate: when a curator approves an auction
    • The callbacks contract will be called at the end of the AuctionHouse.curate() function.
    • If the callbacks contract is configured to supply base tokens and the auction lot is pre-funded, the onCurate() callback will be expected to transfer the required amount of base tokens.
  • Atomic Auctions
    • onPurchase: when a purchase is made from an Atomic Auction
      • The callbacks contract will be called at the end of the AtomicAuctionHouse.purchase() function. This provides a way for the seller to configure post-processing or even validation (such as an allowlist).
      • If the callbacks contract is configured to supply base tokens, the onPurchase() callback will be called and is expected to transfer the required amount of base tokens.
      • If the callbacks contract is configured to receive quote tokens, the quote tokens will be transferred to the callbacks contract, and then the onPurchase() callback will be called.
  • Batch Auctions
    • onBid: when a bid is submitted to a Batch Auction
      • The callbacks contract will be called at the end of the BatchAuctionHouse.bid() function. This provides a way for the seller to configure post-processing or even validation (such as an allowlist).
    • onSettle: when a Batch Auction is settled
      • The callbacks contract will be called at the end of the BatchAuctionHouse.settle() function. This provides a way for the seller to configure post-processing.
      • If the callbacks contract is configured to supply base tokens, any remaining base tokens will be transferred to the callbacks contract, and then the onSettle() callback will be called.
      • If the callbacks contract is configured to receive quote tokens, the quote tokens will be transferred to the callbacks contract, and then the onSettle() callback will be called.

Examples

Here are a few examples of callbacks contracts, some of which have already been implemented by the Axis team.

  • Allowlist: used to restrict participation in an auction, based on a predetermined set of criteria.
    • Merkle Allowlist: Utilises a merkle tree to allow specific addresses to participate in an auction.
    • Allocated Merkle Allowlist: Similar to the Merkle Allowlist, but each address has its own purchase limit.
    • Capped Merkle Allowlist: Similar to the Merkle Allowlist, but each address has the same purchase limit.
    • Token Allowlist: Enables participation in an auction to be gated by a minimum balance of an ERC20 or ERC721 token.
  • Minting payout tokens: the callbacks contract could mint an auction lot's base tokens on-demand, instead of the seller having to manage approvals and an ongoing balance.
  • Direct-to-Liquidity: a callbacks contract could receive the quote token proceeds from an auction lot, then deposit a combination of tokens into a liquidity pool.
    • Uniswap V2: Upon settlement, deposits the specified percentage of the auction lot's proceeds into a Uniswap V2 pool.
    • Uniswap V3: Upon settlement, deposits the specified percentage of the auction lot's proceeds into a Uniswap V3 pool.
    • Baseline: Upon settlement, deposits the specified percentage of the auction lot's proceeds into a Baseline pool.
  • Custom fees: a seller could use a callbacks contract to implement a custom fee arrangement.

Creating a Custom Callback

Axis provides a number of starting points for creating a custom callback:

Callback Address Prefix

A Callbacks contract must be located at an address that has a prefix matching the Callbacks.Permissions defined for that callback. If the contract inherits from BaseCallback, validation will be performed at deployment-time. Otherwise, the contract address will be validated when an auction lot is created.

When deploying a Callbacks contract, there are a number of steps to fulfilling the address prefix requirement:

  1. Determine the contract bytecode

    address contractArg1 = address(0x1);
    address contractArg2 = address(0x2);

    // Permissions are equivalent to 10011000 in binary, which is 98 in hex
    Callbacks.Permissions memory permissions = Callbacks.Permissions({
    onCreate: true,
    onCancel: false,
    onCurate: false,
    onPurchase: true,
    onBid: true,
    onSettle: false,
    receiveQuoteTokens: false,
    sendBaseTokens: false
    });
    bytes memory args = abi.encode(
    contractArg1,
    contractArg2,
    permissions
    );
    bytes memory bytecode = abi.encodePacked(type(CustomCallback).creationDate, args);
    vm.writeFile("bytecode.bin", vm.toString(bytecode));
  2. Generate a salt with the required prefix from the bytecode using the cast utility in foundry. The salt will be printed to the console and can then be copied.

    cast create2 -c -s 98 -i $(cat ./bytecode.bin)
  3. Deploy the contract using the salt

    address contractArg1 = address(0x1);
    address contractArg2 = address(0x2);
    Callbacks.Permissions memory permissions = Callbacks.Permissions({
    onCreate: true,
    onCancel: false,
    onCurate: false,
    onPurchase: true,
    onBid: true,
    onSettle: false,
    receiveQuoteTokens: false,
    sendBaseTokens: false
    });
    // Salt generated by cast
    bytes32 deploymentSalt = bytes32(0x11f6c4c27a20cd6e6925ddd316451417e2cdf68d28a8bd2be486f9709f89a301);

    vm.broadcast();
    CustomCallback callback = new CustomCallback{salt: deploymentSalt}(
    contractArg1, contractArg2, permissions
    );