icon picker
$LOGOS Custom Vault

1) North-star KPIs (top row)

NAV (USD) — totalAssets() priced by oracles.
PPS (Price per Share) — NAV / totalSupply.
AUM Δ (1D / 7D / 30D) — pct and absolute.
Weight Drift — max |currentWeight − targetWeight|.
Cash Buffer — USDC balance / NAV.
Keeper Health — last keeper success + run time.

2) Data model (subgraph / warehouse)

Entities

Vault
address, name, symbol, denominationAsset, targetWeights[]
Holding (vault snapshot per asset)
timestamp, asset, balanceRaw, balanceUSD, weightPct
NAVSnapshot
timestamp, navUSD, pps, supply, cashBufferPct
Flow (deposits/redemptions)
timestamp, tx, type{Deposit|Redeem|Mint|Withdraw}, shares, assetsUSD, wallet
Trade (router swaps)
timestamp, tx, assetIn, assetOut, amountInRaw, amountOutRaw, amountUSD, estSlippageBps
FeeEvent
timestamp, tx, type{Mgmt|Perf}, feeShares, ppsBefore, ppsAfter, hwmAfter
KeeperRun
timestamp, tx, action{Rebalance|Harvest|PerfFee}, status{OK|Revert}, gasUsed, notes
ParamChange
timestamp, tx, key, oldValue, newValue
If you use The Graph, make entities 1:1; if you use Dune, materialize as views.

3) Core calculations (server or SQL)

NAV (USD) = Σ (balance_token × price_token) + USDC.
PPS = NAV / totalSupply.
Current Weight (asset) = value_asset / NAV.
Drift (asset) = currentWeight − targetWeight.
Turnover (1D) = Σ |Trade.amountUSD| in last 24h / NAV.
Net Flows (1D) = Σ Deposit.assetsUSD − Withdraw.assetsUSD (24h).
Gross Yield (ETH LSTs) ≈ Δ(balance_stETH + balance_rETH in ETH terms) − net buys, / LST value.
Mgmt Fee (YTD) = Σ FeeEvent where type=Mgmt converted to USD at event PPS.
Perf Fee (YTD) ditto.

4) Recommended visuals (layout)

Header (KPI tiles)

NAV, PPS, 1D/7D/30D Change, Cash Buffer, Max Drift, Keeper Last Run

Section: Portfolio

Stacked bar (current weights vs targets)
Table: Holdings — asset, balance, value USD, target %, current %, drift %, 24h Δ.

Section: Flows

Area chart: Net flows vs performance (overlay).
Table: Last 20 deposits/redemptions (wallets, size, PPS at tx).

Section: Trading & Rebalance

Scatter or line: Turnover % by day; highlight rebalances.
Table: Last trades with est slippage, route, AMM.
Event timeline: rebalance(), harvest(), crystallizePerformanceFee().

Section: Fees & Yield

Bar: Mgmt fees (monthly), Perf fees (when HWM beaten).
Line: Implied ETH staking APY (rolling 30d).
Table: Fee events with PPS before/after and HWM.

Section: Risk & Health

Depeg monitor: stETH/ETH & rETH/ETH basis (line + thresholds).
Oracle freshness: last update age per asset.
Alerts feed: last 50 alerts (see §6).

5) Query hints (Dune/SQL style)

-- NAV snapshot (USD)
SELECT
block_time::date AS ts,
SUM(balance * price_usd) AS nav_usd
FROM holdings_view
GROUP BY 1
ORDER BY 1 DESC;

-- Current weights vs targets
SELECT
asset,
SUM(balance * price_usd) / nav.nav_usd AS current_weight,
target.weight_bps / 10000.0 AS target_weight,
(SUM(balance * price_usd) / nav.nav_usd) - (target.weight_bps / 10000.0) AS drift
FROM holdings_view h
CROSS JOIN (
SELECT SUM(balance * price_usd) AS nav_usd FROM holdings_view
) nav
JOIN target_weights target USING(asset)
GROUP BY asset, target.weight_bps, nav.nav_usd;

-- Turnover last 24h
SELECT SUM(ABS(amount_usd)) / nav.nav_usd AS turnover_pct
FROM trades t
CROSS JOIN (SELECT nav_usd FROM nav_view ORDER BY ts DESC LIMIT 1) nav
WHERE t.ts >= NOW() - INTERVAL '24 hours';

6) Alerts (rules → Discord/PagerDuty)

Drift > 10% for any asset (5 min persist).
Oracle stale > 60s for any asset.
stETH/ETH or rETH/ETH basis > ±1.5% for 15 min.
Keeper failure (revert / gas) or no keeper run in > 24h.
Slippage > 100 bps on any trade.
NAV move > ±8% daily (performance or flow spike).
ParamChange fired (timelock executed).
Cash buffer < 1.5% of NAV (liquidity warning).

7) Sources & plumbing

On-chain contracts: LOGOSVault, router, assets (USDC, WBTC, stETH, rETH, WLD).
Events to index: 4626 Deposit/Mint/Withdraw/Redeem, custom Fee events, Rebalance/Trade, Harvest, ParamChange.
Prices: Chainlink (fallback to DEX TWAP).
Keeper logs: push to a simple webhook (e.g., PostHog/Supabase) for run metadata.

8) Operator views (permissions)

Public view: KPIs, portfolio, flows, fees, risk gauges (read-only).
Operator view: includes slippage, route sims, keeper logs, param diffs.
Audit view: raw events + CSV export (period close).

