use ank_accounting::{UserId, TokenId};
use ank_engine::EngineCtx;
use ank_exec::{Tx, TxBundle, Action};
use ank_protocol::Protocol;
use ank_risk::compute_aave_values_from_views;
use indexmap::IndexMap;
use serde_json::json;
let target_ltv_bps = 7000u64; // 70%
let band_bps = 250u64; // ±2.5%
let user = UserId(1);
let mut planner = move |ctx: EngineCtx,
prots: &IndexMap<String, Box<dyn Protocol>>,
portfolios: &IndexMap<UserId, Balances>| -> Vec<TxBundle> {
let mut txs = Vec::new();
let wallet = portfolios.get(&user).cloned().unwrap_or_default();
// Read Aave state
let aave_user = prots["aave-v3"].view_user(user);
let aave_mkt = prots["aave-v3"].view_market();
let (dep_val, debt_val, ltv_bps, hf_bps) =
compute_aave_values_from_views(&aave_user, &aave_mkt);
// First tick: stake and deposit
if ctx.step_idx == 0 {
let initial = 10000u128 * 1_000_000_000_000_000_000; // 10k ETH
txs.push(Tx {
protocol: "lido".into(),
action: Action::Custom(json!({"kind":"stake","amount":initial.to_string()})),
gas_limit: Some(200_000),
});
txs.push(Tx {
protocol: "aave-v3".into(),
action: Action::Custom(json!({"kind":"deposit","token":3,"amount":initial.to_string()})),
gas_limit: Some(300_000),
});
return vec![TxBundle { txs }];
}
// Sync Lido wstETH price to Aave
let lido_mkt = prots["lido"].view_market();
let er = lido_mkt["exchange_rate_ray"].as_str().unwrap().parse::<u128>().unwrap();
let eth_px = aave_mkt["reserves"]["ETH"]["price_e18"].as_str().unwrap().parse::<u128>().unwrap();
let wsteth_price = (er as u128 * eth_px) / 1_000_000_000_000_000_000_000_000_000; // ray → e18
txs.push(Tx {
protocol: "aave-v3".into(),
action: Action::Custom(json!({"kind":"set_price","token":3,"price_e18":wsteth_price.to_string()})),
gas_limit: None,
});
// Rebalance logic
if ltv_bps < target_ltv_bps - band_bps {
// Leverage up: borrow more ETH
let gap = (target_ltv_bps - ltv_bps) as u128;
let borrow_amt = (dep_val * gap) / 10_000;
txs.push(Tx {
protocol: "aave-v3".into(),
action: Action::Custom(json!({"kind":"borrow","token":1,"amount":borrow_amt.to_string()})),
gas_limit: Some(350_000),
});
// Stake borrowed ETH → deposit next tick
txs.push(Tx {
protocol: "lido".into(),
action: Action::Custom(json!({"kind":"stake","amount":borrow_amt.to_string()})),
gas_limit: Some(200_000),
});
} else if ltv_bps > target_ltv_bps + band_bps {
// Deleverage: repay debt
let excess = (ltv_bps - target_ltv_bps) as u128;
let repay_amt = (debt_val * excess) / 10_000;
let wallet_eth = wallet.get(TokenId(1));
if wallet_eth < repay_amt {
// Withdraw wstETH and unstake to get ETH
let shortfall = repay_amt - wallet_eth;
txs.push(Tx {
protocol: "aave-v3".into(),
action: Action::Custom(json!({"kind":"withdraw","token":3,"amount":shortfall.to_string()})),
gas_limit: Some(400_000),
});
txs.push(Tx {
protocol: "lido".into(),
action: Action::Custom(json!({"kind":"unstake","amount":shortfall.to_string()})),
gas_limit: Some(150_000),
});
}
txs.push(Tx {
protocol: "aave-v3".into(),
action: Action::Custom(json!({"kind":"repay","token":1,"amount":repay_amt.to_string()})),
gas_limit: Some(300_000),
});
}
vec![TxBundle { txs }]
};