Skip to main content

Sable Apex

The Apex vault is Sable’s most advanced strategy, combining three yield pillars to maximize diversified BTC returns.
Risk Level: 5 (High)
Strategy: 40% Vesu leverage loop + 35% Ekubo WBTC/ETH LP + 25% Endur xWBTC staking

Strategy Overview

WBTC Deposit (100%)

    ├── 40% → Vesu 3x Leverage Loop (same as Turbo)
    │         ↓
    │    Amplified Lending APY + BTCFi STRK

    ├── 35% → Ekubo WBTC/ETH Concentrated Liquidity LP
    │         ↓
    │    Swap Fees + Impermanent Gain/Loss

    └── 25% → Endur xWBTC Liquid Staking

         Validator Rewards + xWBTC Appreciation

Yield Sources

PillarAllocationYield TypeAPY (Example)
Vesu Leverage40%Lending APY (3x amplified) + BTCFi STRK12-18%
Ekubo LP35%Swap fees + IL8-15%
Endur Staking25%Validator rewards + xWBTC appreciation5-10%
Blended APY100%Weighted average9-15%
Impermanent Loss (IL): Ekubo LP position gains when ETH outperforms BTC, loses when BTC outperforms ETH. This creates natural hedging.

Risk Profile

  • High complexity: Three simultaneous strategies increase smart contract risk surface
  • Moderate leverage: 40% of deposit uses 3x loop (overall portfolio leverage ~1.3x)
  • IL exposure: Ekubo LP loses value if BTC/ETH ratio diverges significantly
  • Multi-protocol risk: Depends on Vesu, Ekubo, Endur, and AVNU all working correctly
  • Liquidity risk: Ekubo LP withdrawal depends on pool liquidity

Contract Architecture

File: apex.cairo (~1000 LOC)
Deployed: 0x071eb7fc3a912c0ee85b1dc795e29fd77ff4203a33384a1171dd4fcb7c7b3df9

Core Components

Apex auto-splits every deposit into three strategies:
fn _deploy_to_strategy(ref self: ContractState, amount: u256) {
    // Split: 40% Vesu, 35% Ekubo, 25% Endur
    let staking_amount = (amount * 25) / 100;
    let ekubo_amount = (amount * 35) / 100;
    let vesu_amount = amount - staking_amount - ekubo_amount;

    // Pillar 3: Stake 25% to Endur -> xWBTC
    let endur = IERC4626Dispatcher { contract_address: endur_addr };
    let xwbtc_shares = endur.deposit(staking_amount, this);
    self.endur_staked.write(self.endur_staked.read() + xwbtc_shares);

    // Pillar 2: Deploy 35% to Ekubo WBTC/ETH LP
    self._deploy_to_ekubo_lp(ekubo_amount);

    // Pillar 1: Deploy 40% to Vesu 3x leverage loop
    self._deploy_to_vesu_leverage(vesu_amount);
}
From apex.cairo:1000

Key Functions

User Functions

deposit
function
Deposit WBTC and receive yvBTC-APEX sharesParameters:
  • assets (u256): Amount of WBTC to deposit (8 decimals)
  • receiver (ContractAddress): Address to receive vault shares
Returns: u256 — Number of shares minted
Auto-splits deposit:
  • 25% → Endur xWBTC staking (instant)
  • 35% → Ekubo WBTC/ETH LP (swap half to ETH first)
  • 40% → Vesu 3x leverage loop (deposit → borrow → swap → repeat)
withdraw
function
Burn shares and withdraw WBTCParameters:
  • assets (u256): Amount of WBTC to withdraw
  • receiver (ContractAddress): Address to receive WBTC
  • owner (ContractAddress): Share owner
Returns: u256 — Number of shares burned
Multi-Strategy Unwind:
  1. Withdraw from Ekubo LP (WBTC + ETH) → swap ETH → WBTC
  2. Swap xWBTC → WBTC via AVNU (Endur has 7-day queue)
  3. Flash loan unwind Vesu leverage (if debt exists)
  4. Transfer combined WBTC to user

