:book: Borrowable Limit Order Book - SPEC
Specifies a borrowable limit order book with dual-sided orders, collateral positions, interest rate model, and self-replacing orders.
What this file does
Specifies a borrowable limit order book with dual-sided orders, collateral positions, interest rate model, and self-replacing orders.
When to use it
- Building a DeFi protocol with lending and limit order functionality
- Designing a system where orders can be borrowed and used as collateral
- Implementing an order book with automatic replacement on opposite side
- Creating a protocol with interest-based liquidation and time-weighted rates
Assumes this stack
:book: Borrowable Limit Order Book - SPEC
:family: Actors
- Makers/lenders:
- place orders
- receive interest
- can make their order unborrowable
- Makers/borrowers:
- place orders and borrow from other-side orders
- pay interest
- Takers:
- take orders on the book and exchange at limit price
- Liquidators:
- liquidate borderline positions due to growing interest rate
:card_index: Orders: type and status
- Limit buy order (or bid): order to buy the base token (e.g., ETH) at a price lower than current price in exchange of quote tokens (e.g., USDC)
- Limit sell order (or ask): order to sell the base token at a price higher than current price in exchange of quote tokens
- Collateral order: limit order which assets (quote token for a buy order, base token for a sell order) serve as collateral for a borrowed positions on the other side of the book (example: borrow ETH from a sell order by depositing USDC in a buy order)
- Borrowable order: order which assets can be borrowed
- Unborrowable order: order which asset cannot be borowed, either because the maker made it unborrowable or because the maker is a borrower
:twisted_rightwards_arrows: Data Structure UML Overview
The UML diagram visually represents the relationships and structures of a Solidity contract's data model.

