Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The lending protocol

πŸ“ Draft. This chapter has not been reviewed yet β€” content may be incomplete or change.

Problem. Two strangers want to transact a collateralised loan with no escrow agent and no trusted backend. A borrower locks collateral and advertises terms; a lender supplies the principal; the loan later settles by repayment or β€” if the borrower defaults β€” by liquidation after a deadline. Every rule is enforced on-chain by Simplicity covenants.

This is the capstone of the cookbook. Everything the recipes introduced one at a time β€” covenant UTXO types, multiple spending paths, asset issuance & NFTs, formulas & derived params, hooks & tapleaf compute, and the template / instance model β€” shows up here at once, wired into a single working protocol.

The full example lives in the repository at examples/lending/: one txmanifest.json plus five .simf covenant programs. This chapter walks through it in the order you'd actually run it.

The deal, in one paragraph

Alice has collateral (say, L-BTC) and wants to borrow L-USDT against it without selling. She locks her collateral into a covenant and publishes her terms β€” amount, interest, expiry β€” as on-chain NFTs anyone can read. Bob sees the offer, likes the terms, and accepts by sending Alice the principal; in the same transaction her collateral moves into a second covenant that holds it for the life of the loan. To get her collateral back, Alice repays principal plus interest before the deadline. If she doesn't, Bob can seize the collateral once the deadline passes. At no point does either party have to trust the other or any third party β€” the covenants only permit the honest transitions.

The cast

Two roles. The protocol has a borrower and a lender. They are different people with different wallets, so when you run it you'll keep two wallet files β€” borrower.json and lender.json β€” and run each action as the appropriate party.

Five covenant programs. Each .simf file is a small Simplicity program that gates one kind of UTXO. They are deliberately tiny and composable:

ProgramRole
pre_lock.simfHolds the collateral while the offer is open. PATH::LEFT lets a lender activate the loan; PATH::RIGHT lets the borrower cancel (with a signature).
lending.simfHolds the collateral during the active loan. PATH::LEFT is repayment; PATH::RIGHT is liquidation after expiry.
script_auth.simfWraps each NFT so it can only be spent co-spent with the right collateral covenant. The glue that binds the NFTs to the deal.
asset_auth.simfGuards the lender's principal vault: release requires burning the Lender NFT.
p2pk.simfThe borrower's plain Schnorr payout address β€” the Hello World program, reused for where the principal lands.

Four NFTs. The protocol mints four single-unit Liquid assets at construction. Two are bearer auth tokens (whoever holds it can act); two encode the loan terms in their amount field so the offer is self-describing on-chain:

NFTCarriesUsed for
Borrower NFTnothing (amount = 1)proves a transaction is the borrower's; co-spent in setup and repayment
Lender NFTnothing (amount = 1)the lender's bearer token; needed to liquidate and to drain the vault
First Parameters NFTinterest rate, expiry, decimals β€” bit-packed into its amountpublishes the loan terms; checked by every covenant
Second Parameters NFTcollateral & principal base amounts β€” bit-packed into its amountpublishes the amounts; checked by every covenant

The lifecycle

The whole protocol is one lending_contract contract template, and its actions walk an instance through a sequence of states:

StateReached byMeaning
nfts_issuedIssueUtilityNFTsThe four NFTs exist; terms are encoded.
offer_openLockCollateralCollateral and NFTs sit behind pre_lock.
cancelledCancelOfferThe borrower withdrew before a lender took it.
loan_activeSetupLendingA lender funded the offer.
repaidRepayLoanThe borrower paid principal plus interest.
liquidatedLiquidateAfterExpiryThe deadline passed; the lender took the collateral.
settledClaimPrincipalWithInterestThe lender drew the repayment from the vault.

These state names are ours, not the format's. A manifest has nowhere to declare a state machine β€” no states list, no from/to on an action. The table above is prose, written to make this chapter readable.

What actually constrains the order is the chain. Each action's inputs name UTXO types that only the previous action creates, so SetupLending cannot run before LockCollateral has produced a pre_lock output to spend. Read the sequence off the covenants, which enforce it, rather than off a table, which cannot.

Rendered, those transitions are the protocol's flow:

                    IssueUtilityNFTs
                          β”‚   (borrower mints 4 NFTs + computes covenant hashes)
                          β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ nfts_issuedβ”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚   LockCollateral  (borrower)
                          β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   CancelOffer (borrower, unilateral)
                    β”‚ offer_open β”‚ ─────────────────────────────►  cancelled
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚   SetupLending  (lender accepts)
                          β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚loan_active β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚        β”‚
        RepayLoan      β”‚        β”‚   LiquidateAfterExpiry
     (borrower,        β”‚        β”‚   (lender, unilateral,
      cooperative)     β–Ό        β–Ό    after LOAN_EXPIRATION_TIME)
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚ repaid β”‚  β”‚ liquidatedβ”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
        ClaimPrincipalWithInterest (lender drains the vault)
                       β–Ό
                    settled

Two of those arrows are unilateral escape hatches β€” CancelOffer and LiquidateAfterExpiry β€” that one party can take without the other's cooperation. That's the whole point of a trustless protocol: the exits don't depend on the counterparty playing along. RepayLoan is the cooperative happy path both sides want. Nothing in the manifest labels them as such; what makes an exit unilateral is that its covenant path needs only one party's key or NFT, which you can read off the action's witnesses.

How this chapter is organised

The walkthrough follows those states across four pages, each building one phase and pulling in the recipes that introduced its pieces:

  1. Issuing the NFTs & encoding the terms β€” the IssueUtilityNFTs constructor: minting four NFTs from issuance inputs, bit-packing the loan terms into Parameter NFT amounts, and computing the web of interdependent covenant hashes that every later step relies on.
  2. Opening the offer β€” LockCollateral puts the collateral and NFTs on-chain behind the pre_lock covenant, with an op_return discovery beacon.
  3. Accepting or cancelling the offer β€” the two spending paths of pre_lock: the lender's SetupLending (with the required_index discipline a covenant demands) versus the borrower's CancelOffer.
  4. Settling: repay, liquidate, withdraw β€” the borrower's RepayLoan versus the lender's LiquidateAfterExpiry on the lending covenant, then draining the principal vault with ClaimPrincipalWithInterest.

Before you run it

The CLI is tx-manifest-wallet, aliased throughout the book to txw. Do the one-time setup and made a wallet first. Because this protocol has two roles, create two wallets and fund both from the testnet faucet:

txw create-wallet --out borrower.json
txw create-wallet --out lender.json
# fund each from https://liquidtestnet.com/faucet, then:
txw sync --wallet borrower.json
txw sync --wallet lender.json

Where's the funding address? Run info on each wallet and copy the receive address it prints, then paste that into the faucet:

txw info --wallet borrower.json   # copy the receive address, fund it, repeat for lender.json

See Fund and sync for the full walk through.

Get oriented with describe and validate before building anything β€” describe prints the contract templates, their fields and actions; validate checks the manifest is internally consistent:

txw describe examples/lending/txmanifest.json
txw validate examples/lending/txmanifest.json

Then start with Issuing the NFTs.

This is the most involved example in the book. If you haven't worked through Hello World and the Last Will covenant yet, do those first β€” they introduce the single-key and multi-path patterns this protocol composes at scale.