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
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 hand | All-in routers | feeyo | |
|---|---|---|---|
| Watching rates | You, by hand | A bot, hourly | A bot, plus a rule anyone can check |
| Where funds sit | Wherever you left them | 100% in the top protocol | Best rates first, max 50% in any one |
| When funds move | When you remember | On rate drift | Only when the gain beats the cost within 7 days |
| Exits | Depends on the protocol | Pausable by the admin | Cannot be paused, 10% kept liquid |
| Admin changes | n/a | Instant, one key | 48h public queue |
Quickstart
- Connect a walletPhantom, Solflare or any wallet adapter wallet. On devnet, grab test USDC or SOL from the wallet menu
- 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
- DepositOne transaction. Shares are recorded in your depositor account at the current share price. SOL is wrapped and unwrapped for you
- 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
// 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
{
"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
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
// 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
| Risk | What happens | What limits it |
|---|---|---|
| Smart contract | A bug in feeyo or in a protocol it uses could lose funds | Deposit cap, tests against the real protocol programs |
| Protocol failure | A lending market or staking pool could be exploited | Max 50% in any one, listed protocols only |
| Liquidity | A fully borrowed lending market cannot pay out at once | 10% buffer, exits pull from every protocol |
| Exit costs | Marinade'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 outage | If the keeper stops, nobody rebalances | Anyone can call rebalance, funds keep earning where they are |
| Admin key | A stolen key could queue harmful changes | 48h public queue, no power over withdrawals. Today it is one key; it moves to a multisig |
| Program upgrades | Whoever holds the program's upgrade key can replace its code, past the 48h queue | Today it is one key. It moves to a multisig, and is given up once the code is settled |
| A protocol breaks | Every deposit and withdrawal reads all listed protocols, so one that can no longer be read would stop them | It 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:
| Instruction | What it does | Who |
|---|---|---|
initialize_vault | Create a vault for one asset with its config | The program's upgrade authority (mainnet) |
list_market | List a market before the first deposit; after that only through the queue | Admin |
deposit | Take tokens, mint shares at the current price | Anyone |
withdraw | Burn shares, pay out tokens plus yield minus fee | Share holder |
rebalance | Move funds between listed protocols if all six guardrails pass | Anyone |
refresh_rates | Record realised rates from receipt token prices | Anyone |
checkpoint | Record share price and emit an event. Moves no funds | Anyone |
queue_change / apply_change | Propose a config change (fee, list or take off a protocol, guardian, admin, fee tiers), apply it after 48h | Admin / anyone after the delay |
cancel_change | Drop a queued change | Admin |
set_deposits_paused | Pause or resume new deposits | Guardian |
raise_tvl_cap | Raise the deposit cap. One way, it can never go down | Admin |
set_gate | Name the FEEYO mint and the fee tiers, once. Later tier changes go through the queue | Admin |
stake / unstake | Stake FEEYO toward a lower fee, take it back any time | Anyone, 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
| Fee | Amount | When |
|---|---|---|
| Performance fee | 5% of profit, less with staked FEEYO | At withdrawal, on profit only |
| Deposit fee | None | — |
| Early exit | 0.1% of the payout, kept in the vault for the others | Only within 48 hours of your last deposit |
| Management fee | None | — |
| Network fees | Paid by whoever signs | Solana 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
| Tier | Staked | Fee |
|---|---|---|
| Standard | No stake | 5% of profit |
| Bronze | 10,000 FEEYO | 3.5% of profit |
| Silver | 100,000 FEEYO | 2% of profit |
| Gold | 1,000,000 FEEYO | 0% |
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
| Milestone | Where it is |
|---|---|
| Vault program: shares, buffer, six guardrails, queue | Built, passes end to end on a local validator |
| Stand-in markets for test clusters | Built, mirror live mainnet rates |
| Keeper: refresh, plan, rebalance, checkpoint | Built |
| App: vaults, deposit and withdraw, live guardrails, history | Built |
| Devnet deployment | Live: 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 job | Built, rehearsed end to end on a mainnet snapshot with today's protocol programs |
| Keeper | Running hourly on our server against devnet |
| Mainnet | Not deployed yet. Opens with a deposit cap: 50,000 USDC and 300 SOL |
Glossary
| Vault | The on-chain program account that holds one asset for everyone who deposited it |
| Share | Your 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 price | Everything the vault holds divided by all shares. It grows as yield lands |
| Receipt token | What a protocol gives back for a deposit (kUSDC, mSOL, jitoSOL). Its price is where yield shows up |
| Buffer | The idle 10% of the vault that pays out withdrawals instantly |
| Keeper | The bot that calls rebalance and refresh. It has no powers anyone else lacks |
| Payback window | The 7 days a move gets to earn back what it cost |
| Epoch | Solana'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