Paboxo

Interest rate model

Vault-wide utilisation, the per-market kinked curves, max utilisation, the parameter bounds, the supply rate, and what APY means here.

The narrative is on interest rates. This page is the arithmetic.

Borrow and supply APR against utilisation
Market
Borrow APRSupply APR (after 10% reserve)
Vertical scale
Kinked interest rate curve for NVDA/USDG: 1% at 0% utilisation, 9% at the 80% kink, 100% at 100% utilisation. Borrows that would reach 100% utilisation revert.0%25%50%75%100%0%20%40%60%80%100%Utilisationkink 80%
80.0%
Borrow APR
9.000%
Supply APR
6.480%
Borrow APY (continuous)
9.417%
New borrow here
Allowed

The hollow dot is the vault's utilisation, 1.09% as read at block 97359729, on NVDA/USDG's curve (borrow 1.109%). Max utilisation is 100% on every market: a new borrow reverts only when it would take utilisation to 100%, and accrued interest can still carry utilisation up the curve.

Show as a table
UtilisationBorrow APRSupply APRBorrow APY
0%1.00%0.00%1.01%
20%3.00%0.54%3.05%
40%5.00%1.80%5.13%
60%7.00%3.78%7.25%
80%9.00%6.48%9.42%
85%31.75%24.29%37.37%
90%54.50%44.15%72.46%
95%77.25%66.05%116.52%
100%100.00%90.00%171.83%
Per-market curves as read on mainnet 4663 at block 96027397; the faint lines are the other markets' borrow curves. Utilisation is the Earn vault's, one figure for every market, so at the same utilisation each market charges its own rate. Supply APR is what the vault would earn on its assets if all of its debt were in this market, before its fee. The contracts compute in integers, rounding each slope down, so on-chain values can sit up to 1e-18 below these.

Utilisation

Every market that lends from one Earn vault prices its borrow rate at the same utilisation: the vault's. Each market keeps its own curve, so at the same utilisation each market charges its own rate.

