How lenders discover offers
Covenants enforce rules on-chain. The indexer watches the chain and serves data to wallets and the demo UI.
Why not read the chain directly?
A wallet could scan every Liquid transaction. In practice that is slow, expensive, and error-prone. The lending indexer:
- Recognizes protocol transactions by structure and covenant hashes
- Maintains a database of offers, statuses, and tracked UTXOs
- Exposes a REST API so apps list pending offers and show “your loans”
The indexer does not hold private keys or sign transactions. Wallets still build and broadcast covenant spends; the indexer catches up afterward.
Two layers of trust
| Layer | Who | What it guarantees |
|---|---|---|
| On-chain | Simplicity covenants | Only valid witness paths can move funds |
| Off-chain | Indexer | Correctly identifies and tracks protocol txs; serves queryable state |
When reviewing an indexer client (e.g. HTTP bindings in a wallet library), ask: does it parse the same fields the API returns, and does it treat indexer data as hints while the chain remains authoritative?
Offer discovery and verification
Official docs (Pre-lock / parameter tokens)
From the docs, a valid offer-initialization tx must match a template (input/output counts, OP_RETURN with BIP340 pubkey, parameter UTXOs on inputs 0–1). The indexer then:
- Extracts parameters from the transaction
- Recompiles the Pre-lock covenant
- Compares the resulting CMR (script hash) to output 0
If they match, the tx is a genuine offer — not a lookalike with fake terms.
Reference implementation (LendingOffer)
In simplicity-lending, discovery uses LendingOffer::try_from_tx: parse OP_RETURN metadata, validate output layout, confirm covenant address. Offer creation is only attempted when the same transaction also spent a known IssuanceFactory (factory issued the role NFTs first). Detailed walkthrough →
LendingOffer and pending_offer UTXOs. Trust the implementation and tests when reviewing.
Per-block pipeline
For each block, the indexer worker (block processor):
- Fetches block txs from Esplora
- For each transaction, runs trackers in order:
Status transitions (reference impl)
| Spend of… | Tx pattern | New status |
|---|---|---|
pending_offer | Cancellation outputs | cancelled |
pending_offer | Otherwise (acceptance) | active |
active_offer | Repayment outputs (5+ outs, pattern) | repaid |
active_offer | Otherwise | liquidated |
repayment vault | Lender claim | claimed |
Full status list and API fields: Indexer API reference.
What wallets consume
The demo web app reads the indexer for listings and details, then uses LWK to sign covenant transactions. Typical flow:
GET /offers?status=pending— lender browses open offersGET /offers/{id}— fetch NFT asset ids, UTXOs, amounts for tx buildingGET /borrowers/offers?script_pubkey=…— borrower sees their loansGET /factories/by-script?script_pubkey=…— borrower finds factory before creating offer
Interest is quoted in basis points (1000 = 10%). Amounts are decimal satoshi strings.
Check your understanding
1. What verifies a new offer creation tx is genuine?
2. When does the indexer try to index a new offer?
3. Spending a pending-offer UTXO updates status to?
Try: “what’s the difference between repaid and claimed?” or “how would I test an indexer client without mainnet?”