Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/reserve-protocol/reserve-index-dtf/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Folios support permissionless minting and redemption:
  • Minting: Deposit basket assets to receive Folio shares
  • Redeeming: Burn Folio shares to receive basket assets
Both operations are proportional to the current basket composition and include fee mechanisms.
Minting and redemption are disabled if the Folio is deprecated. Use folio.isDeprecated() to check status.

Understanding Share Accounting

Total Supply

The total supply includes:
  1. Circulating shares (held by users)
  2. Pending DAO fee shares (not yet distributed)
  3. Pending fee recipient shares (not yet distributed)
uint256 totalShares = folio.totalSupply();
// Includes all pending fees

Asset Composition

Shares represent proportional ownership of all basket assets:
(address[] memory assets, uint256[] memory amounts) = folio.totalAssets();

// Example output:
// assets = [USDC, WETH, DAI]
// amounts = [1,000,000e6, 100e18, 500,000e18]
Asset composition may be unreliable during trusted fill execution. Check folio.stateChangeActive() before relying on asset data.

Minting Shares

Minting requires depositing all basket assets proportionally.
1

Query Required Assets

Calculate how many tokens you need for a desired share amount:
uint256 desiredShares = 100e18; // Want 100 shares

(address[] memory assets, uint256[] memory amounts) = folio.toAssets(
    desiredShares,
    Math.Rounding.Ceil // Round up to ensure sufficient amounts
);

// amounts = [USDC: 1000e6, WETH: 1e18, DAI: 1000e18]
Always use Math.Rounding.Ceil when calculating required deposits to avoid “insufficient amount” errors.
2

Calculate Fees

Understand the fee structure:
uint256 mintFee = folio.mintFee(); // e.g., 0.01e18 = 1%

// Shares received = desiredShares * (1 - mintFee) * (1 - daoSplit)
// Remaining goes to: DAO fee recipient + Folio fee recipients
Fee Distribution:
  • DAO takes minimum 15bps from all fees
  • Remaining goes to fee recipients (if configured)
  • If no fee recipients, DAO gets everything
3

Approve Token Transfers

Grant allowances for all basket assets:
for (uint256 i = 0; i < assets.length; i++) {
    IERC20(assets[i]).approve(address(folio), amounts[i]);
}
You can also use permit() for gasless approvals if tokens support ERC-2612.
4

Execute Mint

uint256 minSharesOut = 99e18; // Minimum shares after fees (slippage protection)

(address[] memory returnedAssets, uint256[] memory returnedAmounts) = folio.mint(
    desiredShares,
    msg.sender,      // Recipient of shares
    minSharesOut     // Revert if you receive less than this
);

// You now have shares in your wallet
uint256 yourBalance = folio.balanceOf(msg.sender);
After minting:
  • Your share balance increased
  • Your token balances decreased by returnedAmounts
  • Pending fee shares increased (distributed later)
5

Set Slippage Protection (Optional)

Use allowances to limit token spend in case of state changes:
// Instead of approving exact amounts, approve maximum acceptable amounts
uint256 maxUSDC = amounts[0] * 1.01e18 / 1e18; // 1% slippage
IERC20(usdc).approve(address(folio), maxUSDC);

Redeeming Shares

Redemption burns shares and returns proportional assets.
1

Calculate Redemption Output

uint256 sharesToRedeem = 50e18; // Redeem 50 shares

(address[] memory assets, uint256[] memory amounts) = folio.toAssets(
    sharesToRedeem,
    Math.Rounding.Floor // Round down (you receive slightly less)
);

// amounts = [USDC: 500e6, WETH: 0.5e18, DAI: 500e18]
2

Set Minimum Amounts

Protect against unfavorable state changes:
// Set minimum acceptable amounts (99% of expected)
uint256[] memory minAmountsOut = new uint256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
    minAmountsOut[i] = amounts[i] * 99 / 100;
}
3

Execute Redemption

uint256[] memory actualAmounts = folio.redeem(
    sharesToRedeem,
    msg.sender,      // Recipient of assets
    assets,          // Must match basket exactly
    minAmountsOut    // Minimum amounts to receive
);

// You now have assets in your wallet
// Shares were burned from your balance
The assets parameter must match the current basket exactly (same order, same tokens). Otherwise, the transaction will revert.

Fee Distribution

Fees accumulate as pending shares and are distributed separately.

Manual Distribution

// Anyone can call this to distribute accumulated fees
folio.distributeFees();
This will:
  1. Calculate all pending fee shares from TVL fees and mint fees
  2. Mint shares to fee recipients according to their portions
  3. Mint remaining shares to DAO fee recipient
  4. Reset pending fee counters

Automatic Distribution

Fees are automatically distributed (via poke()) before:
  • Minting
  • Redemption
  • Fee configuration changes
  • Any state-changing operation
// Happens automatically, but can be called directly:
folio.poke();
The poke() function updates pending fees based on time elapsed since the last update (in full days only).

Fee Types

TVL Fee (Time-Based)

Annual demurrage fee on assets under management
uint256 tvlFee = folio.tvlFee(); // D18{1/s} fee per second