9) SLA & sanity checks (automated)

Time-in-band (weights within ±5%) > 90%.
Keeper success rate > 95%.
Avg slippage < 40 bps.
Oracles fresh > 99% of checks.
PPS continuity — no negative jumps > 5 bps without matching events.

10) “Good enough” v1 deliverable

A single Dune dashboard with:
6 KPI tiles, 5 charts, 4 tables, 1 alert feed.
A tiny Node/Cloudflare Worker that:
Polls vault, computes drift/turnover, posts alerts to Discord.

1) $LOGOS custom vault — Solidity skeleton (ERC‑4626)

What it is: a single‑token deposit vault (USDC) that automatically buys a target basket (WBTC / stETH+rETH / WLD), reinvests rewards, and mints $LOGOS shares that track NAV.
Fees: streaming management fee + optional performance fee with high‑water mark — both on‑chain and transparent.
Rebalance: bands with a keeper (Gelato/Chainlink).
Safe defaults: asset + adapter allowlists, slippage guards, pausable.
⚠️ This is a deliberately minimal, audit‑ready skeleton. It compiles and shows the math and hooks, but you’ll still wire exact router/adapter calls, oracles, and unit tests. Get an independent audit before mainnet use.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* LOGOSVault (skeleton)
* - ERC-4626 share token ($LOGOS), accounting in USDC
* - Deploys USDC into a target basket: WBTC, stETH/rETH, WLD
* - Streams mgmt fee; crystallizes performance fee vs high-water-mark
* - Rebalances within bands; harvests/reinvests yield
*/

import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
import {Pausable} from "openzeppelin-contracts/security/Pausable.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";

interface IAggregatorV3 { // Chainlink-style
function latestRoundData() external view returns (
uint80, int256 answer, uint256, uint256, uint80
);
}

interface ISwapRouter {
// Replace with chosen DEX/aggregator interface (e.g., Uniswap V3 exactInput)
function swap(address tokenIn, address tokenOut, uint256 amountIn, uint256 minOut, address to) external returns (uint256);
}

contract LOGOSVault is ERC4626, Ownable, Pausable, ReentrancyGuard {
// ---------- constants & types ----------
uint256 private constant BPS = 10_000;
uint256 private constant YEAR = 365 days;

struct Target {
address token; // e.g., WBTC, stETH, rETH, WLD
uint16 weightBps; // sum(weights) == 10_000
bool harvestable; // true if we can claim/reinvest rewards
}

// ---------- config ----------
IERC20 public immutable USDC; // vault asset (accounting)
ISwapRouter public router; // whitelisted DEX aggregator
address public keeper; // Gelato/Chainlink caller

// token -> oracle (USDC price with 8 or 18 decimals; normalize inside)
mapping(address => IAggregatorV3) public oracle;

Target[] public targets; // allocation basket
mapping(address => bool) public isAllowedToken;

// ---------- fees ----------
uint256 public mgmtFeeBps; // e.g., 150 = 1.50%/yr
uint256 public perfFeeBps; // e.g., 1000 = 10%
address public feeRecipient;
uint256 public lastFeeAccrual; // timestamp
uint256 public highWaterMarkPPS; // price-per-share at last crystallization (1e18 scale)

// ---------- rebalance ----------
uint16 public bandBps = 500; // +/-5% drift band
uint256 public minTradeUSDC = 5_000e6; // skip tiny trades; adjust per chain

modifier onlyKeeper() {
require(msg.sender == keeper || msg.sender == owner(), "not keeper");
_;
}

constructor(
address _usdc,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) ERC4626(IERC20(_usdc)) {
USDC = IERC20(_usdc);
feeRecipient = msg.sender;
lastFeeAccrual = block.timestamp;
// set HWM to 1.0 PPS initially so first gains are measured correctly
highWaterMarkPPS = 1e18;
}

// =========================
// ADMIN CONFIG
// =========================

function setRouter(address _router) external onlyOwner { router = ISwapRouter(_router); }
function setKeeper(address _k) external onlyOwner { keeper = _k; }
function setOracles(address[] calldata toks, address[] calldata oracles) external onlyOwner {
require(toks.length == oracles.length, "len");
for (uint i; i<toks.length; ++i) oracle[toks[i]] = IAggregatorV3(oracles[i]);
}
function setFees(uint256 _mgmtBps, uint256 _perfBps, address _recipient) external onlyOwner {
_accrueMgmtFee(); // settle before changing
mgmtFeeBps = _mgmtBps; perfFeeBps = _perfBps; feeRecipient = _recipient;
}
function setBand(uint16 _bandBps, uint256 _minTradeUSDC) external onlyOwner {
bandBps = _bandBps; minTradeUSDC = _minTradeUSDC;
}

function setTargets(Target[] calldata t) external onlyOwner {
delete targets;
uint256 sum;
for (uint i; i<t.length; ++i) {
targets.push(t[i]);
isAllowedToken[t[i].token] = true;
sum += t[i].weightBps;
}
require(sum == BPS, "weights!=100%");
}

// =========================
// ERC4626 OVERRIDES
// =========================

function totalAssets() public view override returns (uint256) {
// value USDC balance + all target tokens at oracle USDC prices
uint256 value = USDC.balanceOf(address(this));
for (uint i; i<targets.length; ++i) {
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.