MechanicsMatching Engine

Matching Engine

The RevvFi matching engine (RevvFiOfferBook.getBestOffers()) fills a borrow request from the lowest-APR active offers available in that market — there’s no pooled rate, and matching is entirely order-book driven.

The Matching Algorithm

When a borrower calls borrow(amount, useSeniorOnly, maxApr), the OfferBook executes:

1. Offer Selection

The engine scans all active offers where:

  • active == true
  • remainingAmount > 0
  • expiry > block.timestamp
  • If useSeniorOnly == true: only offers with seniority == 0 (senior) are considered

2. Sorting

Offers are bucketed by APR internally (aprBuckets), and buckets are sorted lowest APR first. Within a bucket, offers are filled in insertion order. Seniority is not an automatic priority tier in the fill order — it’s purely a filter the borrower can opt into via useSeniorOnly. A senior offer at a higher APR does not get filled before a junior offer at a lower APR.

3. Execution

  1. The OfferBook (executeDrawdown) walks buckets lowest-APR-first, taking as much as needed from each offer (supporting partial fills) until amount is satisfied, and returns the filled offers plus their weighted-average APR.
  2. The Market checks that weighted-average APR against the borrower’s maxApr ceiling — if it’s exceeded (or exceeds the protocol-wide MAX_APR_BPS cap), the entire borrow reverts, not just the offers above the ceiling.
  3. On success, the requested principal is transferred to the borrower.
  4. One RevvFiPositionNFT is minted per lender whose offer was (partially or fully) filled, each carrying that lender’s own APR — the weighted average is only used for the maxApr check and the Borrow/DrawdownExecuted events; it is never stored as what any individual position accrues at.

Seniority Tiers

  • Senior (seniority == 0): Paid back first during liquidation loss distribution. Borrowers can restrict a draw to senior-only liquidity via useSeniorOnly.
  • Junior (seniority == 1): Absorbs losses first during liquidation (first-loss position), in exchange for lenders being free to quote whatever APR they want without a senior-only restriction working against them.

Seniority affects loss distribution during liquidation, not the order offers are matched in during a normal borrow.

Matching Example

Borrower Request: 10,000 USDC, useSeniorOnly = false, maxApr = 800 (8%)

Available Offers:

  • Offer A: 5,000 USDC @ 5% APR (Senior)
  • Offer B: 3,000 USDC @ 6% APR (Junior)
  • Offer C: 5,000 USDC @ 10% APR (Senior)

Result:

  • Engine fills 100% of Offer A (5,000 @ 5%), 100% of Offer B (3,000 @ 6%), and 2,000 of Offer C (@ 10%) to reach the full 10,000 requested — lowest APR first, regardless of seniority.
  • Weighted-average APR of the fill: (5,000×5% + 3,000×6% + 2,000×10%) / 10,000 = 6.3%.
  • 6.3% ≤ maxApr (8%), so the borrow succeeds. Three position NFTs are minted, accruing at 5%, 6%, and 10% respectively — each lender earns exactly what they quoted, independently of the others and of the 6.3% figure (which only exists for the maxApr check and the emitted event).
  • Had the borrower instead set maxApr = 550 (5.5%), this exact same fill would revert in full — even though Offers A and B alone (8,000 of the 10,000 needed) are individually well under 5.5% — because completing the requested 10,000 requires touching Offer C, and the resulting blended rate (6.3%) exceeds the ceiling.