Curator Functions (Pillar 1: Vesu Leverage)

execute_leverage
function
Execute one Vesu leverage loop (deposit WBTC → borrow USDC → swap → re-deposit)Parameters:
  • collateral_amount (u256): WBTC to deposit
  • borrow_amount (u256): USDC to borrow
  • min_swap_out (u256): Minimum WBTC from USDC swap
  • routes (Array<Route>): AVNU swap routes
fn execute_leverage(
    ref self: ContractState,
    collateral_amount: u256,
    borrow_amount: u256,
    min_swap_out: u256,
    routes: Array<Route>,
)
Implementation at apex.cairo:379
deleverage
function
Reduce Vesu leverage (repay USDC debt + withdraw WBTC collateral)Parameters:
  • repay_amount (u256): USDC to repay
  • withdraw_collateral (u256): WBTC to withdraw
fn deleverage(ref self: ContractState, repay_amount: u256, withdraw_collateral: u256)
Implementation at apex.cairo:449
flash_unwind_vesu
function
Atomically close all Vesu leveraged positions via flash loanParameters:
  • wbtc_to_sell (u256): WBTC to swap for USDC flash loan repayment
  • min_usdc_out (u256): Minimum USDC from swap
  • routes (Array<Route>): AVNU swap routes
fn flash_unwind_vesu(ref self: ContractState, wbtc_to_sell: u256, min_usdc_out: u256, routes: Array<Route>)
Implementation at apex.cairo:668

Curator Functions (Pillar 2: Ekubo LP)

deploy_to_ekubo
function
Deploy WBTC to Ekubo WBTC/ETH concentrated liquidity LPParameters:
  • wbtc_amount (u256): WBTC to deploy
  • eth_min_out (u256): Minimum ETH from half WBTC swap
  • routes (Array<Route>): AVNU WBTC→ETH swap routes
  • pool_key (PoolKey): Ekubo pool identifier (token0, token1, fee, tick_spacing, extension)
  • bounds (Bounds): LP tick range (lower, upper)
  • min_liquidity (u128): Minimum liquidity to mint (slippage protection)
fn deploy_to_ekubo(
    ref self: ContractState,
    wbtc_amount: u256,
    eth_min_out: u256,
    routes: Array<Route>,
    pool_key: PoolKey,
    bounds: Bounds,
    min_liquidity: u128,
)
Implementation at apex.cairo:501
Ekubo Pattern: Transfers tokens to Positions contract, then calls mint_and_deposit or deposit. Positions reads balanceOf(positions_address) internally.
withdraw_from_ekubo
function
Withdraw liquidity from Ekubo LP positionParameters:
  • liquidity (u128): Liquidity units to withdraw
  • pool_key (PoolKey): Same as used in deploy_to_ekubo
  • bounds (Bounds): Same tick range
  • min_token0 (u128): Minimum WBTC output (slippage protection)
  • min_token1 (u128): Minimum ETH output
fn withdraw_from_ekubo(
    ref self: ContractState,
    liquidity: u128,
    pool_key: PoolKey,
    bounds: Bounds,
    min_token0: u128,
    min_token1: u128,
)
Implementation at apex.cairo:563Returns WBTC + ETH to vault. Curator must swap ETH → WBTC separately if needed.

Curator Functions (Pillar 3: Endur Staking)

stake_to_endur
function
Stake idle WBTC into Endur vaultParameters:
  • amount (u256): WBTC to stake
fn stake_to_endur(ref self: ContractState, amount: u256)
Implementation at apex.cairo:600
unwind_endur
function
Swap all idle xWBTC → WBTC via AVNU (bypasses 7-day Endur queue)Parameters:
  • min_amount_out (u256): Minimum WBTC from xWBTC swap
  • routes (Array<Route>): AVNU xWBTC→WBTC swap routes
