Custom Wrapper Development

Table of Contents


Architecture Overview

Asset wrappers in Flux provide a unified interface for managing heterogeneous assets. They enable vaults to support:

  • Standard fungible tokens (ERC20)

  • Yield-bearing tokens (ERC4626 vaults like Aave, Compound)

  • NFT positions (Uniswap V3 LP NFTs)

  • LP tokens (Aerodrome, Curve, Balancer)

  • Any custom asset type you can imagine

Key Principles

  1. Three-Layer Position Model: account (vault) → subaccount (manager) → positionId

  2. Isolated Namespaces: Each vault operates independently

  3. Permissionless: One wrapper deployment serves unlimited vaults

  4. Oracle Pricing: Wrappers query the Oracle Registry for valuations

  5. Access Control: Only the vault can modify its positions


Understanding the IAsset Interface

All wrappers must implement IAsset (src/interfaces/IAsset.sol:9):

Function Responsibilities

Function
Purpose
Called By

deposit()

Transfer assets from manager to wrapper

Vault during locked_depositToWrapper()

claim()

Claim pre-received assets (airdrops, rewards)

Vault during locked_claimFromWrapper()

withdraw()

Transfer assets from wrapper to recipient

Vault during locked_withdrawFromWrapper()

transfer()

Move position between accounts

Vault during internal transfers

getValue()

Get USD value of position

Strategy during health checks

getBalance()

Get raw balance (shares, NFT count, etc.)

Vault for accounting

underlyingAsset()

Get underlying token address

Oracle registry for pricing

ledgerTransferSubaccount()

Transfer all positions during liquidation

Vault during liquidation

customInteraction()

Execute custom logic (optional)

Vault during locked_interactWithWrapper()


Base Contracts

Flux provides two base contracts to simplify wrapper development:

1. BaseAssetWrapper (src/base/BaseAssetWrapper.sol:22)

Provides access control and security enforcement:

Benefits:

  • Access control handled automatically

  • Focus on business logic in _internal functions

  • Secure by default

2. SimpleFungibleWrapper (src/base/SimpleFungibleWrapper.sol:18)

Extends BaseAssetWrapper for simple 2-position model:

Use SimpleFungibleWrapper for:

  • ERC20 tokens

  • ERC4626 vaults

  • Any fungible asset with working capital + bond positions

Use BaseAssetWrapper for:

  • NFT positions (Uniswap V3, etc.)

  • Complex multi-position assets

  • Assets requiring custom position enumeration


Wrapper Development Patterns

Pattern 1: Simple Fungible Token Wrapper

Best for: ERC20 tokens, simple yield-bearing tokens

Pattern 2: NFT Position Wrapper

Best for: Uniswap V3 positions, ERC721-based assets

Pattern 3: LP Token Wrapper with Rewards

Best for: Curve, Balancer, Aerodrome LP positions with reward claiming


Position ID Semantics

Position IDs serve different purposes depending on asset type:

Fungible Assets (ERC20/ERC4626)

Standard 2-position model:

Strict enforcement in ERC20AssetWrapper and ERC4626AssetWrapper:

Why? Prevents "ghost collateral" - assets deposited to positions not valued by strategies.

NFT Assets (Uniswap V3, etc.)

Use token ID as position ID:

Multi-Position Assets

Use semantic identifiers:

Best Practices

  1. Document position ID semantics clearly in your wrapper

  2. Validate position IDs if using restricted model

  3. Use descriptive IDs for multi-position wrappers

  4. Ensure strategies understand your position ID scheme


Oracle Integration

Wrappers don't store prices - they query the Oracle Registry:

Standard Pattern

Multi-Asset Valuation

For positions with multiple underlying assets:

ERC4626 Pattern

Convert shares to underlying before pricing:


Security Model

Namespace Isolation

Each vault operates in isolated namespace:

Access Control Pattern

Use BaseAssetWrapper to get this right automatically!

Claim Validation

Prevent fake collateral attacks:

Reentrancy Protection

For complex interactions:


Example Implementations

Example 1: Rebasing Token Wrapper (stETH)

Example 2: Synthetic Asset Wrapper (Synthetix sUSD)


Testing Your Wrapper

Unit Test Template

Integration Test with Real Vault


Deployment & Integration

Step 1: Deploy Wrapper

For vanilla wrappers, use AssetWrapperFactory:

For custom wrappers, deploy directly:

Step 2: Whitelist with Governance

Step 3: Include in Strategy

Step 4: Test with Real Vault


Best Practices

DO

  1. Extend BaseAssetWrapper - Don't reimplement access control

  2. Use SimpleFungibleWrapper for standard fungible assets

  3. Validate position IDs if using restricted model

  4. Handle type(uint256).max in withdraw for "withdraw all"

  5. Measure actual balance changes for fee-on-transfer tokens

  6. Track shares for rebasing tokens (stETH, aTokens)

  7. Validate claim amounts against actual unclaimed balance

  8. Document position ID semantics clearly

  9. Test thoroughly with unit and integration tests

  10. Gas optimize - wrappers are called frequently

DON'T

  1. Don't skip access control - use onlyAuthorized modifier

  2. Don't store prices - always query Oracle Registry

  3. Don't allow cross-account transfers without authorization

  4. Don't assume token balances - always measure actual transfers

  5. Don't ignore rebasing - use shares for rebasing tokens

  6. Don't hardcode decimals - query from token contract

  7. Don't skip edge cases - test zero amounts, max withdrawals

  8. Don't forget events - emit for all state changes

  9. Don't implement custom oracles in wrapper - use registry

  10. Don't make upgradeable - wrappers should be immutable

Security Checklist

Gas Optimization Tips


Common Pitfalls & Solutions

Pitfall 1: Ghost Collateral

Problem: Manager deposits to non-standard position ID, strategy doesn't value it

Solution: Validate position IDs in fungible wrappers

Pitfall 2: Rebasing Token Accounting

Problem: Token balance changes without transfers (stETH, aTokens)

Solution: Track shares, not balances

Pitfall 3: Fee-on-Transfer Tokens

Problem: safeTransferFrom(amount) doesn't guarantee amount received

Solution: Measure actual balance change

Pitfall 4: Unimplemented ledgerTransferSubaccount

Problem: Liquidations fail because subaccount can't be transferred

Solution: Implement proper subaccount enumeration


Further Reading

Last updated