Using Derivatives
For a primer on derivatives, read the Concepts - Derivatives page.
This guide will provide an example of how to interact with an auction that utilises an Axis derivative module. Any payout (curator fees, purchases or winning bids) will use the specified derivative module to mint derivative tokens to the recipient. In turn, when the conditions are fulfilled, the recipient will be able to redeem the derivative token for the underlying payout (the auction's base token).
In this example, the LinearVesting
derivative module will be used.
To simplify the example, an atomic auction will be used. However, derivative modules work with both atomic and batch auctions.
Auction Creation
Setup
First, we define a variable for the AuctionHouse. The address used here is a testnet address. However, the current addresses for both testnet and production deployments can be obtained from the Contract Addresses page.
// Define the deployed AuctionHouse
IAtomicAuctionHouse auctionHouse = IAtomicAuctionHouse(_atomicAuctionHouse);
The next step is to set up the tokens that will be used in the auction. In this example, mock ERC20
contracts are deployed to make minting simple.
// Define the tokens used in the auction
MockERC20 quoteToken = _getQuoteToken();
MockERC20 baseToken = _getBaseToken();
Inputs
In this section, we prepare the inputs to the auction create function.
Derivative Parameters
The parameters for the derivative module will be included in the routing parameters at the time of auction creation. The derivative parameters therefore need to be prepared first.
// Prepare the parameters for the linear vesting derivative module
uint48 vestingStart = uint48(block.timestamp + 7 days);
uint48 vestingExpiry = vestingStart + 90 days;
ILinearVesting.VestingParams memory vestingParams =
ILinearVesting.VestingParams({start: vestingStart, expiry: vestingExpiry});
These parameters will result in the auction payout to the curator (if applicable) and buyers:
- Commencing vesting 7 days from the time of auction creation
- Linearly vesting over 90 days from the vesting start time
Routing Parameters
The Routing parameters are then created. In this code block, the derivativeType
and derivativeParams
are specified, unlike in the Create Auction Guide.
// Define the auction routing parameters
IAuctionHouse.RoutingParams memory routingParams = IAuctionHouse.RoutingParams({
auctionType: toKeycode("EMPA"),
baseToken: address(baseToken),
quoteToken: address(quoteToken),
curator: address(0), // Optional
callbacks: ICallback(address(0)), // Optional
callbackData: abi.encode(""), // Optional
derivativeType: toKeycode("LIV"), // Linear vesting
derivativeParams: abi.encode(vestingParams), // Linear vesting parameters
wrapDerivative: true // true to wrap derivative tokens in an ERC20
});
Note that once an auction is created, the derivative configuration cannot be modified.
Auction Parameters
The configuration of the FixedPriceSale
auction module is then created.
// Define the auction module parameters
IFixedPriceSale.AuctionDataParams memory fpsParams;
{
uint256 fpsPrice = 1e18;
uint24 maxPayoutPercent = 10_000; // 10000 = 10%
fpsParams = IFixedPriceSale.AuctionDataParams({
price: fpsPrice,
maxPayoutPercent: maxPayoutPercent
});
}
The FixedPriceSale configuration is then encoded in the generic auction parameters:
// Define the auction parameters
IAuction.AuctionParams memory auctionParams;
uint256 capacity;
{
uint48 start = uint48(block.timestamp + 1 days);
uint48 duration = uint48(3 days);
bool capacityInQuote = false;
capacity = 10e18;
auctionParams = IAuction.AuctionParams({
start: start,
duration: duration,
capacityInQuote: capacityInQuote,
capacity: capacity,
implParams: abi.encode(fpsParams)
});
}
Token Funding
While the atomic auction is not pre-funded, the base tokens need to be available for spending by the AtomicAuctionHouse.
// Mint base tokens to the seller
baseToken.mint(_SELLER, capacity);
// The AuctionHouse will pull base tokens from the seller upon purchase
// so approve the auction capacity
vm.prank(_SELLER);
baseToken.approve(_atomicAuctionHouse, capacity);
Contract Call
Lastly, we create the auction. The caller of the contract is considered the seller, so we use vm.prank
to assume the role of the seller in the example script.
// Define the IPFS hash for additional information
string memory ipfsHash = "";
// Create the auction
vm.prank(_SELLER);
uint96 lotId = auctionHouse.auction(routingParams, auctionParams, ipfsHash);
console2.log("Created auction with lot ID:", lotId);
Purchase
In order to obtain the derivative token as a payout from the atomic auction, a purchase must be performed. This is identical to what is contained in the Purchase Guide.
However, as the auction is configured with a derivative module, the purchase will result in the derivative token being minted to the buyer, instead of the auction's base token. Due to wrapDerivative
being true
, the derivative will take the form of an ERC20.
Derivative Redemption
The next stage is to redeem the derivative token for the underlying token (the auction base token). The example script will attempt to redeem the maximum amount of base tokens that can be redeemed.
Setup
The sample script isolates the code for redemption into its own function, so a variable must be created for the AtomicAuctionHouse.
IAtomicAuctionHouse auctionHouse = IAtomicAuctionHouse(_atomicAuctionHouse);
We obtain from the LOT_ID
environment variable the lot ID that identifies each auction lot. The lot ID is a uint96
number, so should be checked before being downcast.
// Obtain the lot from the environment variable
uint256 lotIdRaw = vm.envUint("LOT_ID");
if (lotIdRaw > type(uint96).max) {
revert("LOT_ID must be less than uint96 max");
}
uint96 lotId = uint96(lotIdRaw);
Inputs
We define a variable for the purchase recipient, which is the same as what was defined in the purchase step.
// Prepare inputs
address recipient = address(0x10);
As we will be accessing the derivative module, the address should be determined and cached.
// Obtain the address of the derivative module
IDerivative derivativeModule = auctionHouse.getDerivativeModuleForId(lotId);
The unique tokenId
is required in order to determine the amount that can be redeemed. The unique value is determined based on the underlying token (the auction's base token) and the vesting parameters.
// Determine the token id
(, address baseToken,,,,,,, bytes memory derivativeParams) = auctionHouse.lotRouting(lotId);
uint256 tokenId = derivativeModule.computeId(baseToken, derivativeParams);
Contract Call
For logging purposes, we then call the redeemable()
view function to determine how much can be redeemed at the current timestamp.
// Determine how much can be redeemed
uint256 redeemable = derivativeModule.redeemable(recipient, tokenId);
console2.log("Redeemable amount:", redeemable);
Lastly, the redeemMax()
function is called to perform the redemption.
// Redeem the maximum amount
// Must be run as the recipient
vm.prank(recipient);
derivativeModule.redeemMax(tokenId);
console2.log("Redeemed maximum amount");
This will result in:
redeemable
quantity of the derivative token being burned by the derivative moduleredeemable
quantity of the auction's base token being transferred to the recipient
Source Code
The source code for the guide is located in the using-derivatives.s.sol file.