Request a borrow
Anyone can callrequestBorrow() to lock collateral and open a borrow request with a rising interest rate curve. Lenders accept by calling lend(); until then the borrower can cancel and recover the collateral.
Review Supported tokens before using a token pair: openLend’s and openOracle’s accounting is only compatible with the token classes listed there.
uint48
Loan duration in seconds, measured from the moment a lender accepts. Bounded [30 minutes, 1 year].
address
Collateral token.
address(0) means native ETH. Must differ from borrowToken.address
Borrow token.
address(0) means native ETH. Must differ from supplyToken.uint24
Fraction in 1e7 fixed-point (8e6 = 80%). Liquidation becomes possible when debt valued in
supplyToken exceeds liquidationThreshold * supplyAmount / 1e7. Bounded [3e6, 1e7].uint128
Collateral amount transferred from
msg.sender into the contract on this call.uint128
Borrow amount requested. Pulled from the lender to the borrower at accept time.
uint16
Liquidator stake fraction in 10000 fixed-point (100 = 1%). The liquidation caller wagers
stake * supplyAmount / 10000 of supplyToken on the liquidation succeeding.uint24
Minimum interest floor in 1e7 fixed-point (1e7 = 100% of full-term interest). Close-out interest =
max(commitmentFraction * fullInterest, accruedInterest). Minimum interest is counted against the borrower immediately for the purpose of liquidations: debt is calculated as principal plus the greater of accrued interest and commitment interest. Immutable for the loan’s life.uint96
ETH escrowed to pay the lender who accepts this request. Refunded to the borrower if the request is cancelled or the loan terminates with the curve still open.
address
Address recorded as borrower. Collateral is still funded by
msg.sender. The position is owned by the borrower: collateral is always returned to this address, and only it can cancel the request or open a refinance. Make sure you control the address passed here.address
Optional contract authorized to call
refinance() on the borrower’s behalf. This delegate is fully trusted: it can stage refinances with arbitrary terms, including new rate curves, terms, and oracle parameters. Only set a contract you trust completely. Pass address(0) for none. Changeable or revocable later by the borrower via setRefiDelegate().Oracle parameters
Oracle game configuration applied to any future liquidation of this loan. See the oracle contract reference (testnet) for guidance on how to play the oracle game, and Price oracle for the full mechanics of the game openLend creates:uint48
openOracle settlement window in seconds. A liquidation price report settles after this long without a dispute. Bounded [120, 14400].
uint24
openOracle dispute delay in seconds. Must be less than
settlementTime.uint24
Per-round fee on oracle game takers, 1e7 = 100%. Routed to a fee receiver and split between borrower, lender, and liquidator. Bounded [0, 1e6].
uint16
Sets the oracle game’s escalation halt:
escalationHalt = supplyAmount * escalationFactor / 100. Bounded [10, 5000], and must be at least initialLiquidity.uint8
Initial token1 size of the oracle report as a percentage of
supplyAmount. Bounded [5, 250].uint16
openOracle per-round dispute multiplier, 140 = 1.4x. Bounded [100, 1000].
uint48
Optional basefee gate: a liquidation only finalizes against the borrower when
block.basefee is at or below this value, scaled by the oracle game’s growth. 0 disables the gate.uint64
Fixed ETH reward the liquidator escrows at liquidate time, paid to whoever finalizes the liquidation. This helps avoid the situation where the oracle game has finished but nobody finalizes the loan, leaving the outcome unapplied while interest keeps accruing. It is a non-refundable liquidation expense, so setting this value too high is reflected in a lender’s minimum demanded interest rate.
Thinking about oracle parameterization
Disputes in the oracle game cost roughly 50–150k gas, so you want enough initial liquidity in the game (initialLiquidity * supplyAmount / 100) that correcting a mispricing stays profitable even during gas spikes. An alternative is a longer settlementTime, which allows gas spikes to calm down before the report can settle. If initial liquidity is too high, liquidation becomes more expensive, because the liquidation price report is itself two time-locked limit orders at that size. A lender may therefore require a much higher interest rate to loan against high amounts of initial liquidity relative to the equity buffer capturable by liquidation.
A settlementTime of 20+ minutes is generally preferred, with a disputeDelay not much longer than 2–3 minutes.
Part of oracleGameFee is routed to you as the borrower, but a larger fee share decreases oracle accuracy, which may have adverse effects on you in liquidation or, if large relative to the equity buffer, may result in lenders demanding a higher rate.
multiplier between 1.5x and 2x is preferred. Too low a multiplier and the oracle game’s ability to punish delay is hurt; too high a multiplier and the price error required to dispute increases by too much (roughly, a price error of settlementTime volatility times multiplier is required for a dispute to be profitable to a third-party arbitrageur).
escalationFactor sets the point at which oracle disputes can continue but sizes stop growing. Generally it is preferred to set this as high as you can stomach, but if you want to keep the oracle game at a size where you can always participate and defend yourself, you can set it lower. Too low and the oracle game is not punishing enough for manipulators.
Interest rate curve
Curve parameters for rate discovery. The offered rate at round k ismin(maxRate, startingRate * (growthRate/10000)^k), where rounds advance every roundLength seconds from the request. The first lender to accept locks in the offered rate at that moment, wherever it sits on the curve, and that rate is fixed for the life of the loan:
uint32
Hard ceiling on the rate, 1e8 = 10% APR. Note the interaction with
commitmentFraction: minimum interest is counted against the borrower immediately for the purpose of liquidations, and it is priced at the rate the lender locks in, so a loan accepted high on the curve is born with a larger liquidation debt. A high maxRate combined with a high commitmentFraction can put a fresh loan much closer to its liquidation threshold than the raw loan-to-value may suggest.uint32
Rate at round 0, 1e8 = 10% APR. Must not exceed
maxRate.uint24
Seconds per round. Larger means a slower curve.
uint16
Per-round growth, 10500 = 1.05x. Must be greater than 10000.
uint16
Hard cap on rounds. Bounded [1, 100].
supplyToken is native ETH, msg.value must equal supplyAmount + gasCompensation; otherwise msg.value must equal gasCompensation and the collateral is pulled from msg.sender as an ERC-20.
Cancel a borrow request
Until a lender accepts, the recordedborrower can cancel through cancelBorrowRequest(). Cancelling returns the full collateral to the borrower and refunds the escrowed gas compensation. A cancelled lending id is permanently closed.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().Refinance a loan
The borrower (or a registered refi delegate) opens a refinance throughrefinance(). This stages new terms and re-opens the rate curve; the actual roll happens when a lender accepts via lend(). At acceptance, the new lender pays the previous lender the close-out debt, funds any extra borrow, and any pulled collateral is returned to the borrower. The new loan’s term starts from the moment of acceptance, and the rate is wherever the refi auction’s curve sits at that time.
The close-out debt paid to the previous lender includes at least the commitment interest, so refinancing inside the prepayment penalty window pays the same floor as a full repayment.
Refinancing is allowed only on an active loan with no refi auction already open. It is also allowed during a live liquidation: the staged curve is parked, and it starts ticking from the oracle game’s settleable-at time (more than settlementTime has passed since the last oracle price report) if the liquidation fails.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().uint128
Additional borrow token pulled to the borrower at acceptance, on top of rolling the existing debt.
uint128
Supply token returned to the borrower at acceptance. Must be less than the current collateral.
uint48
Term for the post-refi loan in seconds, measured from lender acceptance. Pass 0 to keep the existing term length. If nonzero, bounded [30 minutes, 1 year].
uint96
ETH escrowed to pay the lender who accepts the refinance. Must equal
msg.value. Refunded to the borrower if the refinance is cancelled or the loan terminates with the curve still open.InterestRateParams
New curve parameters governing the refi auction, applied immediately at staging. Pass
maxRate = 0 to keep the existing curve. Fields as defined in Request a borrow.OracleParams
Optional new oracle game configuration for the post-refi loan, validated now and applied at acceptance. Pass
settlementTime = 0 to keep the existing configuration. Fields as defined in Request a borrow.bytes32
Optional hash pinning the loan’s configuration. Pass
bytes32(0) to skip. If the loan’s terms change between transaction broadcast and inclusion, the hash no longer matches and the refinance reverts instead of staging under the new terms. See Calculating the param hash for how to calculate it.uint128
Reverts if the loan’s collateral is below this value.
uint128
Reverts if the loan’s principal is above this value.
extraDemanded, and the borrower receives extraDemanded of borrow token and supplyPulled of supply token.
Cancel a refinance
Until a lender accepts, the recordedborrower can close the refi auction with cancelRefinance(). The staged terms are cleared, the escrowed gas compensation is refunded, and the live loan continues unchanged on its existing rate, term, and start.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().Repay debt
Borrowers repay throughrepayDebt(). Partial payments are streamed to the lender immediately, satisfying interest first and then reducing principal. If the payment covers the full close-out debt, the loan finishes and the collateral is returned to the borrower. The close-out debt is principal plus the greater of accrued interest and commitment interest, minus interest already paid.
Repayment reverts during a live liquidation, after maturity plus any grace period, or once the loan is finished or cancelled.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().uint128
Borrow token amount to apply against debt. May exceed the close-out debt: only what is owed is pulled for ERC-20s, and excess native ETH is refunded.
bytes32
Optional hash pinning the loan’s configuration. Pass
bytes32(0) to skip. If the loan’s terms change between transaction broadcast and inclusion (for example a staged refinance executes), the hash no longer matches and the repay reverts instead of paying into the new terms. See Calculating the param hash for how to calculate it.uint128
Reverts if the loan’s collateral is below this value.
uint128
Reverts if the loan’s principal is above this value.
bool
Reverts if the repayment does not fully close the loan. Pass
true with a small overpay when you intend a full repayment: interest accruing between transaction broadcast and inclusion then cannot silently downgrade it to a partial payment.repayAnyDebt() is the permissionless variant with identical parameters and gating. Anyone can repay any loan’s debt, the caller supplies the borrow token, and the collateral still returns to the recorded borrower on a full repayment.
Token flows
To repay, the call must be funded with:
borrowToken is native ETH, amount rides in msg.value and any excess above the close-out debt is refunded; otherwise only the amount owed is pulled from msg.sender as an ERC-20.
Top up collateral
Borrowers add collateral throughtopUpCollateral(). Topping up widens the buffer between debt and the liquidation threshold.
Top-ups revert during a live liquidation, during a grace period from a failed liquidation, after maturity plus any grace period, or once the loan is finished or cancelled.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().uint128
Supply token amount to add to the loan’s collateral.
bytes32
Optional hash pinning the loan’s configuration. Pass
bytes32(0) to skip. If the loan’s terms change between transaction broadcast and inclusion, the hash no longer matches and the top-up reverts instead of adding collateral under the new terms. See Calculating the param hash for how to calculate it.uint128
Reverts if the loan’s collateral before the top-up is below this value.
uint128
Reverts if the loan’s principal is above this value.
topUpCollateralAnyone() is the permissionless variant with identical parameters and gating. Anyone can add collateral to any loan, with the caller supplying the tokens.
Token flows
To top up, the call must be funded with:
supplyToken is native ETH, msg.value must equal amount exactly; otherwise the collateral is pulled from msg.sender as an ERC-20 and msg.value must be zero.
Delegation
setRefiDelegate() registers or revokes the contract allowed to call refinance() on the borrower’s behalf. The delegate is fully trusted, as described under Request a borrow. Borrower-only.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().address
Contract to authorize. Pass
address(0) to revoke.Finalize and autoSettle
A liquidation resolves once its oracle game can no longer be disputed. Anyone can callfinalize() at that point. The existing-loan entrypoints, lend, repay, top-up, refinance, cancel refinance, and claim collateral, also carry an autoSettle step that resolves a settleable liquidation before the body runs, so your own repay, top-up, or refinance never needs a separate finalize first.
The settled oracle price is fixed when the game ends, but debt keeps accruing until finalization actually lands: the liquidation outcome compares the threshold against debt measured at finalize time. Waiting to finalize can therefore flip a position that was healthy at settlement into a successful liquidation, or shrink the equity buffer returned from one. This is the point of finalizerReward: it pays whoever finalizes so resolution happens promptly. You can also call finalize() yourself and collect the reward.
If the liquidation fails, the liquidator’s forfeited stake is added to your collateral. If the resolution lands within the last 30 minutes of the term or after maturity, half the stake goes to the lender instead and a grace period is granted. The grace clock is anchored at the game’s settleable-at time, not at when the finalize transaction lands.
uint256
Unique identifier of the lending arrangement, returned by
requestBorrow().Recover held tokens
When an outgoing token transfer from openLend fails, for example because the recipient is blacklisted by the token, the amount is credited to a per-recipient escrow instead of blocking the operation.getTempHolding() retries the transfer for the caller; if the retry also fails, the amount is re-credited. Balances are readable via tempHolding(recipient, token).
address
Token to withdraw from the caller’s escrow balance.
Receiving WETH
openLend pays native ETH with a capped amount of gas. If the direct send fails, for example because the recipient is a contract that rejects plain ETH transfers or needs more gas than the cap forwards, the payment does not revert: the amount is wrapped and sent as WETH instead. If an expected ETH payment did not arrive, check your WETH balance. This applies to every native ETH payout, including loan proceeds, collateral returns, gas compensation, and refunds.Oracle game fees
Each disputed round of a liquidation’s oracle game chargesoracleGameFee on takers, and half of the collected fees are routed to you as the borrower. Collection goes through the loan’s per-liquidation fee receiver. See Fee receiver.
Calculating the param hash
repayDebt(), topUpCollateral(), and refinance() all accept a param hash that pins the loan’s configuration: if the loan’s terms change between transaction broadcast and inclusion, the hash no longer matches and the call reverts instead of executing against terms you didn’t review.
The hash is keccak256(abi.encode(arrangement)) over the loan’s full LendingArrangement, with the volatile fields zeroed. The zeroed fields are the ones that legitimately drift (amortization state) or that a pending liquidation settling can mutate (liquidation lifecycle state), so a hash computed from a pre-settle read stays valid across the settle. Those fields are instead guarded live at execution by the other transaction parameters, such as expectedMinSupply, expectedMaxPrincipal, and minRate.
Read the arrangement from lendingArrangements(lendingId) on openLend, or getLending(lendingId) on the data provider, then compute:
OracleParams and InterestRateParams are as defined in the Request a borrow section above.
Compute the hash yourself from the arrangement you actually read and reviewed. If the state you read is wrong or hostile, the hash you pass won’t match the real arrangement and the transaction reverts, which is the failure mode you want. liquidate() uses a stricter variant of this hash, covered in the liquidator reference.