fn unwind_endur(ref self: ContractState, min_amount_out: u256, routes: Array<Route>)
Implementation at apex.cairo:691

Integration with External Protocols

Vesu PRIME Pool (Pillar 1)

Pool ID: 0x0451fe483d5921a2919ddd81d0de6696669bccdacd859f72a4fba7656b97c3b5 Apex uses Vesu PRIME for leverage loops (same as Turbo vault):
  • Collateral: WBTC
  • Debt: USDC
  • Strategy: 3x loop (deposit → borrow → swap → repeat)

Ekubo WBTC/ETH Pool (Pillar 2)

Pool Key:
PoolKey {
    token0: wbtc_addr,  // WBTC (lower address)
    token1: eth_addr,   // ETH (0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7)
    fee: 0x20c49ba5e353f80000000000000000,  // 0.05%
    tick_spacing: 1000,
    extension: Zero::zero(),
}
Apex provides concentrated liquidity (narrow tick range) to maximize fee capture. Ekubo Contracts:
  • Core: 0x00000005dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b
  • Positions (NFT): ekubo_positions (constructor parameter)
LP Valuation: Apex reads live LP value via get_token_info:
let info = positions.get_token_info(position_id, pool_key, bounds);
// info.amount0 = WBTC principal
// info.fees0 = WBTC swap fees earned
// info.amount1 = ETH principal
// info.fees1 = ETH swap fees earned
// info.pool_price.sqrt_ratio = current pool price (for ETH→WBTC conversion)
From apex.cairo:936

Endur xWBTC Vault (Pillar 3)

Address: endur_vault (constructor parameter) Apex stakes WBTC via Endur’s ERC-4626 vault:
let endur = IERC4626Dispatcher { contract_address: endur_addr };
let xwbtc_shares = endur.deposit(wbtc_amount, vault_address);
xWBTC share price increases over time as staking rewards accrue.

AVNU Swap Routes

Apex uses AVNU for all swaps:
  1. USDC → WBTC (Vesu leverage loop)
  2. WBTC → ETH (Ekubo LP deploy)
  3. ETH → WBTC (Ekubo LP unwind)
  4. xWBTC → WBTC (Endur unwind)
  5. WBTC → USDC (Flash loan repayment)

Example Usage

Depositing WBTC

import { Contract } from 'starknet'

const apexVault = new Contract(apexABI, apexAddress, account)
const wbtc = new Contract(erc20ABI, wbtcAddress, account)

// Check minimum deposit
const minDeposit = await apexVault.min_deposit()
console.log('Min deposit:', minDeposit / 1e8, 'WBTC')

// Deposit (auto-splits 40/35/25)
const amount = 200_000_000n // 2.0 WBTC
await wbtc.approve(apexAddress, amount)
await apexVault.deposit(amount, account.address)

// Check yvBTC-APEX balance
const shares = await apexVault.balance_of(account.address)

Checking Three-Pillar Allocation

// Pillar 1: Vesu leverage (collateral, debt, loops, paused)
const [vesuColl, vesuDebt, numLoops, paused] = await apexVault.get_strategy_info()
console.log('Vesu collateral:', vesuColl / 1e8, 'WBTC')
console.log('Vesu debt:', vesuDebt / 1e6, 'USDC')

// Pillar 2: Ekubo LP (position_id, liquidity)
const [ekuboPositionId, ekuboLiquidity] = await apexVault.get_ekubo_position()
console.log('Ekubo position ID:', ekuboPositionId)
console.log('Ekubo liquidity:', ekuboLiquidity)

// Pillar 3: Endur staking (xWBTC shares)
const endurStaked = await apexVault.get_endur_staked()
console.log('Endur xWBTC shares:', endurStaked / 1e8)

// Convert to WBTC values
const endurVault = new Contract(erc4626ABI, endurAddress, provider)
const endurWBTCValue = await endurVault.convert_to_assets(endurStaked)

