Asset Wrappers provide a unified interface for managing heterogeneous assets within Flux vaults. They enable managers to hold positions in any tokenized asset—from standard ERC20 tokens to Uniswap V3 NFTs to yield-bearing vaults.
What is an Asset Wrapper?
An Asset Wrapper is a smart contract that:
Implements the IAsset interface
Manages positions for multiple vaults and managers
Integrates with the Oracle Registry for pricing
Provides a consistent API regardless of underlying asset type
Think of wrappers as "adapters" that translate diverse asset types into a common position tracking format that Flux vaults understand.
Why Asset Wrappers?
Traditional lending protocols are limited to simple ERC20 tokens. Flux's wrapper architecture enables:
// Same pattern for any asset type
vault.locked_depositToWrapper(wrapper, positionId, amount);
vault.locked_withdrawFromWrapper(wrapper, positionId, amount);
// VaultA and VaultB use same wrapper but track separately
balances[vaultA][manager1][positionId] = 100e18
balances[vaultB][manager1][positionId] = 200e18
// No interference between vaults
function deposit(...) external {
require(msg.sender == account, "Unauthorized");
// Only vault can modify its positions
}
// Deploy WETH wrapper
address wethWrapper = assetWrapperFactory.deployERC20Wrapper(WETH);
// Inside manager callback:
// Deposit WETH to position
vault.locked_depositToWrapper(
IAsset(wethWrapper),
bytes32(0), // Generic position ID
10e18 // 10 WETH
);
function getValue(address account, address subaccount, bytes32 positionId)
external view returns (uint256)
{
uint256 balance = balances[account][subaccount][positionId];
// Query factory's oracle registry
IOracleRegistry registry = factory.oracleRegistry();
// Get USD value from oracle
return registry.getValueUSD(underlyingAsset, balance);
}
// Deploy aUSDC wrapper
address aUsdcWrapper = assetWrapperFactory.deployERC4626Wrapper(aUSDC);
// Deposit to aUSDC position
vault.locked_depositToWrapper(
IAsset(aUsdcWrapper),
bytes32(0),
100_000e6 // 100K USDC worth of aUSDC
);
contract UniswapV3Wrapper is IAsset, BaseAssetWrapper {
INonfungiblePositionManager public immutable positionManager;
function deposit(
address account,
address subaccount,
bytes32 positionId,
uint256 tokenId // NFT token ID
) external {
require(msg.sender == account, "Unauthorized");
// Transfer NFT to wrapper
positionManager.transferFrom(subaccount, address(this), tokenId);
// Store position (positionId = tokenId for NFTs)
positions[account][subaccount][positionId] = Position({
tokenId: tokenId,
exists: true
});
}
function getValue(
address account,
address subaccount,
bytes32 positionId
) external view returns (uint256) {
Position memory pos = positions[account][subaccount][positionId];
// Get position liquidity and tick range
(,,,,,,, liquidity, , , ,) = positionManager.positions(pos.tokenId);
// Calculate value based on current price and liquidity
return calculatePositionValue(pos.tokenId, liquidity);
}
}
// VaultA cannot access VaultB's positions
balances[vaultA][manager][positionId] = 100e18 // VaultA's tracking
balances[vaultB][manager][positionId] = 200e18 // VaultB's tracking (separate)
// Enforced by access control
function deposit(...) external {
require(msg.sender == account); // Only vault can modify its positions
}
modifier onlyAccount(address account) {
require(msg.sender == account, "Unauthorized");
_;
}
function deposit(address account, ...) external onlyAccount(account) {
// Only the vault (account) can deposit to its positions
}
// Approve wrapper for token
WETH.approve(wethWrapper, amount);
// Inside unlock callback
vault.locked_depositToWrapper(
IAsset(wethWrapper),
bytes32(0), // Position ID
amount
);
// Inside unlock callback
vault.locked_withdrawFromWrapper(
IAsset(wethWrapper),
bytes32(0), // Position ID
amount // Or type(uint256).max for all
);
// Manager received 10 WETH directly
// Claim into position without transferring
vault.locked_claimFromWrapper(
IAsset(wethWrapper),
bytes32(0),
10e18
);
// Inside liquidation callback
function fluxUnlockCallback(bytes calldata data) external {
// Extract manager's WETH position
vault.locked_withdrawFromWrapper(
IAsset(wethWrapper),
bytes32(0),
type(uint256).max // All of it
);
// WETH sent to liquidator (us)
// Swap for base asset + profit
}
// Anyone deploys via factory
address wrapper = assetWrapperFactory.deployERC20Wrapper(WETH);
// Query all registered wrappers for manager
IAsset[] memory wrappers = vault.getRegisteredWrappers(manager);
// Calculate total position value
uint256 total = 0;
for (uint i = 0; i < wrappers.length; i++) {
total += wrappers[i].getValue(vault, manager, positionId);
}
// Called by vault during liquidation
function ledgerTransferSubaccount(
address fromSubaccount,
address toSubaccount
) external {
require(msg.sender == account, "Only vault");
// Transfer all positions from manager to liquidator
// Implementation varies by wrapper type
}