Money that finds the best rate

feeyo is a non-custodial vault on Solana. It spreads deposits across the best paying lending and staking protocols, moves them only when a move pays for itself, and keeps six guardrails on chain that nobody can switch off

Abstract

You deposit USDC or SOL into a vault and get shares back. The vault places the money in listed Solana protocols, Jupiter Lend, Kamino, Save, Marinade, Jito, ranked by the rate they actually paid. A keeper checks the rates every hour and proposes moves. The program accepts a move only if its extra yield beats its cost within 7 days and it keeps every guardrail intact

Your shares grow in value as yield lands. Withdraw whenever you like, you get principal plus yield, minus a 5% fee on the profit. Leaving within 48 hours of a deposit also leaves 0.1% in the vault for the others; there are no other fees

i

Devnet first. feeyo runs on devnet today, with test money. There the queue waits 10 minutes instead of 48 hours and a FEEYO stake counts after 10 minutes instead of 7 days, so you can watch it all work. Mainnet comes next, with a deposit cap that rises slowly

The problem

Solana lending and staking rates move hour by hour. Chasing them by hand costs time and transaction fees, so most money just sits in one place. Automated routers fix the chasing, but many of them swap one problem for others: they put all funds in the single top protocol, move on small rate wiggles that never earn back the cost, and let one admin key change fees or pause exits

By handAll-in routersfeeyo
Watching ratesYou, by handA bot, hourlyA bot, plus a rule anyone can check
Where funds sitWherever you left them100% in the top protocolBest rates first, max 50% in any one
When funds moveWhen you rememberOn rate driftOnly when the gain beats the cost within 7 days
ExitsDepends on the protocolPausable by the adminCannot be paused, 10% kept liquid
Admin changesn/aInstant, one key48h public queue

Quickstart

  1. Connect a walletPhantom, Solflare or any wallet adapter wallet. On devnet, grab test USDC or SOL from the wallet menu
  2. Pick a vaultUSDC or SOL. Each shows where its money sits right now and the rate it is earning, as measured at the last hourly check
  3. DepositOne transaction. Shares are recorded in your depositor account at the current share price. SOL is wrapped and unwrapped for you
  4. Leave itThe vault routes and compounds on its own. Withdraw from the same screen whenever you want

Deposits and shares

Each vault holds one asset. A deposit mints shares at the current share price: everything the vault holds, idle buffer and receipt tokens at their current value, divided by all shares. Every holder owns an exact, checkable slice

sharespseudocode
// deposit: shares are priced at what the vault holds right now
share_price = (total_assets + 1e6) / (total_shares + 1e6)
shares_out  = amount / share_price

// a virtual 1 USDC and 1,000,000 virtual shares sit in both sums,
// so the first depositor cannot skew the price against the next one

A vault is set up with a short config. All of it is public, and any change goes through the 48 hour queue

usdc-vault.jsonexample
{
  "asset": "USDC",
  "protocols": ["jupiter-lend", "kamino", "save"],
  "maxPerProtocolPct": 50,
  "bufferPct": 10,
  "paybackDays": 7,
  "performanceFeePct": 5,
  "timelockHours": 48,
  "tvlCap": 50000
}

Routing

The keeper ranks listed protocols by realised rate and fills them from the top: the best rate takes up to 50% of the vault, the next best takes the rest up to its own 50%, and so on. 10% always stays in the buffer

The ranking uses what each protocol actually paid the vault, not what it advertises. Lending rates fall as more money is supplied, so the paid rate already carries the vault's own weight in the market, and the 50% cap keeps any one market from absorbing it all

i

Why not all-in? On a typical day the gap between the first and second rate is a few tenths of a percent. Giving that up on half the vault costs little, and it halves what you lose if the top protocol has a bad day

The rebalance rule

Every move has a price: about 2 bps for a lending market (rounding and fees in and out), 15 to 33 bps for a staking token (Jito's 0.1% withdrawal fee, Marinade's liquid unstake fee), plus network fees. A move is worth making only if the higher rate earns that back quickly. feeyo writes this into the program instead of leaving it to the bot

rebalancepseudocode
// anyone may call rebalance; the program runs the checks
if !led_last_three_checks(to)  return Refused(NOT_CONFIRMED)
gain = (rate_to - rate_from) * amount * payback_days / 365
cost = amount * (exit_bps(from) + entry_bps(to)) / 10_000

if gain <= cost                return Refused(GAIN_BELOW_COST)
if share_after(to) > 50%       return Refused(OVER_PROTOCOL_CAP)
if !listed(to)                 return Refused(NOT_LISTED)
if buffer_after < 10%          return Refused(BUFFER_LOW)
return Allowed

rebalance is permissionless. The keeper calls it by default, but so can anyone. The caller does not matter, the checks do. A move that fails any check reverts and nothing happens

Where rates come from

Advertised APYs are projections. feeyo uses realised rates: how much each market's exchange rate actually grew since the previous refresh, which anyone can call once an hour. The vault keeps the last three. Staking tokens only reprice once per epoch, so theirs is measured from one epoch to the next and stands until the next reprice. Three readings then take three epochs, about six days: a new staking token earns its place slowly

