Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.blend.money/llms.txt

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

Blend’s on-chain protocol consists of 9 contract systems handling Safe deployment, vault operations, cross-chain bridging, and security enforcement.

Contract map

Contract systems

Deploys and sets up Gnosis Safe proxies with Blend’s module and guard architecture.Key functions:
// Update module + guard for future deployments
function setModuleAndGuard(
    address _module,
    address _guard
) external onlyOwner;

// Deploy a new Safe with CREATE2
function deploySafe(
    uint256 saltNonce,
    address[] calldata owners,
    uint256 threshold
) external returns (address safe);

// Called via delegatecall during Safe setup
function enableModuleAndSetGuardFromFactory(
    address factoryAddress
) external;
deploySafe is restricted to the module’s executor (StrategyManager(module).executor()). It validates non-empty owners, valid threshold, and that module/guard are set.enableModuleAndSetGuardFromFactory reads module and guard addresses from factory storage at runtime. This is what enables cross-chain address determinism. Module and guard can differ per chain while Safe addresses stay identical.Setup installs three components:
  1. RolesReceiver as module (strategy execution)
  2. RolesGuard as transaction guard (pause + access control)
  3. Safe4337Module as module and fallback handler (account abstraction)
Core orchestration layer. Manages vault configs, executes rebalances, and enforces rate limiting.Key structs:
struct VaultConfig {
    IVaultController control;
    MarketConfig[] markets;
    ActionConfig[] actions;
}

struct MarketConfig {
    bytes32 marketId;
    IMarketAdapterController adapter;
    uint256 leverage;    // 1e18 = 1x, 2e18 = 2x
    bool isWrapped;
    bytes data;
}

struct ActionConfig {
    IVaultActionController controller;
    bytes data;
}
Key functions:
// Trigger rebalance on a Safe
function executeRebalance(
    address safe,
    address vault,
    RebalanceData[] calldata rebalanceData
) public onlyExecutor;

// Trigger a specific vault action
function executeVaultAction(
    address safe,
    address vault,
    IVaultActionController actionController,
    bytes calldata data
) external onlyExecutor;
struct RebalanceData {
    bytes32 marketId;
    bool isIncrease;
    uint256 amount;
    bytes extraData;   // Slippage, swap params, price data
}
Security invariants:
  • Rate limited per (safe, vault) pair via keccak256(abi.encodePacked(safe, vault))
  • MIN_SECONDS_BETWEEN_OPERATIONS enforced (1-300 seconds)
  • Timestamp set before execution; full transaction reverts on failure
  • Separate reentrancy guards: isRebalanceInitiated and isVaultActionInitiated
  • Config validation rejects duplicate marketIds, leverage below 1e18, and duplicate action controllers
Three contracts that enforce transaction validation and propagate config across chains.RolesGuard - Transaction guard on every Safe:
// Pause or unpause all transactions
function pause(bool _shouldPause) external onlyRole(PAUSER_ROLE);

// Pre-execution hook for direct transactions
function checkTransaction(...) external view override;

// Pre-execution hook for module transactions
function checkModuleTransaction(...) external view override;
When paused: checkTransaction allows Safe owners to execute directly. checkModuleTransaction blocks ALL module transactions with no owner bypass.RolesBroadcaster - Sends role updates cross-chain via LayerZero:
function send(
    uint32 _dstEid,
    bytes calldata _callData,
    bytes calldata _options
) external payable onlyOwner;
Same-chain calls route directly to sameChainReceiver.processSameChainCall().RolesReceiver - Receives and applies role updates. Extends StrategyManager:
  • Immutable trust anchors: TRUSTED_BROADCASTER (bytes32) and TRUSTED_CHAIN_ID (uint32) set at construction
  • setPeer() always reverts (peer locked at deployment)
  • _lzSend() always reverts (receive-only)
  • Processes updateVaultConfig and setExecutor messages
  • Double-checks source chain ID AND broadcaster address for defense-in-depth
Orchestrates Morpho vault rebalancing through Bundler3 flashloans.
function executeRebalance(
    address vault,
    RebalanceData[] calldata rebalanceData
) external override;
Execution flow:
  1. Wrap native ETH if vault token is WETH
  2. Deposit existing vault tokens into vault
  3. Process all decrease operations (repay debt, withdraw collateral)
  4. Process all increase operations (supply collateral, borrow)
  5. Clean up GeneralAdapter (deposit remaining tokens back)
Supporting libraries:
  • MorphoLeverageLib - Manages leveraged positions via flashloans. _increaseLeverageAllocation flashloans loan tokens, swaps to collateral, supplies to Morpho, borrows back. _decreaseLeverageAllocation runs the reverse.
  • MorphoSeedingLib - Handles non-leveraged collateral supply. No borrowing involved.
  • MorphoVaultLib - Utility functions for borrow/supply math, deposit/withdraw with slippage checks, and price data decoding.
Security invariants:
  • Temporarily authorizes and then cleans up GeneralAdapter in Morpho
  • Slippage protection via assets/shares ratio checks against assetPerShare
  • Processes decreases before increases (frees capital first)
Two controllers handle different withdrawal scenarios.UserWithdrawController - Full 9-step withdrawal lifecycle:
function executeWithdraw(
    address vault,
    uint256 withdrawAmount,
    uint256 withdrawAssetPerShare,
    uint256 depositAssetPerShare,
    RebalanceData[] calldata rebalanceData,
    uint256[] calldata forceDeallocateAdapterIndices,
    bytes calldata bridgePayload,
    uint256 bridgeOffsets,
    uint256 bridgeFee,
    address bridgeTarget
) external _onlyDelegateCall;
The 9 steps:
  1. Validate amount, module, vault, single-owner requirement
  2. Liquidity reset via VaultController.executeRebalance
  3. Snapshot balance, resolve max withdrawal
  4. Force deallocate from adapters (zero-penalty only)
  5. Vault withdrawal with slippage protection
  6. Determine actual withdrawn amount
  7. Deduct withdrawal fee (max 5%)
  8. Transfer to owner OR bridge cross-chain
  9. Redeposit remaining assets into vault
