Skip to main content
This page covers the smart contract implementation details. See Glossary.
The FeeManager is an optional contract that you can attach to a Multi-Vehicle or Conduit at deployment time. It automates fee calculation and collection across the vehicle lifecycle. Once set during initialization, the FeeManager cannot be changed or removed, ensuring predictability for users. You can share a single FeeManager across multiple Multi-Vehicle contracts to maintain consistent fee behavior and centralize configuration changes.

The 4 fee types

Railnet supports four distinct fee types, each calculated and applied at different points in the vehicle lifecycle.

Performance fee (on earnings)

A fee charged on gains above the high water mark. You only collect when the strategy makes money.
High Water Mark: $1,000,000
Current Assets:  $1,100,000
Earnings:        $100,000

// If performance fee = 20%:
// Fee = $100,000 * 20% = $20,000 (minted as shares)
// New High Water Mark: $1,100,000

// Formula:
// earnings = max(0, currentTotalAssets - lastTotalAssets)
// performanceFeeAssets = earnings * performanceFeeBps / 10000
Best for: Hedge fund-style fee models, performance-aligned compensation. Users only pay when the strategy profits.
If the strategy loses value, no performance fees are collected until the share price recovers past the previous high water mark.

Management fee (time-based)

An annualized fee prorated by time elapsed since the last operation. Charged regardless of performance.
Total Assets:     1,000,000 USDC
Management Fee:   2% annual (200 bps)
Time Elapsed:     30 days (2,592,000 seconds)

// Fee = totalAssets * managementFeeBps * timeDelta / (10000 * 31536000)
// Fee = 1,000,000 * 200 * 2,592,000 / (10000 * 31536000)
// Fee = ~1,644 USDC worth of shares minted
Best for: Covering ongoing operational costs (gas, infrastructure, monitoring). Provides a predictable revenue stream.
Management fees accrue continuously based on block.timestamp. The first operation after a long idle period may mint substantial shares.

Deposit fee (entry fee)

A fee charged when users deposit assets. Deducted from shares received during the unlock phase.
User Deposits:     10,000 USDC
Share Price:       $1.00/share
Expected Shares:   10,000 shares

// If deposit fee = 1%:
// Fee = 10,000 * 1% = 100 shares
// User Receives:   9,900 shares
// FeeManager Gets: 100 shares
Best for: Discouraging short-term “hot money” deposits, one-time onboarding costs, aligning incentives for long-term holders.

Redeem fee (exit fee)

A fee charged when users redeem shares. Deducted from assets received during the unlock phase.
User Redeems:      10,000 shares
Share Price:       $1.00/share
Expected Assets:   10,000 USDC

// If redeem fee = 0.5%:
// Fee = 10,000 * 0.5% = 50 USDC
// User Receives:   9,950 USDC
// FeeManager Gets: 50 USDC
Best for: Discouraging short-term withdrawals, compensating for liquidity management costs, protecting remaining holders from withdrawal impact.

When fees are applied

Fee typeCalculation pointApplied as
PerformanceonOperations (before each deposit/redeem)Shares minted to FeeManager
ManagementonOperations (before each deposit/redeem)Shares minted to FeeManager
DepositapplyFees (during unlock phase)Reduced shares paid to user
RedeemapplyFees (during unlock phase)Reduced assets paid to user

Fee recipients and distribution

Fees can be distributed to multiple recipients with configured percentage splits:
FeeRecipient[] memory recipients = new FeeRecipient[](3);
recipients[0] = FeeRecipient({
    target: operatorAddress,
    shareBps: 6000  // 60% to operator
});
recipients[1] = FeeRecipient({
    target: daoTreasuryAddress,
    shareBps: 3000  // 30% to DAO treasury
});
recipients[2] = FeeRecipient({
    target: developerAddress,
    shareBps: 1000  // 10% to developers
});
// Must sum to exactly 10,000 (100%)

feeManager.setFeeRecipients(recipients);

Example configurations

Fees({
    performanceFeeBps: 2000,  // 20% of profits
    managementFeeBps: 0,
    depositFeeBps: 0,
    redeemFeeBps: 0
})
Aligned with user returns. You only charge on gains.

Configuration

Prerequisites

You need the following roles on your External Access Control, scoped to the Fee Manager contract:
RolePurpose
FEE_MANAGER_SET_FEESUpdate fee percentages
FEE_MANAGER_SET_FEE_RECIPIENTSUpdate the list of fee recipients
FEE_MANAGER_DISPATCH_ERC20Distribute collected fees to recipients
FEE_MANAGER_REDEEM_VEHICLE_SHARESTrigger redemption of vehicle shares held as fees

Set fee percentages