const vesuNetValue = vesuColl - (vesuDebt * 95000n / 1e6) // Assume $95k/BTC

console.log('\nPillar Values (WBTC):')
console.log('Vesu (net):', vesuNetValue / 1e8)
console.log('Ekubo LP: [read from get_token_info]')
console.log('Endur:', endurWBTCValue / 1e8)

Reading Ekubo LP Live Value

const ekuboPositions = new Contract(ekuboPositionsABI, ekuboPositionsAddress, provider)

// Get pool key and bounds from vault
const poolKey = {
  token0: wbtcAddress,
  token1: ethAddress,
  fee: 0x20c49ba5e353f80000000000000000n,
  tick_spacing: 1000,
  extension: 0n,
}
const bounds = {
  lower: { mag: lowerMag, sign: lowerSign },
  upper: { mag: upperMag, sign: upperSign },
}

// Read live LP info
const info = await ekuboPositions.get_token_info(ekuboPositionId, poolKey, bounds)
console.log('WBTC principal:', info.amount0 / 1e8)
console.log('WBTC fees:', info.fees0 / 1e8)
console.log('ETH principal:', info.amount1 / 1e18)
console.log('ETH fees:', info.fees1 / 1e18)
console.log('Pool sqrt_ratio:', info.pool_price.sqrt_ratio)

// Convert ETH to WBTC equivalent
const wad = 1e18
const two_pow_128 = 2n ** 128n
const invSqrtRatio = (two_pow_128 * BigInt(wad)) / info.pool_price.sqrt_ratio
const ethTotal = info.amount1 + info.fees1
const ethInWBTC = (ethTotal * invSqrtRatio / BigInt(wad)) * invSqrtRatio / BigInt(wad)
const totalWBTC = info.amount0 + info.fees0 + ethInWBTC
console.log('Total LP value (WBTC):', totalWBTC / 1e8)

Security Considerations

Risks

  1. Multi-Protocol Risk: Depends on 4 protocols (Vesu, Ekubo, Endur, AVNU) — any failure impacts vault
  2. Impermanent Loss: Ekubo LP loses value if BTC/ETH ratio changes significantly
  3. Liquidation Risk: Vesu leverage (40%) can be liquidated if BTC drops >30%
  4. Complexity Risk: 1000 LOC with three simultaneous strategies increases bug surface
  5. Ekubo Liquidity Risk: Large LP withdrawals may face slippage if pool is thin
  6. Gas Cost: Three-strategy deployment is expensive (~3x normal vault gas)

Risk Mitigation

  • Diversification: Three uncorrelated yield sources reduce single-point-of-failure risk
  • Conservative Vesu allocation: Only 40% uses leverage (not 100%)
  • Narrow Ekubo range: Concentrated liquidity maximizes fees, limits IL exposure
  • xWBTC swap escape: AVNU bypass for Endur’s 7-day withdrawal queue
  • Pause mechanism: Owner can halt deposits if any pillar fails
  • Live valuation: total_assets reads on-chain state from all three protocols

Impermanent Loss Monitoring

// Track IL over time
const initialBTCPrice = 95000
const initialETHPrice = 3500
const initialRatio = initialBTCPrice / initialETHPrice  // ~27.14

const currentBTCPrice = 100000
const currentETHPrice = 4000
const currentRatio = currentBTCPrice / currentETHPrice  // 25.0

// IL formula: IL = 2 * sqrt(price_ratio_new / price_ratio_old) / (1 + price_ratio_new / price_ratio_old) - 1
const priceChange = currentRatio / initialRatio  // 0.921
const IL = 2 * Math.sqrt(priceChange) / (1 + priceChange) - 1
console.log('Impermanent Loss:', (IL * 100).toFixed(2) + '%')  // -0.81%

// If ETH outperformed (ratio decreased), IL is negative (loss)
// If BTC outperformed (ratio increased), IL is also negative (loss)
// Only 0% IL when ratio stays constant

Additional Resources

Build docs developers (and LLMs) love