diff --git a/building-financial-products-in-solana/figures/perps-funding-index.svg b/building-financial-products-in-solana/figures/perps-funding-index.svg
index dd15db13..ce17349c 100644
--- a/building-financial-products-in-solana/figures/perps-funding-index.svg
+++ b/building-financial-products-in-solana/figures/perps-funding-index.svg
@@ -22,9 +22,9 @@
- run once: rate 5,000 per second,
+ run once: rate 277 per second,
5,000 USDC position, 2,000 seconds
- (about 33 minutes) → 50 USDC of funding
+ (about 33 minutes) → 2.77 USDC of funding
shorts outweigh longs:
index falls, shorts pay
diff --git a/building-financial-products-in-solana/perpetual-futures.md b/building-financial-products-in-solana/perpetual-futures.md
index 9304ff83..dfb6216e 100644
--- a/building-financial-products-in-solana/perpetual-futures.md
+++ b/building-financial-products-in-solana/perpetual-futures.md
@@ -14,7 +14,7 @@ That design decision creates the problem the program is organized around: the pr
## Creating the Market, Where the Parameters Are Checked Against Each Other
-Maria creates the pool and is its `authority`; her revenue is the open and close fees that accrue to `program_fees`, and the program gives her no path to anyone's collateral. She calls `initialize_pool()` with the market's risk parameters: 10 basis-point open and close fees, 10x maximum leverage, a 5% maintenance margin, a 1% liquidation fee, and a 1% cap on oracle confidence. The handler validates each parameter against `BASIS_POINTS_DENOMINATOR`, and one cross-check prevents a dead state: `maintenance_margin_bps` must exceed `close_fee_bps`. Otherwise a position could exist that is too healthy to liquidate but too poor to pay its own close fee, and neither `close_position()` nor `liquidate_position()` would accept it, leaving its owner no way to get their collateral out. The test `test_initialize_pool_rejects_close_fee_at_or_above_maintenance_margin` checks it. So the requirement that every participant must be able to get their assets out of every state is enforced in the market's *configuration*, before any position exists.
+Maria creates the pool and is its `authority`; her revenue is the open and close fees that accrue to `program_fees`, and the program gives her no path to anyone's collateral. She calls `initialize_pool()` with the market's risk parameters: 10 basis-point open and close fees, 10x maximum leverage, a 5% maintenance margin, a 1% liquidation fee, and a 1% cap on oracle confidence. The funding rate is set here too, and no handler can change it once the pool exists. The handler validates each parameter against `BASIS_POINTS_DENOMINATOR`, and one cross-check prevents a dead state: `maintenance_margin_bps` must exceed `close_fee_bps`. Otherwise a position could exist that is too healthy to liquidate but too poor to pay its own close fee, and neither `close_position()` nor `liquidate_position()` would accept it, leaving its owner no way to get their collateral out. The test `test_initialize_pool_rejects_close_fee_at_or_above_maintenance_margin` checks it. So the requirement that every participant must be able to get their assets out of every state is enforced in the market's *configuration*, before any position exists.

@@ -75,16 +75,16 @@ Each open creates a **position** account at a PDA with seeds `"position"`, the p
## Funding Is a Fee the Crowded Side Pays
-A perpetual never expires, so what stops its price drifting away from the asset it tracks? On every venue the answer is **funding**: a periodic payment that makes the popular side pay for its popularity, nudging traders toward balance. This program implements funding in its simplest form: a pool-wide index, `cumulative_funding`, advances by `funding_rate_per_second` every second, in whichever direction the open interest leans; whichever side is heavier pays. Each position remembers the index at its entry (`entry_funding`), and settlement charges size × the index movement since entry, at `FUNDING_PRECISION` fixed point. For example, with a rate of 5,000 per second, a 5,000 USDC position held for 2,000 seconds owes 50 USDC.
+A perpetual never expires, so what stops its price drifting away from the asset it tracks? On every venue the answer is **funding**: a periodic payment that makes the popular side pay for its popularity, nudging traders toward balance. This program implements funding in its simplest form: a pool-wide index, `cumulative_funding`, advances by `funding_rate_per_second` every second, in whichever direction the open interest leans; whichever side is heavier pays. Each position remembers the index at its entry (`entry_funding`), and settlement charges size × the index movement since entry, at `FUNDING_PRECISION` fixed point. For example, at the highest rate a pool may have, 277 per second, a 5,000 USDC position held for 2,000 seconds owes 2.77 USDC.
The example documents two simplifications:
-- **The payment goes to the pool**, to the LPs, rather than to the lighter side of traders.
+- **The pool is the other side of every funding payment.** The heavier side pays the pool, and the pool pays the lighter side at the same rate out of `liquidity`, rather than the two sides paying each other.
- **The rate is flat**, rather than scaling with how lopsided the book is.
Production funding is proportional to the imbalance and flows between the sides; the mechanism here teaches the accounting structure, an index plus per-position entry snapshots, which is the part that survives into every real implementation. The test `test_funding_charged_to_long` opens a lone long, lets two thousand seconds pass, and asserts the pool collected exactly the funding the index accrued. To keep every other number in this chapter clean, the walkthrough pool sets the funding rate to zero, as the test suite's default market does.
-The rate is quoted per second of wall-clock time, and the index stores the Clock's timestamp at its last advance, so what a position costs per hour is the rate alone, whatever the network's slot length. `set_funding_rate()` lets the pool operator change the rate, advancing the index at the old rate before storing the new one so the seconds already elapsed are charged at the rate that applied to them. The *Time* chapter covers why funding counts seconds and freshness counts slots.
+The rate is quoted per second of wall-clock time, and the index stores the Clock's timestamp at its last advance, so what a position costs per hour is the rate alone, whatever the network's slot length. The rate is fixed when the pool is created: `initialize_pool()` refuses one above `MAX_FUNDING_RATE_PER_SECOND`, 277, just under 0.1% of a position's size per hour, and no handler changes it afterwards. If Maria could change it, she could open a small position on the lighter side from any wallet, raise the rate, and close the position a few seconds later. The pool would pay her that funding out of the liquidity Carol and the other providers deposited. Everyone who opens a position or deposits liquidity has seen the rate, and `test_operator_on_the_lighter_side_earns_only_the_fixed_rate` checks that a wallet on the lighter side earns exactly that rate and no more. The *Time* chapter covers why funding counts seconds and freshness counts slots.