Configure the four fee rates. Values must not exceed the maximum limits set during Fee Manager deployment.
FeeManager.Fees memory newFees = FeeManager.Fees({
    performanceFeeBps: 1000,  // 10% performance fee
    managementFeeBps: 200,    // 2% annual management fee
    depositFeeBps: 0,         // No deposit fee
    redeemFeeBps: 50          // 0.5% redeem fee
});

feeManager.setFees(newFees);
Fee values must not exceed the maxFees ceiling defined at deployment. Attempting to set fees above the maximum will cause the transaction to revert. You can query the current maximums with feeManager.maxFees().

Fee calculation formulas

The management fee is annualized and prorated by time elapsed:
managementFeeAssets = currentTotalAssets * managementFeeBps * timeDelta / (10000 * 31536000)
Where timeDelta is the seconds elapsed since the last update and 31536000 is the number of seconds in a year (365 days).
The performance fee is applied to earnings above the high water mark:
earnings = max(0, currentTotalAssets - lastTotalAssets)
performanceFeeAssets = earnings * performanceFeeBps / 10000
The high water mark (lastTotalAssets) is updated after each fee collection, so you only pay performance fees on new gains.
Transactional fees are deducted from the gross amount:
feeAmount = amount * feeBps / 10000
netAmount = amount - feeAmount
For reverse calculations (determining gross amount from a desired net amount):
grossAmount = netAmount * 10000 / (10000 - feeBps)

Configure fee recipients

Fee recipients define how collected fees are split among addresses. The sum of all recipient shares must equal 10,000 bps (100%).
FeeManager.FeeRecipient[] memory recipients = new FeeManager.FeeRecipient[](2);

// 70% to the operator
recipients[0] = FeeManager.FeeRecipient({
    target: operatorAddress,
    shareBps: 7000
});

// 30% to the DAO treasury
recipients[1] = FeeManager.FeeRecipient({
    target: daoTreasuryAddress,
    shareBps: 3000
});

feeManager.setFeeRecipients(recipients);
Updating recipients causes old recipients to lose access to any uncollected fees. Always call dispatchERC20 to distribute pending fees before changing recipients.

Collect accumulated fees

Performance and management fees accumulate as vehicle shares held by the Fee Manager. To convert these shares into underlying assets, trigger a redemption.
1

Monitor accumulated fees

Check the FeeManager’s share balance in the vehicle to see how much has been collected.
uint256 accumulatedShares = multiVehicle.balanceOf(address(feeManager));
uint256 sharePrice = multiVehicle.totalAssets() / multiVehicle.totalSupply();
uint256 feeValue = accumulatedShares * sharePrice;
2

Redeem vehicle shares

The Fee Manager holds vehicle shares from accumulated performance and management fees. Redeem them to convert shares into the underlying asset.Requires: FEE_MANAGER_REDEEM_VEHICLE_SHARES role.
bytes32 querySalt = keccak256(abi.encodePacked(block.timestamp, "fees"));

(Query memory query, State state) = feeManager.redeemVehicleShares(
    multiVehicle,
    querySalt
);
// Progress query until settled
3

Distribute to recipients

Once redeemed, dispatch base assets to configured recipients.Requires: FEE_MANAGER_DISPATCH_ERC20 role.
feeManager.dispatchERC20(IERC20(baseAsset));
// Distributes entire balance according to recipient shares

View current configuration

Query the Fee Manager’s current state at any time.
// Get current fee configuration
(bytes32 configId, FeeManager.Fees memory currentFees) = feeManager.fees();

// Get maximum fee limits
FeeManager.Fees memory maxFees = feeManager.maxFees();

// Get current recipients
FeeManager.FeeRecipient[] memory recipients = feeManager.feeRecipients();

// Get fee cache for a specific vehicle (high water mark, last update, etc.)
FeeManager.Cache memory vehicleCache = feeManager.cache(address(vehicle));

Preview fee impact

Use the view functions to simulate fee calculations without executing transactions.
// Preview performance + management fees that would be collected
uint256 feeSharesToMint = feeManager.previewOnOperations(
    totalSupply,
    totalAssets,
    assetDecimals,
    sharesDecimals
);

// Preview deposit or redeem fee deduction
(Asset[] memory netAssets, Asset[] memory feeAssets) = feeManager.previewApplyFees(
    configId,
    assets,
    Mode.DEPOSIT
);

// Reverse calculation: find gross amount needed for a target net amount
(Asset[] memory grossAssets, Asset[] memory reverseFeeAssets) = feeManager.reverseFees(
    configId,
    targetNetAssets,
    Mode.DEPOSIT
);

Immutability and guardrails

Cannot change after deployment:
  • The FeeManager address (set during Multi-Vehicle construction)
  • The maxFees ceiling (set during FeeManager deployment)
Plan carefully: Choose maxFees conservatively. You can always charge less than the maximum, but you can never increase fees beyond it. For example, set maxFees.performanceFeeBps = 3000 (30%) even if you start at 2000 (20%) to allow future flexibility.