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.
- The callbacks contract will be called at the end of the
- 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.
- The callbacks contract will be called at the end of the
- onPurchase: when a purchase is made from an Atomic Auction
- 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).
- The callbacks contract will be called at the end of the
- 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.
- The callbacks contract will be called at the end of the
- onBid: when a bid is submitted to a Batch Auction
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:
- ICallback interface
- BaseCallback abstract contract: The BaseCallback contract provides standard behaviours for callbacks, including permissions validation, caller authorisation and lot registration.
- The axis-periphery repository also contains callbacks contracts.
- Guide: 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:
-
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)); -
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)
-
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
);