B=∑mBm,A=Ivault+∑mSmB = \sum_{m} B_m, \qquad A = I_{\text{vault}} + \sum_{m} S_m U={0B=01018A=0, B>0min⁡(⌊B⋅1018A⌋, 1018)otherwiseU = \begin{cases} 0 & B = 0 \\ 10^{18} & A = 0,\ B > 0 \\ \min\left(\left\lfloor \dfrac{B \cdot 10^{18}}{A} \right\rfloor,\ 10^{18}\right) & \text{otherwise} \end{cases}

Where:

  • mm runs over the markets in either of the vault's queues, each counted once.
  • BmB_m — each market's totalBorrowAssets; SmS_m — its totalSupplyAssets, what the vault has in it.
  • IvaultI_{\text{vault}} — the vault's USDG balance (idle). Reserves are not in AA: they are the protocol's, not lent by the vault.
  • UU — a WAD fraction. It rounds down, and it is clamped at 100%: interest grows BB faster than AA, so the raw ratio can pass 100%, and the clamp holds the rate at the maximum.

UU is stored in the vault and read by every market's rate as vault.utilization(); irm.utilizationOf(router) returns the figure a market's rate is computed from. It changes only at a checkpoint, and a checkpoint runs only after every market of the vault has accrued at the old value: the vault's own deposit, mint, withdraw, redeem and allocation calls accrue every market at their start and checkpoint at their end, and the pools call the vault's accrueInterestAll() before and after every borrow, repay and liquidation. So between two checkpoints every market's rate is constant, and no market's open window is repriced by another market's activity. Source: PaboxoEarnVault · 0xaF5a…c634, InterestRateModel · 0x8390…D741, InterestRateModel · 0x8390…D741.

A market's own debt does not enter its rate except through BB: a market with no debt still reads the vault's UU, and charges it the moment it lends.

vault.utilizationAfter(x) is UU after xx more is borrowed from any of the vault's markets, with every market's pending interest included; vault.utilizationTotals() returns the BB and AA behind it.

Worked, three markets (illustrative): a vault with 40,000 USDG idle; NVDA holding 40,000 USDG of supply and 30,000 of debt; SPY 20,000 and 5,000; WETH only its 1-unit seed. To isolate the utilisation, all three are put on one curve, the launch curve (0.5% / 8% / 100%, kink 80%).

RawHuman
BB3500000000035,000 USDG
AA100000000001100,000.000001 USDG
UU34999999999650000035%
rr, every market on the launch curve378124999996718753.78125%

Under the formula before vault-wide utilisation, each market divided its own debt by its own supply plus the vault's idle balance: NVDA would have paid 4.015625% and SPY 1.28125% for the same USDG from the same vault.

Worked, illustrative market: the vault's only market, with nothing idle, so BB and AA are that market's own.

RawHuman
BB2001000000020,010 USDG
A=SA = S100000000000100,000 USDG
UU20010000000000000020.01%

Mainnet, block 93419456 (20 October 2026, 11:33:27 UTC), after the vault-wide utilisation upgrade: the only debt was on WETH/USDG, so every market priced at that one borrow. Every market then shared the launch curve, so every market also charged the same rate.

RawHuman
BB, booked (WETH/USDG totalBorrowAssets)5000084050.000840 USDG
AA, booked (idle 2161320702 + seeds 1 + 1 + WETH/USDG 50000757)22113214612,211.321461 USDG
UU, vault.utilization() = irm.utilizationOf(router), every market226112941432715552.2611%
rr, calculateBorrowRate(router), every market71198088259317080.7120%

The stored UU is ⌊B⋅1018/A⌋\lfloor B \cdot 10^{18} / A \rfloor on the booked figures of the last checkpoint. vault.utilizationTotals() adds WETH/USDG's pending interest, 3 base units, to both sides — BB = 50000843, AA = 2211321464 — so utilizationAfter(0) read 22611295469250688, the value the next checkpoint would store.

The curve

r(U)={r0+⌊U (ropt−r0)Uopt⌋U≤Uoptropt+⌊(U−Uopt)(rmax⁡−ropt)1018−Uopt⌋U>Uoptr(U) = \begin{cases} r_0 + \left\lfloor \dfrac{U\,(r_{\text{opt}} - r_0)}{U_{\text{opt}}} \right\rfloor & U \le U_{\text{opt}} \\[3mm] r_{\text{opt}} + \left\lfloor \dfrac{(U - U_{\text{opt}})(r_{\max} - r_{\text{opt}})}{10^{18} - U_{\text{opt}}} \right\rfloor & U > U_{\text{opt}} \end{cases}

Where:

  • r0r_0 — base rate.
  • roptr_{\text{opt}} — rate at the kink.
  • UoptU_{\text{opt}} — the kink, optimal utilisation.
  • rmax⁡r_{\max} — max rate, reached at 100% utilisation.

All rates are WAD APRs. The parameters are the market's own; UU is its vault's. Source: InterestRateModel · 0x8390…D741.

On mainnet, set by the protocol Safe (transaction nonce 4) and read at block 96027397:

Marketr0r_0roptr_{\text{opt}}UoptU_{\text{opt}}rmax⁡r_{\max}
SPY / USDG0.5% (5e15)6% (6e16)80% (8e17)100% (1e18)
WETH / USDG0.5% (5e15)7% (7e16)80% (8e17)100% (1e18)
NVDA / USDG1% (1e16)9% (9e16)80% (8e17)100% (1e18)
TSLA / USDG1% (1e16)12% (1.2e17)80% (8e17)100% (1e18)
SPCX / USDG1.5% (1.5e16)14% (1.4e17)80% (8e17)100% (1e18)

The markets listed later reuse these curves: MSFT/USDG and AAPL/USDG NVDA/USDG's (read at block 99388687); QQQ/USDG WETH/USDG's, GOOGL/USDG NVDA/USDG's and AMD/USDG SPCX/USDG's (read at block 113204091). Their rows in the tables below are those markets' rows.

Until then every market shared the launch curve: 0.5% / 8% / 100%, kink 80%. The illustrative examples still use it.

UU0%40%80%90%100%110% (clamped)
SPY, rr raw5e153.25e166e165.3e171e181e18
SPY, rr0.5%3.25%6%53%100%100%
WETH, rr raw5e153.75e167e165.35e171e181e18
WETH, rr0.5%3.75%7%53.5%100%100%
NVDA, rr raw1e165e169e165.45e171e181e18
NVDA, rr1%5%9%54.5%100%100%
TSLA, rr raw1e166.5e161.2e175.6e171e181e18
TSLA, rr1%6.5%12%56%100%100%
SPCX, rr raw1.5e167.75e161.4e175.7e171e181e18
SPCX, rr1.5%7.75%14%57%100%100%
Launch curve, rr raw5e154.25e168e165.4e171e181e18
Launch curve, rr0.5%4.25%8%54%100%100%

Every curve meets at 100%: the upper slope is steepest where the kink rate is lowest (SPY: 94 points of APR over the last 20 points of utilisation; SPCX: 86).

Worked, illustrative market (launch curve):

r=5×1015+⌊200100000000000000×7.5×10168×1017⌋=23759375000000000r = 5 \times 10^{15} + \left\lfloor \frac{200100000000000000 \times 7.5 \times 10^{16}}{8 \times 10^{17}} \right\rfloor = 23759375000000000

That is 2.3759375% APR.

Max utilisation

Max utilisation is not part of the curve and does not enter the rate. It is a separate check on each new borrow, made after the borrow is added, against the vault-wide utilisation and the borrowing market's own limit:

Unew=vault.utilizationAfter(0),Unew≥Umax⁡⇒MaxUtilizationReachedU_{\text{new}} = \texttt{vault.utilizationAfter}(0), \qquad U_{\text{new}} \ge U_{\max} \Rightarrow \texttt{MaxUtilizationReached}

Where UnewU_{\text{new}} is UU computed as above from every market's books after accrual, including the new borrow, and Umax⁡U_{\max} is the market's max utilisation, 100% (1e18) on every mainnet market (90%, 9e17, until the Safe's transaction nonce 4). A borrow does not change AA — the market's own cash or a pull from idle into its supply funds it — so it only adds to BB. The comparison is inclusive: a borrow that lands exactly on Umax⁡U_{\max} reverts. At 100% that is the borrow that would lend out the whole vault; anything short of it passes, so no cash is reserved for lenders' withdrawals. A market that is not in either of its vault's queues cannot lend at all (MarketNotQueuedInVault). Source: LendingPoolRouter.

The largest borrow that passes the utilisation check, from any one market, is therefore

xmax⁡=⌊Umax⁡ A−11018⌋−Bx_{\max} = \left\lfloor \frac{U_{\max}\,A - 1}{10^{18}} \right\rfloor - B

(0 when that is negative or A=0A = 0); at Umax⁡=1018U_{\max} = 10^{18} it is A−1−BA - 1 - B. router.maxBorrowable() returns the smaller of this, the market's cash plus what the vault would still send it, and its borrow-cap headroom. Source: LendingPoolRouter.

Worked, mainnet at block 93419456, when max utilisation was 90%: with BB = 50000843 and AA = 2211321464 from utilizationTotals(), xmax⁡=x_{\max} = 1990189317 − 50000843 = 1940188474, or 1,940.188474 USDG. maxBorrowable() returned exactly that on all three markets: the idle balance (2,161.320702 USDG) is larger and no borrow cap is set, so the utilisation limit binds. The headroom is shared: a borrow from one market uses up the headroom of the others. With max utilisation at 100% the utilisation limit stops binding. At block 96027397, with BB = 50003744 and AA = 4591379349, xmax⁡=x_{\max} = 4591379348 − 50003744 = 4541375604, but maxBorrowable() returned less: 4,341.375972 USDG on SPY/USDG and NVDA/USDG, 4,341.375971 on WETH/USDG and 4,441.375971 on TSLA/USDG and SPCX/USDG. The liquidity limit binds: what the vault would still send (availableFor, its idle 4,341.375971 USDG) plus the market's own cash — 1 base unit on SPY/USDG and NVDA/USDG, none on WETH/USDG, the vault's 100 USDG seed on TSLA/USDG and SPCX/USDG. After the Safe returned those two seeds to the vault (block 96053523), the utilisation limit bound again. At block 97359729, with BB = 50005047 and AA = 4591380523, xmax⁡=x_{\max} = 4591380522 − 50005047 = 4541375475, and maxBorrowable() returned exactly that on all five markets. What the vault would still send is a little more: idle 4,541.375969 USDG plus each market's 1 base unit (none on WETH/USDG). The same happened after MSFT/USDG and AAPL/USDG were listed with 100 USDG seeds on 27 October 2026 and the Safe returned them (block 99432434): at block 99454422 maxBorrowable() returned 4,541.375272 USDG on all seven markets. QQQ/USDG, GOOGL/USDG and AMD/USDG were listed on 13 November 2026 with a 1-base-unit seed each; at block 113204091, with BB = 53031247 and AA = 4849395359, xmax⁡=x_{\max} = 4849395358 − 53031247 = 4796364111, and maxBorrowable() returned exactly that on all ten markets. In the three-market example above, xmax⁡=x_{\max} = 65000000000 (65,000 USDG); in the illustrative market, 79989999999 (79,989.999999 USDG). Interest accruing onto existing debt can still carry utilisation to 100%, where UU is clamped, without any new borrow; the rate just keeps following the curve.

Parameter bounds

The owner can change every parameter above per market. Every setter refuses a value outside these bounds, all WAD:

r0≤ropt≤rmax⁡≤rcap0<Uopt<10180<Umax⁡≤1018Rf≤Rf,cap\begin{aligned} & r_0 \le r_{\text{opt}} \le r_{\max} \le r_{\text{cap}} \\ & 0 < U_{\text{opt}} < 10^{18} \\ & 0 < U_{\max} \le 10^{18} \\ & R_f \le R_{f,\text{cap}} \end{aligned}

Where rcapr_{\text{cap}} is MAX_RATE = 1e19 (1,000% a year) and Rf,capR_{f,\text{cap}} is MAX_RESERVE_FACTOR = 5e17 (50%).

Each bound keeps a step of the curve or of accrual from reverting. The two slopes subtract ropt−r0r_{\text{opt}} - r_0 and rmax⁡−roptr_{\max} - r_{\text{opt}} and divide by UoptU_{\text{opt}} and 1018−Uopt10^{18} - U_{\text{opt}}. The MAX_RATE ceiling keeps B⋅r⋅ΔtB \cdot r \cdot \Delta t from overflowing. Without the bounds, one bad setter call could make accrueInterest() revert. That call runs first in repay, liquidation and every Earn vault action, so the market would freeze. The bounds live in one library, RateCurve, which both the rate model's setters and the factory's market creation check against. Source: InterestRateModel · 0x8390…D741, LendingPoolFactory · 0x7a2b…b1C6.

The single-value setters check the new value against the two rates already stored, so moving a whole curve has to go in order: raise rmax⁡r_{\max}, then roptr_{\text{opt}}, then r0r_0, and lower them in reverse. setLendingPoolRateCurve sets all five curve values in one call and validates them together. The factory uses it when it creates a market.

Every setter that changes what a market is charged (the curve and the reserve factor) accrues that market first, so its open window is booked at the parameters it ran on. Only that market accrues: the input the markets share is the vault's utilisation, not each other's rates. Changing max utilisation charges nothing, so it does not accrue.

Every mainnet curve (base 0.5%–1.5%, kink rate 6%–14%, max rate 100%, kink 80%, max utilisation 100%, reserve factor 10%) sits inside every bound; max utilisation is at the bound's upper end, which is allowed (block 96027397).

Supply rate

Interest is split between lenders and reserves (see accrual). Per unit of time, lenders receive (1−Rf)(1 - R_f) of B⋅rB \cdot r, spread over what they have supplied:

rsup=r⋅BS⋅(1−Rf)r_{\text{sup}} = r \cdot \frac{B}{S} \cdot (1 - R_f)

Where RfR_f is the reserve factor in effect, 10% on every market (see effective reserve factor).

This is per market: rr is the market's rate at the vault's UU, while BB and SS are the market's own.

What an Earn depositor gets is the vault's rate over all of AA, idle included (see net rate). Every market shares the reserve factor, so

∑mSm rmsupA=∑mrm(U) BmA⋅(1−Rf)\frac{\sum_m S_m\, r^{\text{sup}}_m}{A} = \frac{\sum_m r_m(U)\, B_m}{A} \cdot (1 - R_f)

up to rounding and interest not yet booked, before the vault's fee: each market's rate, weighted by its share of the debt. With the per-market curves the riskier markets' debt lifts it. Only when every market with debt is on the same curve does it collapse to r(U)⋅U⋅(1−Rf)r(U) \cdot U \cdot (1 - R_f).

Worked, illustrative market: 2.3759375%×20,010100,000×0.9=0.427883%2.3759375\% \times \frac{20{,}010}{100{,}000} \times 0.9 = 0.427883\%. On mainnet at block 93419456, on the launch curve, WETH/USDG: 0.7120%×50.00084050.000757×0.9=0.6408%0.7120\% \times \frac{50.000840}{50.000757} \times 0.9 = 0.6408\% on the vault's 50.000757 USDG in it; NVDA/USDG and SPY/USDG, with no debt, paid 0 on their seeds. The vault's lending rate on all of AA: 0.7120%×2.2611%×0.9=0.0145%0.7120\% \times 2.2611\% \times 0.9 = 0.0145\%.

At the curve points, with a 10% reserve factor, the vault's lending rate rm(U)⋅U⋅(1−Rf)r_m(U) \cdot U \cdot (1 - R_f) if all of its debt were in one market (the blend of a mixed book sits between the rows):

UU0%40%80%90%100%
SPY / USDG0%1.17%4.32%42.93%90%
WETH / USDG0%1.35%5.04%43.335%90%
NVDA / USDG0%1.8%6.48%44.145%90%
TSLA / USDG0%2.34%8.64%45.36%90%
SPCX / USDG0%2.79%10.08%46.17%90%
Launch curve0%1.53%5.76%43.74%90%

Effective reserve factor

The model stores a raw value per market, tokenReserveFactor(router), and applies

Rf={1017stored=00stored=2256−1storedotherwiseR_f = \begin{cases} 10^{17} & \text{stored} = 0 \\ 0 & \text{stored} = 2^{256} - 1 \\ \text{stored} & \text{otherwise} \end{cases}

A stored 0 means "never set" and applies the 10% default, and the mainnet markets rely on that. So an explicit 0% is stored as the sentinel 2256−12^{256} - 1 (ZERO_RESERVE_FACTOR) instead. No real factor can collide with it, because the setter accepts nothing above 50%. Read reserveFactor(router) for the value in effect; the raw getter returns either special value as it is stored. Source: InterestRateModel · 0x8390…D741, InterestRateModel · 0x8390…D741.

Mainnet, block 93419456: the stored value is 0 on all three markets, and reserveFactor(router) returns 1e17 (10%) for each. The same holds for all five markets at block 94915008, SPCX/USDG and TSLA/USDG included, for MSFT/USDG and AAPL/USDG at block 99393260, and for QQQ/USDG, GOOGL/USDG and AMD/USDG at block 113204091.

APR and APY

Every rate on-chain is an APR. Interest is linear within a window and compounds at each booking: each accrual adds its interest to BB, and the next accrual charges on that larger BB. So the effective annual yield depends on how often the market accrues. If it accrued every Δt\Delta t seconds at a constant rate:

APYΔt=(1+r ΔtY)Y/Δt−1  →  Δt→0    er−1\text{APY}_{\Delta t} = \left(1 + \frac{r\,\Delta t}{Y}\right)^{Y/\Delta t} - 1 \;\xrightarrow{\;\Delta t \to 0\;}\; e^{r} - 1
APR0.5%4.25%8%54%100%
APY, continuous limit0.50125%4.3416%8.3287%71.60%171.83%
APY, daily accrual0.50125%4.3413%8.3278%71.53%171.46%

Those are the launch curve's points. On the per-market curves, APY with daily accrual at each utilisation (at 100% every market is at 100% APR: 171.46% daily, 171.83% continuous):

UU0%40%80%90%
SPY / USDG0.50125%3.3032%6.1831%69.83%
WETH / USDG0.50125%3.8210%7.2501%70.68%
NVDA / USDG1.0050%5.1267%9.4162%72.39%
TSLA / USDG1.0050%6.7153%12.7475%74.99%
SPCX / USDG1.5113%8.0573%15.0243%76.75%

Mainnet at block 93419456, on the launch curve: every market at 0.7120% APR, 0.7145% APY in the continuous limit. With the per-market curves, at block 96027397 (vault utilisation 1.0891%): SPY 0.5749%, WETH 0.5885%, NVDA 1.1089%, TSLA 1.1497%, SPCX 1.6702% APR. Illustrative market: 2.3759375% APR, 2.404388% APY in the continuous limit.

Two caveats. The rate is not constant: UU is re-stored at every checkpoint — every borrow, repay, liquidation and vault deposit or withdrawal — and drifts up as interest accrues, in any of the vault's markets. And accrual only happens when something triggers it, so a quiet market compounds less often than daily. An APY is an idealisation of the APR; the APR is the number the contracts use.

On this page