> For the complete documentation index, see [llms.txt](https://docs.usegimbal.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.usegimbal.app/mechanics/offers-and-origination.md).

# Offers, requests and origination

Every field of a LendOffer and a BorrowRequest, how the nonce bitmap and the fill ledger work, what the relayer proposes, and the exact order of checks inside originate.

Two signed messages meet in one transaction. Lenders sign a `LendOffer`, borrowers sign a `BorrowRequest`, and the desk verifies both before a token moves. Signing is free, cancelling is one cheap call, and settlement verifies every message before spending anything. Blend, NFTfi and Seaport-style exchanges share the pattern: a limit-order book for lenders, where placing or withdrawing an order costs no gas.

## The signing domain

Both messages are EIP-712 typed data. The domain name is `Gimbal`, the version is `1`, the chain ID is 4663, and the desk is the verifying contract. The primary types are `LendOffer` and `BorrowRequest`. After a chain split the desk recomputes the domain separator, so signatures from the other fork fail here; `offerHash` and `requestHash` return the digests a wallet signs.

## The lend offer

```solidity
struct LendOffer {
    address maker;           // the lender whose signature is checked
    Side    side;            // uint8 on the wire; 0 = Lend is the only fillable value
    address collateralToken; // exact token, or address(uint160(tier)) for a tier
    address loanToken;       // USDG
    uint256 principalMin;    // smallest fill, unless the fill drains the offer
    uint256 principalMax;    // total lendable across all fills
    uint16  aprBps;          // the fixed rate, in basis points
    uint16  maxLtvBps;       // highest request LTV the lender will fund
    uint32  termSeconds;     // must equal the request's term
    uint40  expiry;          // last valid second
    uint256 nonce;           // handle in the maker's cancellation bitmap
    bytes32 salt;            // makes the digest unique
    bytes32 requestId;       // zero, a request digest, or a rollover key
    uint8   flags;           // see the flag bits below
}
```

`maxLtvBps` is the maker's own bound; the tier ceiling is tested separately, so a value above it changes nothing. `nonce` is a cancellation handle, not a sequence number: a fill leaves it alone, and several offers may share one nonce to be retired together.

### Targeted and standing

`requestId` fixes where an offer can be used. A targeted offer carries one request's digest and fills nothing else. A standing offer carries zero and points `collateralToken` at one token or at a tier's sentinel address; a request whose terms fit can draw on it, repeatedly, until `principalMax` is used up. A third value, `OfferHash.rolloverKey(loanId)`, restricts an offer to one loan's rollover. Standing offers give the book its depth: a professional lender with a standing offer against every Tier A token is matched with borrower after borrower without signing again.

### The flag bits

Bit 0, `FLAG_SELF_LIQUIDATE`: at liquidation the lender is paid in collateral at the sentinel price before any sale, capped at the slice's pro rata share of the escrow; for lenders who want the stock.

Bit 1, `FLAG_NO_CLOSED_MARKET_LIQUIDATION`: when an auction opens while the equity market is closed, the slice moves into a fresh loan that keeps running, gap risk instead of a weekend sale. If every slice carries the bit, no closed-market auction can open.

Bit 2, `FLAG_PARK_IDLE`: unmatched USDG waits in the whitelisted Morpho vault behind the parking adapter; the desk withdraws exactly the fill at origination or at a rollover acceptance and repays the lender into the vault ([Capital parked between fills](/mechanics/parked-capital.md)).

## The borrow request

A request names the `borrower`, `collateralToken` and `collateralAmount` for escrow, `loanToken` and `principal`, `maxAprBps`, `termSeconds` from the allowed terms, `fillDeadline` (the launch window, after which no fill is possible) and a `salt`. The signature matters only when someone else submits; a borrower calling `originate` directly needs none. `cancelRequest` withdraws a signed request by spending its digest, exactly as a fill would.

## Signature schemes

The desk verifies through OpenZeppelin's `SignatureChecker`: ECDSA from an externally owned account, or an EIP-1271 approval from a contract wallet. The second is essential: Robinhood Chain supports ERC-4337 and EIP-7702 accounts, so many wallets are contracts.

## Cancelling, expiring and filling in parts

`cancel(nonce)` sets one bit in the maker's bitmap and kills every offer carrying that nonce; `cancelWord(wordPos, mask)` sets many bits across the 256 nonces from `wordPos * 256`. Both are immediate and idempotent and emit `OfferCancelled` or `OfferWordCancelled`.

`filled[digest]` records the principal drawn against each offer and `remainingCapacity(offer)` returns what is left: a standing offer with `principalMax` set to 50,000 USDG that lends 10,000 today can still lend 40,000 until expiry or cancellation. Each fill emits `OfferFilled` with the running total; a fill booked in a rollover that never clears is handed back through `OfferReleased`. Past `expiry` an offer stops verifying; no transaction is needed.

## What a relayer does with all this

A relayer, a stateless REST and WebSocket service, holds unfilled offers. It keeps signed offers and requests, validates them against the chain (balance, allowance, eligibility, nonce, expiry), answers queries narrowed by token, tier, term, APR or LTV, and proposes a fill for a request. It has no special power: anyone can run one, every copy of the book is the same set of signed messages, and the indexer mirrors it. See [Services off the chain](/architecture/services.md).

Matching is deterministic. The relayer keeps offers on the lend side that match the request's `loanToken` and `termSeconds`, name its `collateralToken` outright or through the tier sentinel, quote an `aprBps` within the borrower's ceiling, carry a `maxLtvBps` at least the request LTV, and are still live (unexpired, uncancelled, capacity left); sorts them by APR, lowest first; and draws them down until the principal is met. The borrower sees the syndicate, the blended APR and each slice's terms before signing. On-chain, order matters too: the desk fills offers as supplied.

## Inside originate, in order

`originate(request, borrowerSignature, offers, signatures)` returns the new loan id. Any failure reverts everything.

1. New loans must not be paused (`NewLoansPaused`).
2. The clock must not be past `fillDeadline`, the launch window (`RequestExpired`).
3. `principal` and `collateralAmount` must be non-zero (`ZeroAmount`).
4. The request digest must be unused; it is marked used now (`RequestAlreadyUsed`).
5. A caller other than the borrower needs the `RELAYER` role and a valid `borrowerSignature` (`BadRequestSignature`).
6. The borrower must hold `BORROWER`.
7. Policy checks needing no price: the term is allowed (`TermNotAllowed`), both tokens are enabled (`TokenNotEnabled`), the loan token matches the collateral feed's quote token (`LoanTokenMismatch`), and the exposure cap has room (`ExposureCapExceeded`).
8. The sentinel refreshes the price and records a move-cap checkpoint. A paused feed, a zero valuation, a stale feed in a live session (`OracleUnusable`) or the window after a sequencer restart (`SequencerGrace`) rejects the request. A closed session or a stale quote while closed lowers the tier ceiling by the closed-market haircut; the request LTV, principal over collateral value, must not exceed it (`LtvTooHigh`).
9. The loan is written: new id, `Active`, start now, maturity at start plus term.
10. Offers and signatures must match in length (`LengthMismatch`). Then, per offer in order: authenticate (`OfferExpired`, `OfferCancelledError`, `BadOfferSignature`); check side (`OfferSideNotSupported`) and fit on tokens, term, rate, LTV bound and `requestId` (`OfferMismatch`); refuse it once the request is covered (`OverFilled`) or the offer is spent (`OfferExhausted`); fill the smaller of capacity and shortfall, subject to `principalMin` (`FillTooSmall`); require a lender role on the maker; book the fill (`OfferCapacityExceeded`); pull the principal from the vault when bit 2 is set and an adapter is wired, else from the maker's allowance; mint the slice within the 32-per-loan cap (`TooManySlices`), fixing its minimum interest.
11. Fills must sum to the requested principal exactly (`UnderFilled`).
12. Exposure rises and the collateral is pulled into escrow. Coming after the fills, this means an uncoverable request never touches the borrower's balance.
13. The origination fee, `originationBps` of principal, goes to the treasury; the rest goes to the borrower.
14. `LoanOriginated` reports the loan id, borrower, tokens, collateral, gross principal and maturity; each slice was already reported by `SliceCreated`.

Batch signature verification is the heaviest part of the call; a Stylus verifier written in Rust sits on the roadmap to cut it, and [The contract set](/architecture/contracts.md) has the current picture.

## A worked fill

A borrower wants 20,000 USDG for 30 days against 250 NVDA Stock Tokens, paying up to 9.00% APR. At 176.40 per NVDA the 250 tokens are worth 44,100 USDG: a request LTV of 45.4%, inside the Tier A ceiling of 55%. Three standing offers fit:

| APR   | Capacity    | LTV bound | Lender        |
| ----- | ----------- | --------- | ------------- |
| 8.50% | 5,000 USDG  | 50%       | 0x71c3...9e4a |
| 8.90% | 10,000 USDG | 55%       | 0xb02f...17d3 |
| 9.00% | 5,000 USDG  | 50%       | 0x4e88...c0b1 |

Weighted by principal, the blended APR is 8.825%. One `originate` mints three slice tokens and pays the borrower 19,950 USDG, 20,000 less the 0.25% origination fee. With Tier A's liquidation LTV of 70%, the health factor works out as 44,100 × 0.70 = 30,870 over 20,000, or 1.54.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.usegimbal.app/mechanics/offers-and-origination.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