A new leader has to stay ahead for 3 hourly checks in a row before the vault moves to it. Short spikes, the kind that vanish an hour later, never trigger a move

Compounding

There is nothing to harvest. The vault holds receipt tokens (kUSDC, mSOL, jitoSOL), and their price against the asset climbs as interest and staking rewards accrue. Yield shows up in that price, and so in your share price, without any claim step that could be missed

The checkpoint instruction only records the share price and emits an event for charts and history. It moves no funds, and no yield depends on it running

Withdrawals

Burn shares, get your slice of the vault. A withdrawal up to the buffer settles at once. A larger one also pulls from protocols in the same transaction, cheapest exit first: a liquid unstake fee outweighs a week of yield

Within 48 hours of your last deposit, 0.1% of the payout stays in the vault for the depositors who remain. Staking tokens reprice once an epoch, and without this someone could deposit just before a repricing and leave just after with a slice of everyone else's yield. After 48 hours a withdrawal costs nothing but the fee on profit

Each position is valued net of an estimate of what leaving it costs. If a protocol's exit charges more than that (Marinade's fee climbs as its pool drains), the withdrawer covers the difference, up to 0.5%, and the app says so before you sign. The depositors who stay never pay for someone else's exit

When a market is fully borrowed

A lending market with every dollar lent out cannot pay back until borrowers repay. If the buffer and the other protocols cannot cover a withdrawal either, it is refused whole instead of paid short: withdraw a smaller amount now and the rest as cash returns. Your shares keep earning meanwhile, and the keeper refills the buffer from wherever cash is

!

No pause on exits. The program has no instruction that stops withdrawals. The guardian can only pause new deposits

The six guardrails

Every move, by the keeper, the admin or anyone else, runs past these. They live in the program, not in the bot

Gain must beat cost

A move happens only if its extra yield over 7 days is larger than everything the move costs

Max 50% per protocol

No single protocol ever holds more than half of the vault, however good its rate

Listed protocols only

Funds can only go to protocols on the vault's list. Adding one goes through the 48h queue

10% kept liquid

A tenth of the vault stays idle, so most withdrawals settle instantly

48h timelock

Fee, protocol list, guardian and admin changes wait 48 hours in an on-chain queue anyone can read

Withdrawals never paused

There is no instruction that stops an exit. The guardian can only pause new deposits

Security model

Non-custodial

Funds sit in program-derived accounts. Only the vault program can move them, and only through the instructions in this document. Nobody on the team can take them out

Separate keys, small powers

The admin can only queue changes; today it is one key, and it moves to a Squads multisig. The guardian can only pause deposits. On mainnet the keeper is a plain wallet with no special rights, it just pays the network fee to call rebalance, so stealing its key gets an attacker nothing. On devnet it also sets the stand-in markets' rates, which takes the markets' key

48 hour queue

Fee changes, new or removed protocols, a new guardian or admin: each sits in an on-chain queue for 48 hours before it can apply. Anyone watching can withdraw first. Once anyone has deposited, a vault only ever lists a protocol through the queue

Only the deployer creates a vault

A vault's address follows from its asset, so whoever created it first would own the address everyone uses. On mainnet only the holder of the program's upgrade key can create one

Taking a protocol off

A protocol comes off through the queue once the vault's position there is empty; dust someone sends to it can't block that. A protocol that breaks and can no longer be read can be written off through the same 48 hour queue, so one failure never freezes the rest of the vault

Deposit cap

Each vault has a TVL cap that can only go up. It limits what is at stake while the protocol is young and can never be used to lock funds in

Open and verifiable

The devnet programs are live and anyone can read them: the vault 57Wy4uCE63YkWzHyTv4ravgFSybnfUSuv2BQLKVJtFqG and the stand-in markets BVuwmeZUHrT82KExXUCvfqNMVyaHnU2TfoC6eD2eC7jF. The mainnet program ID goes here once it is deployed. The source code is not public yet

Risks

Yield comes with risk. Here is what can go wrong and what limits it

RiskWhat happensWhat limits it
Smart contractA bug in feeyo or in a protocol it uses could lose fundsDeposit cap, tests against the real protocol programs
Protocol failureA lending market or staking pool could be exploitedMax 50% in any one, listed protocols only
LiquidityA fully borrowed lending market cannot pay out at once10% buffer, exits pull from every protocol
Exit costsMarinade's liquid unstake charges 0.03% to 1.5% with its pool; Jito's pool charges 0.1%Valued net of an estimate, cheapest exits first, the withdrawer covers any excess up to 0.5%
Keeper outageIf the keeper stops, nobody rebalancesAnyone can call rebalance, funds keep earning where they are
Admin keyA stolen key could queue harmful changes48h public queue, no power over withdrawals. Today it is one key; it moves to a multisig
Program upgradesWhoever holds the program's upgrade key can replace its code, past the 48h queueToday it is one key. It moves to a multisig, and is given up once the code is settled
A protocol breaksEvery deposit and withdrawal reads all listed protocols, so one that can no longer be read would stop themIt can be written off through the 48h queue: what is left in it is given up, the rest of the vault moves again

