Docs

Mempool & Router

Mempool

Everything so far describes a position you already hold: how its health is measured and what happens if it deteriorates. But a position only exists once a borrower and a lender have traded units. Midnight has no order book matching them: instead, makers broadcast signed offers onchain through the mempool, a minimal contract that simply logs each offer as an event (no storage or mapping is performed by the mempool).

The mempool is not a single canonical place. Offers exist entirely offchain so different participants might see different subsets of offers. The competition to find and fill the best offers is the province of routers.

Router

Because offers float offchain with no canonical queue and no protocol-enforced priority, a taker looking for the best rate faces a genuine search problem. The process of discovering, evaluating, and selecting which offers to take is called routing.

Because routing is permissionless, anyone can run one. Morpho operates its own router, which aggregates offers and surfaces executable offers so integrators don't have to build discovery infrastructure themselves.

Validating offers

The Morpho router only indexes offers that pass its rules. Each rule constrains one dimension of an offer. If any rule is violated, Morpho's router ignores the offer.

Rule groupWhat the validation endpoint checks
Payload formatVersion, framing, compression, ABI decoding, duplicate offers, and the offer count.
Chain and market supportChain and deployment identity, allowed maturities, supported loan tokens, nonzero collateral token and oracle addresses, configured loan_collateral conditions, enabled LLTVs and liquidation settings, and collateral count.
Offer size and priceAmount selection and configured minimum offer size, tick range and spacing, duration, and ratifier support.
Batch consistencyShared maker and ratifier, group identity, compatible group terms, and non-overlapping active windows.
CallbacksEmpty-callback policy, supported Blue buy callback content, and maker registration.

The validation endpoint is a pre-flight check: "will the router index this offer?" If validation returns issues, the offer can still exist in the mempool, but the router ignores it.

POST /v0/midnight/mempool/validate

// Check if the router will index your offers
POST /v0/midnight/mempool/validate
{
  "chain_id": 8453,
  "payload": "0x01..."
}

// Response excerpt (other fields omitted; values are illustrative)
{
  "data": {
    "issues": []
  }
}

// Response excerpt (other fields omitted; values are illustrative)
{
  "data": {
    "issues": [
      {
        "rule": "loan_token",
        "details": null
      },
      {
        "rule": "maturity",
        "details": null
      },
      {
        "rule": "tick_spacing",
        "details": null
      }
    ]
  }
}

Validation checks the declared ratifier against the router's policy. It does not authenticate signatures or Merkle proofs in ratifierData; those checks happen later during indexing or onchain execution.

Available offers

The router aggregates all signed & active offers from the mempool that pass its rules in the form of a book (a bid/ask market view). It groups them by tick and sums the units and assets at each tick into a price level (price level = one tick).

To fetch a single book's state run the following query: GET /v0/midnight/books

GET /v0/midnight/books?chain_ids=8453&limit=1

// Response excerpt (other fields omitted; values are illustrative)
{
  "cursor": null,
  "data": [
    {
      "market_id": "0x52b0f65ac6906080cb2100a2ffbef8f81c6c40ec65ebca6b5fc6ad4412ad77cf",
      "chain_id": 8453,
      "midnight": "0x2f7a3aa739ba5792ce1b4ea046117f2c0095bca6",
      "loan_token": "0x4200000000000000000000000000000000000006",
      "collaterals": [
        {
          "token": "0xcbb7...33bf",
          "lltv": "860000000000000000",
          "liquidation_cursor": "250000000000000000",
          "oracle": "0x10b95702a0ce895972c91e432c4f7e19811d320e"
        }
      ],
      "maturity": 1783090800,
      "rcf_threshold": "0",
      "enter_gate": "0x0000000000000000000000000000000000000000",
      "liquidator_gate": "0x0000000000000000000000000000000000000000",
      "asks": [
        {
          "tick": 4736,
          "price": "998890800000000000",
          "units": "666666666666666",
          "assets": "665927200000000",
          "count": 2
        },
        {
          "tick": 4740,
          "price": "998912700000000000",
          "units": "1333333333333334",
          "assets": "1331883600000002",
          "count": 2
        },
        {
          "tick": 4744,
          "price": "998934100000000000",
          "units": "2000000000000000",
          "assets": "1997868200000000",
          "count": 2
        }
      ],
      "bids": [
        {
          "tick": 4732,
          "price": "998868400000000000",
          "units": "667421921312824",
          "assets": "666666666666666",
          "count": 2
        },
        {
          "tick": 4728,
          "price": "998845700000000000",
          "units": "1334874178597690",
          "assets": "1333333333333334",
          "count": 2
        },
        {
          "tick": 4724,
          "price": "998822400000000000",
          "units": "2002357976753426",
          "assets": "2000000000000000",
          "count": 2
        }
      ]
    }
  ]
}