// Convert to annual percentage:
// annualFee = tvlFee * 365 days / 1e18
// Example: 317097919837645 per second ≈ 1% annual
Calculation:
  • Accrues every full day (24-hour periods)
  • Causes supply inflation (new shares minted to fee recipients)
  • Max 10% annually

Mint Fee (One-Time)

Percentage fee charged on minting
uint256 mintFee = folio.mintFee(); // D18{1} e.g., 0.01e18 = 1%

// On a 100 share mint with 1% fee:
// - User receives: ~99 shares
// - Fees: ~1 share (split between DAO and fee recipients)
Characteristics:
  • One-time charge when minting
  • Does NOT cause supply inflation (taken from minted shares)
  • Max 5%

Folio Self Fee

Fraction of fee-recipient shares that are burned
uint256 folioFeeForSelf = folio.folioFeeForSelf(); // D18{1}

// Example: 0.1e18 = 10%
// Of the fee recipient shares, 10% are burned instead of minted
This creates deflationary pressure on supply.

Advanced Minting Strategies

Batch Minting for Multiple Users

contract BatchMinter {
    function mintForUsers(
        Folio folio,
        address[] memory recipients,
        uint256[] memory shares
    ) external {
        for (uint256 i = 0; i < recipients.length; i++) {
            // Calculate required assets
            (address[] memory assets, uint256[] memory amounts) = folio.toAssets(
                shares[i],
                Math.Rounding.Ceil
            );

            // Transfer assets from users to this contract
            for (uint256 j = 0; j < assets.length; j++) {
                IERC20(assets[j]).transferFrom(recipients[i], address(this), amounts[j]);
                IERC20(assets[j]).approve(address(folio), amounts[j]);
            }

            // Mint to recipient
            folio.mint(shares[i], recipients[i], shares[i] * 99 / 100);
        }
    }
}

Mint with Single Asset

Use a DEX aggregator to convert a single asset into the basket:
function mintWithSingleAsset(
    Folio folio,
    IERC20 inputToken,
    uint256 inputAmount,
    uint256 desiredShares
) external {
    // 1. Calculate required basket assets
    (address[] memory assets, uint256[] memory amounts) = folio.toAssets(
        desiredShares,
        Math.Rounding.Ceil
    );

    // 2. Swap input token for each basket asset
    for (uint256 i = 0; i < assets.length; i++) {
        if (address(inputToken) != assets[i]) {
            // Swap on Uniswap/1inch/etc.
            _swapExactOutput(inputToken, IERC20(assets[i]), amounts[i]);
        }
    }

    // 3. Approve and mint
    for (uint256 i = 0; i < assets.length; i++) {
        IERC20(assets[i]).approve(address(folio), amounts[i]);
    }

    folio.mint(desiredShares, msg.sender, desiredShares * 99 / 100);
}

Handling Edge Cases

During rebalances, the basket composition may change:
// Check if a rebalance is active
(, , , , IFolio.RebalanceTimestamps memory timestamps, ) = folio.getRebalance();

if (block.timestamp < timestamps.availableUntil) {
    // Rebalance active - basket may change
    // Consider waiting or using larger slippage
}
Deprecated Folios can only be redeemed:
if (folio.isDeprecated()) {
    // Minting is disabled
    // Auctions are disabled
    // Only redemption is available
    folio.redeem(...);
}
Basket tokens with zero balance are still part of the basket:
(address[] memory assets, uint256[] memory amounts) = folio.totalAssets();

// Some amounts[i] may be 0
// Still required to approve these tokens for minting
// But actual transfer amount will be 0
TVL fees accrue in full days only:
uint256 lastPoke = folio.lastPoke();
uint256 nextAccrual = ((lastPoke / 1 days) + 1) * 1 days;

// Fees will next accrue at nextAccrual timestamp

Querying Fee Information

// Get pending fee shares
uint256 pendingFees = folio.getPendingFeeShares();

// Get fee recipients
for (uint256 i = 0; i < recipientCount; i++) {
    (address recipient, uint96 portion) = folio.feeRecipients(i);
    // portion is in D18 format (1e18 = 100%)
}

// Get DAO fee configuration
(address daoRecipient, uint256 minFeeBps, uint256 maxFeeBps, uint256 daoSplit) = 
    folio.daoFeeRegistry().getFeeDetails(address(folio));

Gas Optimization Tips

If you’ll be minting multiple times, approve max once:
IERC20(token).approve(address(folio), type(uint256).max);
Minting has fixed gas costs. Mint larger amounts less frequently:
// Expensive: 10 mints of 1 share
// Better: 1 mint of 10 shares
If fees are pending, mint() will trigger distribution automatically. Consider timing mints after fee accrual periods.

Code Reference

  • Mint function: contracts/Folio.sol:441-476
  • Redeem function: contracts/Folio.sol:482-508
  • Asset calculation: contracts/Folio.sol:420-433
  • Fee distribution: contracts/Folio.sol:521-551

Next Steps

Auction Participation

Provide liquidity by bidding on rebalancing auctions

Deploying Folio

Create your own Folio with custom parameters

Build docs developers (and LLMs) love