Bridge patching uses packed bridgeOffsets (recipientOffset, inputAmountOffset, outputAmountOffset, destDecimals) to inject values into pre-built bridge calldata.VaultWithdrawController - Simple atomic vault redemption:
  • No fees, no bridging, no liquidity reset
  • Validates amount is within balance
  • Calls MorphoVaultLib._withdrawAssets directly
  • Assets stay in the Safe
Three adapters handle token swaps within Bundler3 multicalls.SwapAdapter - Base contract:
function swapToCollateral(
    IERC20 loanToken,
    IERC20 collateralToken,
    address recipient,
    bytes memory strategyData,
    bytes calldata extraData
) external override;

function swapToLoanToken(
    IERC20 loanToken,
    IERC20 collateralToken,
    address recipient,
    bytes memory strategyData,
    bytes calldata extraData
) external override;
Three layers of slippage protection:
  1. Governance max (maxSlippageBps) - hard ceiling
  2. Per-swap (slippageBps <= maxSlippageBps) - caller-specified
  3. Oracle-verified minimum output via PriceLib
checkSwapInvariants ensures the adapter holds zero tokens before and after every swap.WhitelistedSwapAdapter - Adds temporal access control. Swaps only execute during active isRebalanceInitiated() or isVaultActionInitiated() windows. At least one of ALLOW_ON_REBALANCE / ALLOW_ON_VAULT_ACTION must be true.BalanceReplacementAdapter - Reads token balances and injects them into calldata at specified offsets using bitwise OR. Handles temporary approvals and balance draining.
Two bridge adapters and a batching controller for cross-chain transfers.AcrossXChainAdapter - Bridges via Across V3 SpokePool:
function execute(
    uint256 chainId,
    address sender,
    address recipient,
    address inputToken,
    bytes calldata data
) external;
Supports any token. Validates slippage via PriceLib.normalize and applySlippage. Validates all address and amount parameters match. Refunds dust to sender.CCTPXChainAdapter - Bridges USDC via Circle CCTP V2:USDC-only. Queries burnLimitsPerMessage from the minter. If the amount exceeds the limit, it splits into multiple depositForBurn calls automatically.Uses maxFee=0, minFinalityThreshold=2000 (Standard finality).XChainVaultAction - Orchestrates batched cross-chain vault actions:
  • Requests MUST be sorted ascending by destinationChainId (enables O(N) duplicate detection)
  • Single vault withdrawal, then executes each bridge request
  • Verifies zero adapter balance after each transfer
  • Sweeps remaining balance back to vault
Layered controller system for vault operations and perpetual trading.DelegateController - Base security. _onlyDelegateCall modifier compares address(this) against immutable SELF. Ensures code only runs via delegatecall from a Safe.VaultController - Extends DelegateController. Provides _validateRebalanceData modifier that checks rebalanceData length matches markets, all marketIds match, and vault is configured.VaultActionController - Extends DelegateController. Provides the executeAction(vault, strategyData, extraData) entry point that delegates to abstract _execute.BasePerpVaultController - Abstract base for perpetual trading with a 6-step rebalance flow:
  1. _preTransaction() (protocol-specific setup)
  2. Consolidate Safe collateral into vault
  3. Close all positions (decreases first)
  4. Consolidate freed capital
  5. Open new positions (increases with amount > 0)
  6. Deposit remaining and emit event
Provides _validateSlippage and _validatePriceDeviation.OstiumVaultController - Concrete implementation for Ostium perpetual trading. Uses IOstiumRegistry for dynamic contract resolution. MarketId encoding packs pairIndex (16 bits) plus an isLong flag.VaultToVaultAction - Moves funds between vaults with optional swap. Validates both source and destination vaults are registered. Uses SwapAdapter with oracle slippage protection when asset types differ.
Utility contracts used across the protocol.PriceLib - Oracle and decimal utilities:
// Reduce amount by basis points
function applySlippage(uint256 amount, uint256 slippageBps)
    internal pure returns (uint256);

// Convert between decimal scales (max delta 77)
function scale(uint256 amount, uint8 from, uint8 to)
    internal pure returns (uint256);

// Oracle price conversion (Morpho 10^36 scale)
function quoteToBase(uint256 quoteAmount, address oracle)
    internal view returns (uint256);

function baseToQuote(uint256 baseAmount, address oracle)
    internal view returns (uint256);
CallBuilder - Constructs Bundler3 Call structs. All functions are internal pure for deterministic calldata encoding. Covers: swapToCollateral, swapToLoanToken, wrapNative, erc20Transfer, erc4626Deposit/Withdraw/Redeem, morphoSupplyCollateral, morphoBorrow, morphoRepay, morphoWithdrawCollateral, morphoFlashLoan.MarketWrapper - ERC-4626 vault wrapping any ERC-20 with authorization gating:
// One-time inflation attack protection
function createBurnDeposit() public;
  • _deposit requires roleReceiver.isRebalanceInitiated() to be true
  • Dual-layer inflation attack protection: virtual shares via decimal offset plus burn deposit to address(1)
  • createBurnDeposit() locks 0.001 tokens to prevent empty vault attacks

Architecture overview

See how these contracts fit into the full system.

Security deep dive

Understand the 4 security layers protecting every transaction.

Deployments

Find deployed contract addresses on each chain.

Audits

Review independent security audit reports.
Last modified on May 7, 2026