Program instructions

The vault program is plain Rust on pinocchio, no framework. The mainnet build carries the five protocol adapters and nothing else, about 124 KB on chain, about 0.87 SOL of rent; the devnet build swaps them for the stand-in markets and is 86 KB. Every instruction, and who may call it:

InstructionWhat it doesWho
initialize_vaultCreate a vault for one asset with its configThe program's upgrade authority (mainnet)
list_marketList a market before the first deposit; after that only through the queueAdmin
depositTake tokens, mint shares at the current priceAnyone
withdrawBurn shares, pay out tokens plus yield minus feeShare holder
rebalanceMove funds between listed protocols if all six guardrails passAnyone
refresh_ratesRecord realised rates from receipt token pricesAnyone
checkpointRecord share price and emit an event. Moves no fundsAnyone
queue_change / apply_changePropose a config change (fee, list or take off a protocol, guardian, admin, fee tiers), apply it after 48hAdmin / anyone after the delay
cancel_changeDrop a queued changeAdmin
set_deposits_pausedPause or resume new depositsGuardian
raise_tvl_capRaise the deposit cap. One way, it can never go downAdmin
set_gateName the FEEYO mint and the fee tiers, once. Later tier changes go through the queueAdmin
stake / unstakeStake FEEYO toward a lower fee, take it back any timeAnyone, for their own stake

What is not on the list matters as much: no instruction pauses withdrawals, sends funds to an unlisted address, or lowers the deposit cap

Fees

FeeAmountWhen
Performance fee5% of profit, less with staked FEEYOAt withdrawal, on profit only
Deposit feeNone—
Early exit0.1% of the payout, kept in the vault for the othersOnly within 48 hours of your last deposit
Management feeNone—
Network feesPaid by whoever signsSolana fees, usually under a cent

Lower fees for FEEYO stakers

Stake FEEYO in a vault and, once the stake has sat for 7 days, the fee on your profit drops. FEEYO exists on devnet today, the wallet menu hands out test tokens; it is not on mainnet yet. The tier is measured in tokens staked, not a share of supply, and checked at the moment you withdraw. Deposits are never capped by tier

TierStakedFee
StandardNo stake5% of profit
Bronze10,000 FEEYO3.5% of profit
Silver100,000 FEEYO2% of profit
Gold1,000,000 FEEYO0%

Example: you deposit 5,000 USDC. At around 6% a year, after twelve months your shares are worth about 5,300 USDC. The 300 USDC profit pays a 15 USDC fee, and you receive about 5,285 USDC. With a Silver stake the fee is 6 USDC, with Gold nothing. If you made no profit, you pay nothing

Status

MilestoneWhere it is
Vault program: shares, buffer, six guardrails, queueBuilt, passes end to end on a local validator
Stand-in markets for test clustersBuilt, mirror live mainnet rates
Keeper: refresh, plan, rebalance, checkpointBuilt
App: vaults, deposit and withdraw, live guardrails, historyBuilt
Devnet deploymentLive: both vaults, stand-in markets at live rates, keeper routing
Jito adapter (SPL stake pool)Built, tested against the real program and the mainnet pool
Marinade adapter (deposit, liquid unstake)Built, tested against the real program and mainnet state
Save adapter (refresh, deposit, redeem)Built, tested against the real program and the mainnet USDC reserve
Kamino adapter (deposit, redeem; klend accrues itself)Built, tested against the real program and the mainnet USDC reserve
Jupiter Lend adapter (deposit, redeem, update_rate)Built, tested against the real lending and liquidity programs and the mainnet USDC market
Mainnet tooling: set-up, lookup tables, keeper jobBuilt, rehearsed end to end on a mainnet snapshot with today's protocol programs
KeeperRunning hourly on our server against devnet
MainnetNot deployed yet. Opens with a deposit cap: 50,000 USDC and 300 SOL

Glossary

VaultThe on-chain program account that holds one asset for everyone who deposited it
ShareYour claim on the vault, kept in your depositor account. The vault knows what you paid for it, so the fee only ever touches profit
Share priceEverything the vault holds divided by all shares. It grows as yield lands
Receipt tokenWhat a protocol gives back for a deposit (kUSDC, mSOL, jitoSOL). Its price is where yield shows up
BufferThe idle 10% of the vault that pays out withdrawals instantly
KeeperThe bot that calls rebalance and refresh. It has no powers anyone else lacks
Payback windowThe 7 days a move gets to earn back what it cost
EpochSolana's ~2 day cycle. Staking rewards, and so LST rates, update once per epoch

FAQ

Disclaimer

This document explains how feeyo works. It is not financial advice and not an offer to buy or sell anything. DeFi carries real risk, including bugs, protocol failures and loss of funds. Deposit only what you can afford to lose, and check every transaction before you sign it