@@ -112,7 +112,7 @@ Here is where everyone ends up:
- **Alice** turned 1,000 into 1,990 by being right with leverage.
- **Bob** turned 1,000 into 195 by being wrong and slow.
- **Dave** collected 50 USDC for one well-timed transaction.
-- **Maria**'s `program_fees` holds 15, from two opens and one close (Bob's liquidation charges no close fee). She sweeps it with `collect_fees()`, which pays out what the fee ledger holds and sets it to zero. That handler and `set_funding_rate()` both refuse any signer but her (`test_collect_fees_requires_authority`, `test_only_authority_can_set_funding_rate`).
+- **Maria**'s `program_fees` holds 15, from two opens and one close (Bob's liquidation charges no close fee). She sweeps it with `collect_fees()`, which pays out what the fee ledger holds and sets it to zero. It is the only handler that needs her signature once the pool exists, and it refuses anyone else (`test_collect_fees_requires_authority`).
- **Carol** owns a twelfth of the pool, which absorbed Bob's 750 loss and paid Alice's 1,000 profit: down 250 on the trading, about 21 dollars of it hers, plus the losses and funding of the next thousand traders.
## The Math: One Formula per Concept, All Floored in the Pool's Favor
@@ -132,7 +132,7 @@ The book's standing invariants hold here too. The vault balance always equals `l
- **A stale or manipulated price** is the catastrophic one: with 10x leverage, a 1% price error is a 10% equity error, market-wide, simultaneously. That explains the five checks, the confidence cap, and the one trust decision the program leaves visible: the pool creator chooses the feed, and the feed account's owner is deliberately unchecked, so whoever controls that feed sets every price the pool settles at.
- **The unliquidated-position gap**: liquidations that pay nothing do not happen, and the LPs absorb the loss. The example documents this gap, and it is why production venues add an insurance fund.
-- **First-depositor share inflation**: a donation to the vault moves nothing, because AUM reads `liquidity`, not the vault balance. But `liquidity` grows with every funding payment and trader loss, and a liquidity provider can also be the pool's only trader. An attacker holding a single share can pay funding into the pool from their own position to make that share expensive, then wait for a deposit to round down. The withheld `MINIMUM_LIQUIDITY` makes this cost them, because it counts as 1,000 shares in both divisors: the attacker's share is 1 of 1,001, and almost everything they pay in stays with the locked shares. A deposit mints deposit × (shares outstanding + 1,000) ÷ AUM. In the test the attacker pumps AUM to just over 1,000 USDC and the victim deposits just under twice that, which mints the victim 2,001 shares against the attacker's 1. Dividing by the bare supply of 1 would have minted the victim a single share. `test_inflating_liquidity_through_own_trades_does_not_pay` runs the attack: the attacker pays about 1,000 USDC of funding and gets back about 1 USDC. Divided by the bare supply instead, the same attack returns them about 1,500 USDC, 500 of it from the victim's 2,000 USDC deposit. `test_first_deposit_below_minimum_fails` checks that a first deposit smaller than `MINIMUM_LIQUIDITY` is refused with `DepositTooSmall`.
+- **First-depositor share inflation**: a donation to the vault moves nothing, because AUM reads `liquidity`, not the vault balance. But `liquidity` grows with every funding payment and trader loss, and a liquidity provider can also be the pool's only trader. An attacker holding a single share can pay funding into the pool from their own position to make that share expensive, then wait for a deposit to round down. The withheld `MINIMUM_LIQUIDITY` makes this cost them, because it counts as 1,000 shares in both divisors: the attacker's share is 1 of 1,001, and almost everything they pay in stays with the locked shares. A deposit mints deposit × (shares outstanding + 1,000) ÷ AUM. In the test the attacker holds a long for ten simulated years at the highest funding rate a pool may have, pumping AUM to about 88,000 minor units. The victim deposits just under twice that, which mints the victim 2,001 shares against the attacker's 1. Dividing by the bare supply of 1 would have minted the victim a single share. `test_inflating_liquidity_through_own_trades_does_not_pay` runs the attack: the attacker pays about 87,000 minor units of funding and gets back 88. Divided by the bare supply instead, the same attack returns them about 132,500, about 44,000 of it from the victim's deposit. `test_first_deposit_below_minimum_fails` checks that a first deposit smaller than `MINIMUM_LIQUIDITY` is refused with `DepositTooSmall`.
- **Winning too big**: profit beyond the reservation is clipped by the cap rather than bankrupting the pool, and traders know the cap before they open.
- **Sandwiching and price drift**: every trader and LP handler takes a user-supplied bound and fails with `SlippageExceeded` instead of filling at a surprise price.
- **Double close, double liquidation**: the position account closes when the position is closed or liquidated; the second call finds no account.