To fetch a book with a specific depth (max price levels per side), run the following query: GET /v0/midnight/books/{market-id}

GET /v0/midnight/books/{market-id}?depth=50

To only fetch the book's ask side, run the following query: GET /v0/midnight/books/{market-id}/{side}

GET /v0/midnight/books/{market-id}/asks

// Response excerpt (other fields omitted; values are illustrative)
{
  "data": [
    {
      "tick": 4736,
      "price": "998890800000000000",
      "units": "666666666666666",
      "assets": "665927200000000",
      "count": 2
    },
    {
      "tick": 4740,
      "price": "998912700000000000",
      "units": "1333333333333334",
      "assets": "1331883600000002",
      "count": 2
    },
    {
      "tick": 4744,
      "price": "998934100000000000",
      "units": "2000000000000000",
      "assets": "1997868200000000",
      "count": 2
    }
  ]
}

Takeable offers

The book endpoint returns only aggregated summaries. A price level tells you "10,000 units are available at price 0.9524". To execute, you need the full Offer struct plus the ratifierData.

That's what a takeable offer is: an individual offer that is currently executable, packaged with everything needed to call the Midnight contract onchain. The quote endpoint returns these individual offers complete with Offer structs and ratifierData ready for onchain submission. You tell it how much you want to trade, a market, a side, a target size, and optional slippage and it searches the mempool, ranks offers by price, and returns a bundle-ready execution plan.

For example, if you want to lend 10,000 USDC and take asks you run the following query: GET /v0/midnight/books/{market-id}/{side}/quote

GET /v0/midnight/books/{market-id}/asks/quote
  ?assets=10000000000
  &slippage=0.5

// Response excerpt (other fields omitted; values are illustrative)
{
  "data": {
    "average_best_price": "998890800000000000",
    "average_worst_price": "1003885254000000000",
    "available_assets": "14983800",
    "available_units": "15000000",
    "takeable_offers": [
      {
        "market_id": "0xa249f0b3ffe8891c96ec93fd9c7d00e479da04e4ffe2f2502ea0e5db8eabc75d",
        "units": "833333",
        "offer": {
          "buy": false,
          "maker": "0x6ba008e3f6ec55dc6412e459ac67949c6d1620c5",
          "max_units": "833333",
          "max_assets": "0",
          "tick": 4736,
          "start": 1782484425,
          "expiry": 1783090799,
          "group": "0x6c544af73868898482d1802005d87e7a4d1100d4e1a6e099a978f6bd2e6cba17",
          "ratifier": "0x1db7b555ce19e85e625428b0e1047da3198211ea"
        },
        "ratifier_data": "0x0000000000000000000000000000000000000000...",
        "valid_until": 1783090799,
        "warning": null
      },
      // more offers
      {...}
    ]
  }
}

Takeability warnings

An offer can sit in the book while only part of it, or none of it, can be taken right now. For example, the maker may have moved the assets backing it, lowered an approval, or revoked something the offer relies on. Makers see the main reason on their own offers, as a warning on GET /v0/midnight/takeable-offers. On the book listing and quote endpoints, which takers use, warning is deprecated: it will become null, then be removed. Takers can rely on units instead. On the book listing, it gives how much of each offer can be taken. On a quote, it's a per-offer cap, and the caps together can exceed the requested size.

How to read a warning

  • warning: null means the offer's complete remaining size can be taken.
  • Some codes limit how much can be taken, possibly down to zero. For these, the offer's units on GET /v0/midnight/takeable-offers shows how much can still be taken.
  • Other codes mean none of the offer can be taken right now, for example because the maker revoked an authorization or canceled the offer. An offer reserved for one taker is the exception: that taker can still take it.

The Takeability warnings section in the API reference lists which codes belong to each group, with their meaning.

How to surface a warning

Your app can turn each code into a short message for makers, on their own offers, with what they can change:

  • MAKER_ALLOWANCE: raise the loan-token approval.
  • MAKER_LIQUIDITY: add liquidity.
  • ROOT_CANCELED: publish a new offer, since the canceled one can never be taken again.

New codes can be added over time. Your app can fall back to a generic message, such as "This offer can't be taken in full right now", for a code it doesn't recognize.