NAV Issuing and Redeeming

What is the NAVIssuanceModule:

Module that enables issuance and redemption with any valid ERC20 token or ETH if allowed by the manager. The issuer receives a proportional amount of SetTokens on issuance or ERC20 token on redemption based on the calculated net asset value using oracle prices. Manager is able to enforce a premium / discount on issuance / redemption to avoid arbitrage and front running when relying on oracle prices. Managers can charge a fee (denominated in reserve asset).

The NAVIssuanceModule differs from the BasicIssuanceModule in that the end user only needs to bring a single asset versus needing to replicate each position in the SetToken. This dramatically saves gas costs for SetTokens that are more actively traded and participate in yield farming and margin trading.

NAV Issuance obtains the NAV via the SetValuer contract which utilizes price oracles in the Set V2 system. View the supported price oracles here.

Initializing the NAVIssuanceModule:

All modules need to be added to the SetToken as the first step. Learn how to add a module to your SetToken in this section.

Once you have added the NAVIssuanceModule to the SetToken, you must initialize the SetToken on the NAVIssuanceModule:

/**
* SET MANAGER ONLY. Initializes this module to the SetToken with hooks, allowed reserve assets,
* fees and issuance premium. Only callable by the SetToken's manager. Hook addresses are optional.
* Address(0) means that no hook will be called.
*
* @param _setToken Instance of the SetToken to issue
* @param _navIssuanceSettings NAVIssuanceSettings struct defining parameters
*/
function initialize(
ISetToken _setToken,
NAVIssuanceSettings memory _navIssuanceSettings
)

The NAVIssuanceSettings struct is constructed as followed:

struct NAVIssuanceSettings {
INAVIssuanceHook managerIssuanceHook; // Issuance hook configurations
INAVIssuanceHook managerRedemptionHook; // Redemption hook configurations
address[] reserveAssets; // Allowed reserve assets - Must have a price enabled with the price oracle
address feeRecipient; // Manager fee recipient
uint256[2] managerFees; // Manager fees. 0 index is issue and 1 index is redeem fee (0.01% = 1e14, 1% = 1e16)
uint256 maxManagerFee; // Maximum fee manager is allowed to set for issue and redeem
uint256 premiumPercentage; // Premium percentage (0.01% = 1e14, 1% = 1e16). This premium is a buffer around oracle
// prices paid by user to the SetToken, which prevents arbitrage and oracle front running
uint256 maxPremiumPercentage; // Maximum premium percentage manager is allowed to set (configured by manager)
uint256 minSetTokenSupply; // Minimum SetToken supply required for issuance and redemption
// to prevent dramatic inflationary changes to the SetToken's position multiplier
}

The managerIssuanceHook and managerRedemptionHook are smart contracts that can contain any custom logic you write prior to issuance and redemption. For example, the manager issuance contract can include whitelisting functionality, issuance limits and any other custom logic for operating your Set. Input the zero address if you do not have a manager issuance contract.

The reserveAssets are the list of ERC20 tokens that you allow users to issue / redeem with. E.g. USDC, WETH, WBTC. These assets must be supported by the Set PriceOracle contract for NAV issuance to work.

All managerFees are paid to the feeRecipient address. Additionally, you can set a maxManagerFee that is allowed on the SetToken. The premiumPercentage (0.01% = 1e14, 1% = 1e16) is a buffer around oracle price which prevents oracle front running and arbitrage. All premiums are paid to existing SetToken holders and not collected by the manager or protocol.

The minSetTokenSupply is to prevent dramatic inflationary changes to the SetToken's position and is the minimum quantity required for NAV issuance and redemption. For example if the SetToken currently contains 1 in total supply and a user issues 1000 of supply, the SetToken position multiplier will result in an inflationary change that affects precision.

Issuing Sets:

In order to issue Sets you must call issue or issueWithEther (if issuing with ETH) on the NAVIssuanceModule, the interface for issue is as follows:

/**
* Deposits the allowed reserve asset into the SetToken and mints the appropriate % of Net Asset Value of the SetToken
* to the specified _to address.
*
* @param _setToken Instance of the SetToken contract
* @param _reserveAsset Address of the reserve asset to issue with
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
* @param _minSetTokenReceiveQuantity Min quantity of SetToken to receive after issuance
* @param _to Address to mint SetToken to
*/
function issue(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity,
uint256 _minSetTokenReceiveQuantity,
address _to
)
/**
* Wraps ETH and deposits WETH if allowed into the SetToken and mints the appropriate % of Net Asset Value of the SetToken
* to the specified _to address.
*
* @param _setToken Instance of the SetToken contract
* @param _minSetTokenReceiveQuantity Min quantity of SetToken to receive after issuance
* @param _to Address to mint SetToken to
*/
function issueWithEther(
ISetToken _setToken,
uint256 _minSetTokenReceiveQuantity,
address _to
)

The reserveAssetQuantity is the quantity denominated in the reserve asset, not the SetToken. The minSetTokenReceiveQuantity parameter prevents oracle changes that adversely affect issuance.

All ERC20 components must be approved to the NAVIssuanceModule contract with appropriate allowance for transfer. This is not required for issueWithEther.

In order to figure out the correct token amounts needed for issuance you can use the following helper functions:

/**
* Checks if issue is valid
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
*
* @return bool Returns true if issue is valid
*/
function isIssueValid(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)
/**
* Get the expected SetTokens minted to recipient on issuance
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
*
* @return uint256 Expected SetTokens to be minted to recipient
*/
function getExpectedSetTokenIssueQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)

Redeeming Sets:

The logic is very similar just in reverse. The interface for redeem and redeemIntoEther is as follows:

/**
* Redeems a SetToken into a valid reserve asset representing the appropriate % of Net Asset Value of the SetToken
* to the specified _to address. Only valid if there are available reserve units on the SetToken.
*
* @param _setToken Instance of the SetToken contract
* @param _reserveAsset Address of the reserve asset to redeem with
* @param _setTokenQuantity Quantity of SetTokens to redeem
* @param _minReserveReceiveQuantity Min quantity of reserve asset to receive
* @param _to Address to redeem reserve asset to
*/
function redeem(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity,
uint256 _minReserveReceiveQuantity,
address _to
)
/**
* Redeems a SetToken into Ether (if WETH is valid) representing the appropriate % of Net Asset Value of the SetToken
* to the specified _to address. Only valid if there are available WETH units on the SetToken.
*
* @param _setToken Instance of the SetToken contract
* @param _setTokenQuantity Quantity of SetTokens to redeem
* @param _minReserveReceiveQuantity Min quantity of reserve asset to receive
* @param _to Address to redeem reserve asset to
*/
function redeemIntoEther(
ISetToken _setToken,
uint256 _setTokenQuantity,
uint256 _minReserveReceiveQuantity,
address payable _to
)

You specify the amount of Sets you want to redeem and then those Sets are burned and the components are transferred to the calling address. The minReserveReceiveQuantity prevents faulty oracles that affect redemption.

In order to figure out the correct token amounts needed for redemption you can use the following helper functions:

/**
* Checks if redeem is valid
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _setTokenQuantity Quantity of SetTokens to redeem
*
* @return bool Returns true if redeem is valid
*/
function isRedeemValid(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
/**
* Get the expected reserve asset to be redeemed
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _setTokenQuantity Quantity of SetTokens to redeem
*
* @return uint256 Expected reserve asset quantity redeemed
*/
function getExpectedReserveRedeemQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)