Entities:
1. User
Represents an individual or entity participating in the system.
- Attributes:
mapping id: address: A unique identifier for each user, which is their Ethereum address.depositIds: uint256[]: An array storing the IDs of orders where the user has deposited assets.borrowFromIds: uint256[]: An array keeping track of the IDs of orders from which the user has borrowed assets.
2. Order
Represents a buy or sell order placed in the system.
- Attributes:
mapping id: orderId: A unique identifier for each order.maker: address: The Ethereum address of the user who places the order.isBuyOrder: bool: A flag to determine if the order is a buy (true) or sell (false).quantity: uint256: The number of assets specified in the order.price: uint256: The price set for the order.pairedPrice: uint256: The price set for the paired order.isBorrowable: bool: Whether the order can be borrowed.positionIds: uint256[]: An array that lists the IDs of positions that have borrowed from this particular order.
- Methods:
deposit(): To place an order.withdraw(): To withdraw assets from an order.take(): To take or fulfill an order.changeBorrowable(): Switch order between borrowable and non borrowable.
3. Position
Represents the assets borrowed from a specific order.
- Attributes:
mapping id: positionId: A unique identifier for each position.borrower: address: The Ethereum address of the user who has borrowed the assets.orderId: uint256: The ID linking back to the order from which the assets were borrowed.borrowedAssets: uint256: The quantity of assets that have been borrowed.timeWeightedRate: Time-weighted average interest rate for the position
- Methods:
borrow(): To initiate borrowing against an order.repay(): To repay the borrowed assets.liquidate(): Liquidate a position becoming insolvent.
Relationships:
-
User to Order: A user can place multiple orders, and each order is associated with a specific user. This relationship is depicted by the line connecting
UsertoOrder. -
User to Position: A user, in the capacity of a borrower, can open multiple positions. Each position is linked to a user as the borrower. This relationship is represented by the line connecting
UsertoPosition. -
Order to Position: An order can be associated with multiple positions when different borrowers borrow assets from the same order. This relationship is shown by the line linking
OrdertoPosition.
Orderbook's rules
See white paper for explanations.
- Taking an order liquidates all positions which borrow from it
- Removing is limited to unborrowed assets, it cannot liquidate positions on the order book
- Users cannot borrow assets from orders which serve as collateral
- Users whose assets are borrowed cannot use the same assets as collateral to borrow
- Taking a collateral order has the effect of closing the maker's borrowing positions
- Orders cannot be taken at a loss. A price oracle is pulled before any taking to check the condition
- Orders which assets are taken are automatically replaced the opposite side of the order book.
Minimal deposit size and a minimal non-borrowable assets for orders
We want arbitragers to have minimal incentives to take an order when the limit price is crossed. Available assets to take are deposited assets minus borrowed assets. Available assets should always be at least equal to minmal deposit. Reducing available assets in an order can be done three ways, each under different conditions:
withdraw:
- Has order lent assets?
- No: is withdraw full?
- Yes: no condition
- No: remaining assets >= minimum deposit
- Yes: remaining assets >= minimum deposit
- No: is withdraw full?
borrow:
- remaining assets >= minimum deposit
take:
- Are all available assets taken?
- Yes: no condition
- No: remaining assets >= minimum deposit
Price feed
A price feed is pulled when:
- a borrowed order is taken to check that the order is not taken at a loss
- a borrower is liquidated
Excess collateral
Users can be lenders in one side of the book and borrowers in the other side as long as their excess collateral is positive.
Excess collateral for a user and an asset $X$ is the sum of:
- her assets $X$ deposited in active orders
- minus assets which collateralize her borrowing positions in $Y$
- minus assets that other users borrow from her orders
Excess collateral must be positive for all users at any time. It can be used to:
- borrow more assets $Y$
- let other users borrow more assets $X$ from the user
as long as it remains positive.
Removal is limited to excess collateral. To be fully removed, an order must satisfy:
- all positions borrowing from the order being repaid
- all positions collateralized by the order being repaid
Excess collateral increases when the user deposits more assets, repays a position, or other users repay their borrowing from the user's order. Conversely, a positive excess collateral can be used to remove assets, borrow more assets, or let other users borrow more assets from user's limit orders
Taking an order triggers the following actions:
- all positions borrowing from the order are liquidated (even if taking is partial)
- enough positions collateralized by the order are liquidated, so that excess collateral cannot become negative
Deposit more assets $X$ in the order book or repaying a position, or other borrowers repaying a position borrowing from the user's order increases excess collateral:
- more assets can be borrowed from order
- owner of order can borrow more assets
Interest rate model
General model with examples
- Alice deposits a buy order with 6000 USDC (p = 2000)
- Bob deposits a sell order with 3 ETH (p = 2100)
- Bob borrows 4000 USDC from Alice at date t, interest rate is 10%
- 1 year later, Bob's Borrow is 4400
Case 1. Bob repays his position
- repays 4400 USDC, can take back his 3 ETH from his sell order
Case 2. Alice's buy order is taken first for 2000
- Bob's position is liquidated for 4400
- Alice receives (2000 + 4400)/p = 1 + 2.2 ETH
- Bob's sell order is reduced by 2.2 ETH
Case 3. Bob's sell order is taken first for 3 ETH
- Bob receives 3*p = 6300
- from which 4400 are used to repay his loan
Case 4. Bob increases his borrowing
- increase borrowing position by 400 at date t'
- restarts R_t to R_{t'}
Calculation
The interest rates in the buy and sell markets are set according to a linear function of utilization rates: $r_t = \alpha + (\beta + \gamma) \text{UR}_t + \gamma \text{UR}_t^$, with $\text{UR}_t^$ the utilization rate of the opposite market.
Steps
When a user deposits, withdraws, borrows, repays or liquidates a loan, the protocol:
- call _incrementTimeWeightedRates()
- pull current block.timestamp $n_t$ (in seconds) and computes elapsed time $n_t - n_{t-1}$ since last update
- increment time-weighted rates since origin $\text{TWIR}t = n_1 IR_0 + (n_2 - n_1) IR_1 + ... + (n_t - n{t-1}) IR_{t-1}$
- use $IR_{t-1}$ based on UR valid between $t-1$ and $t$, according to the linear formula
- update total deposits and total borrowings in the affected market
- $UR_t$ and $UR_t^*$ will be used to determine $IR_t$ in the next iteration
In addition, when a user borrows from a limit order, the protocol:
- store the updated $\text{TWIR}_t$ in borrowing position struct
When a borrower repays or closes his loan, or he's liquidated at date $T$, the protocol:
- calculate $DR_t = TWIR_T - TWIR_t = (n_{t+1} - n_t) IR_t + ... + (n_T - n_{T-1}) IR_{T-1}$
- compute interest rate $e^{DR_t} - 1$ thanks to Taylor approximation.
Decrease borrowing
Bob borrows 2000 at 10%. One year later, he pays back 1000:
- interest rate is added to his loan which becomes 2200
- $\text{TWIR}_t$ of his borrowing position is updated to TWIR$_T$
- pays 1000 from 2200
- debt is now 1200
Increase borrowing
Bob borrows 2000 at 10%. One year later, he borrows 1000 more:
- interest rate is added to his loan which becomes 2200
- $\text{TWIR}_t$ of his borrowing position is updated to $\text{TWIR}_T$
- 1000 is added to 2200
- debt is now 2200
Partial closing
Bob borrows 2000 at 10%. One year later, his own limit order which serves as collatera is taken. His position is reduced by 1000
- interest rate is added to his loan which becomes 2200
- debt is now 2200
- part of the collateral taken is seized for 1000/p to reduce his debt by 1000
Interest-based liquidation
Borrowing positions is closed out when the limit order from which assets are borrowed is taken, but also when the borrower runs out of collateral to pay a growing interest rate. See white paper for details.
When all remaining collateral is exhausted by the interest rate, the maker/lender can seize the collateral and collect a 2% fee.
Example: Bob borrows 2000 at 10%. One year later, his position is liquidated:
- interest rate is added to his loan which becomes 2200
- debt is now 2200
- his collateral is seized for 2200/p
Steps:
- check that the order is not profitable, if so call take() instead of liquidate()
- check liquidate() is called by maker
- check borrower's excess collateral is zero or negative
- pull price feed to calculate how much collateral to seize and transfer to maker
Self-replacing orders
Orders which assets are taken are automatically replaced on the other side of the book.
The new limit price is chosen by the maker and by default is set + 10% if the order is a buy order and - 9% if a sell order. The paired price is necessarily higher (lower) than the current limit price if the order is a buy order (sell order).
When a user makes a new order, she specifies 2 limit prices: current limit price and a new attribute uint256 _pairedPrice. When order is taken, liquidity receives by maker (from taking and liquidations) after deduction of liquidty used to close maker's own position, is automatically reposted in a new order, the other side of the book and for which limit price is previous paired price and paired price is previous limit price.
Example: Alice deposits 3800 USDC and places a buy order at 1900 USDC. She specifies a dual limit price at 2000 USDC. Once filled at 1900, the converted assets (2 ETH) are automatically reposted in a sell order at 2000 USDC. If the price reverts to 2000 and her sell order is taken, her profit is 4000 - 3800 = 200 USDC. The USDC are automatically reposted in a buy order at 1900.
For orders which assets are not borrowed, the replacement applies to the part of the assets taken. If orders have part of their assets borrowed, the associated collateral is replaced in the paired order, after all borrowing positions have been liquidated.
A (non) borrowable order filled and replaced on the other side of the book is still (non) borrowable.
Consider the case of someone who has a borrowing position B in token X collateralized by an order A in token Y. If order A is filled, position B is first closed before any token X received in exchange of Y from the filling can be replaced in the other side of the book.
Change limit price
Allows Maker to change the limit price of their order
If the order is borrowed, the change takes effect after the borrowing is paid back. Only allows replacing further away from current price.
Non borrowable orders
Users are given the choice to make their orders non borrowable
Makers can choose to make their order non borrowable when the order is placed or at any time during the life of the order. If the order is made non-borrowable while its assets are borrowed, the order becomes non-borowable after the borrowing is repaid.
What's inside
7 sections covering actors, order types, UML data model, rules, interest rate model, self-replacing orders, and price feed
Change this for your project
- Replace
LendBook/Borrowable-limit-order-bookwith your own repository name - Replace
llob_wp.pdfwith your own white paper filename - Replace
Images/lendbook_ulm.pngwith your own UML diagram path
Where it goes
Keep in docs/ or alongside the feature. Agents read it to implement against a defined contract.
Worth borrowing
- Self-replacing orders that flip to opposite side with paired price
- Excess collateral calculation for cross-side borrowing and lending
- Time-weighted interest rate accumulation with Taylor approximation for compounding
Related Documents
GPU Selection Guide for Large Language Models (LLMs)
Guides GPU selection for LLM inference, fine-tuning, and training by mapping model sizes, precision levels, and budgets to VRAM requirements.
Community AI Agent Skills Discovery Sources
Catalogs 50+ platforms, repositories, directories, and communities for discovering and sharing AI agent skills across multiple coding tools.
ReleaseKit - Technical Requirements Document
Specifies a Go library and CLI for release automation with conventional commit parsing, validation checks, and workflow orchestration.
api_llm Specification
Defines a workspace of thin HTTP API clients for major LLM providers with no abstraction layer and explicit developer control.