Protocol Mechanics
What this page is for: the on-chain mechanics behind every rFlow deal — the lifecycle, the settlement math, the oracle, the penalty curve, and how fees flow.
Overview#
rFlow is a yield discounting protocol. It separates a yield-bearing position into two pieces — a principal you still own and a yield stream you can sell — and gives you a trustless market to trade the second one.
A deal is one Anchor PDA. It records who locked which tokens, how much USDC was paid for the future yield, when the contract expires, and (critically) the exchange rate at lock so the program can settle fairly. There is no AMM, no yield-token IOU, and no need for the counterparties to interact again — the program settles permissionlessly once ends_at passes.
Custody
Receipt tokens (or LP NFTs) lock in a PDA vault. The program is the only signer.
Pricing
Sellers set the price in USDC. Buyers either fill or skip — no curve, no slippage.
Settlement
Pyth feeds (mainnet LSTs) anchor settlement values. Anti-manipulation tolerances are enforced by the program.
How It Works#
A single deal moves through four moments. Each one is one Anchor instruction:
Seller calls create_deal. Receipt tokens move into a vault PDA; the deal account is initialized with the price and duration.
Buyer calls buy_deal. USDC flows to the seller (minus a 2% protocol fee) and the deal becomes Active.
The position keeps earning yield in the vault for duration_days. Nothing on-chain needs to happen.
After ends_at, anyone calls settle_deal. Yield tokens go to the buyer, principal tokens go to the seller.
Concrete example
Deal Lifecycle#
A deal account moves through one of three terminal states. The status is stored on the deal PDA and updated by the program:
| Status | Trigger | Outcome |
|---|---|---|
| Created | create_deal | Tokens locked, deal open to any buyer |
| Active | buy_deal | Buyer paid; purchased_at & ends_at stamped |
| Settled | settle_deal (permissionless after ends_at) | Yield to buyer, principal to seller, vault closed |
| Cancelled | cancel_deal (seller only, while Created) | Tokens returned to seller, deal closed |
| BoughtBack | buyback_deal (seller only, while Active) | Seller refunds buyer + accrued yield + penalty |
Why exchangeRateAtLock is stored
exchange_rate_at_lock at creation (principal_value_at_lock / receipt_tokens_amount, scaled by 1e6). Settlement uses this to bound the accepted current value within ±10% — a defense against feeding a manipulated value into settle_deal on chains where the oracle is bypassed.Settlement Math#
At settlement, the program needs to know the current value of the locked tokens. For mainnet LSTs that's a Pyth feed; for devnet or unsupported tokens it's a caller-supplied value bounded by exchange_rate_at_lock.
1// 1. Get the current value (oracle or param)2let current_token_value = if use_oracle {3 let price_data = price_update.get_price_no_older_than(...)?;4 calculate_oracle_value(receipt_tokens_amount, price_data.price, ...)5} else {6 current_token_value_param // bounded ±10% by exchange_rate_at_lock7};89// 2. Yield is the appreciation since lock10let actual_yield = current_token_value11 .saturating_sub(deal.principal_value_at_lock);1213// 3. Split the receipt tokens proportionally14let yield_tokens = receipt_tokens_amount as u12815 * actual_yield as u12816 / current_token_value as u128;17let principal_tokens = receipt_tokens_amount.saturating_sub(yield_tokens);1819// 4. Distribute20token::transfer(vault -> buyer, yield_tokens)?; // buyer earns the yield21token::transfer(vault -> seller, principal_tokens)?; // seller gets principal backThe buyer receives receipt tokens denominated in the same mint the seller locked — not USDC. They can keep holding (the position keeps earning) or redeem at the source protocol.
Meteora LP deals settle differently
claim_meteora_fees; at ends_at the NFT returns to the seller via settle_meteora_lp_deal. See the forward marketplace page.Oracle Role#
rFlow has Pyth integration via pyth-solana-receiver-sdk, but the current mainnet alpha has use_oracle = false. For now, regular LST deals settle the expected-yield model: the caller passes the current value parameter and the program enforces a tolerance band against the stored exchange_rate_at_lock.
Future oracle path
- Pyth feed must be no older than 60 seconds
- Price must be > 0
- Value computed with feed exponent & mint decimals
- No caller value accepted for supported LSTs
Devnet / non-LST path
- Caller supplies
current_token_value - Must fall within ±10% of expected value
- Expected value derived from
exchange_rate_at_lock - Rejects with
InvalidTokenValueotherwise
The Pyth feed IDs for currently-supported LSTs are listed on the Receipt Tokens page.
Early Buyback Penalty#
A seller can exit an active deal early by calling buyback_deal. They refund the buyer the original selling price, plus the yield accumulated so far, plus a penalty. The penalty decays linearly from base_penalty_bps at purchased_at down to min_penalty_bps at ends_at.
1// Defaults from constants.rs (admin-tunable up to MAX_BASE_PENALTY_BPS)2// DEFAULT_BASE_PENALTY_BPS = 300 // 3%3// DEFAULT_MIN_PENALTY_BPS = 100 // 1%45let progress_bps =6 (time_elapsed * BPS_DENOMINATOR / total_duration) as u16; // 0..10_00078let penalty_range = base_penalty_bps - min_penalty_bps;9let penalty_reduction = penalty_range * progress_bps / BPS_DENOMINATOR;10let current_penalty_bps = base_penalty_bps - penalty_reduction;1112let penalty_amount = selling_price * current_penalty_bps / BPS_DENOMINATOR;13let total_buyback = selling_price + actual_yield + penalty_amount;The penalty protects the buyer
base_penalty_bps. As ends_at approaches, the penalty trends toward min_penalty_bps.Participants#
Yield Sellers
Anyone holding a whitelisted receipt token or a Meteora LP NFT.
- Instant USDC for future yield
- Principal stays staked or lent
- No unstaking queues, no LP unwind
- Optional early exit via buyback
Yield Buyers
Anyone with USDC who wants a known payout at a known date.
- Pay a fixed USDC price upfront
- Receive yield in receipt tokens at settlement
- No active management, no liquidations
- Permissionless settlement after expiry
Supported Yields#
rFlow supports three categories of yield, with more on the roadmap. The exact mints and feed IDs are on the Receipt Tokens page.
| Tier | Source | Mechanism | Status |
|---|---|---|---|
| Liquid staking | mSOL, jitoSOL, bSOL | Exchange rate appreciation | Live (Pyth oracle) |
| Lending | kUSDC, cUSDC | Exchange rate appreciation | Whitelist-gated |
| LP fees | Meteora DAMM v2 | Fee accumulation on Position NFT | Live |
Fee Structure#
A single 2% protocol fee is charged on the selling price at fill time — paid by the buyer, withheld from the seller's USDC. Both fee_bps and the penalty parameters live in ProtocolConfig and are admin-tunable (capped at 10% fee / 30% base penalty / 15% min penalty by the program).
1// At buy_deal time2const FEE_BPS = 200; // default: 2%3const fee = sellingPrice * FEE_BPS / 10_000;4const seller = sellingPrice - fee;56token::transfer(buyer -> seller, seller); // seller receives7token::transfer(buyer -> treasury, fee); // protocol treasurySee Tokenomics for the full fee distribution and the public landing page tokenomics for $rFLOW token mechanics.