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

Opening the offer

📝 Draft. This chapter has not been reviewed yet — content may be incomplete or change.

Phase 2 of the lending walkthrough. The borrower publishes the offer on-chain: LockCollateral moves the collateral and all four NFTs into covenant UTXOs, advancing the contract from nfts_issued to offer_open.

After issuing the NFTs the borrower holds four tokens and an instance file, but the collateral is still loose in their wallet. This phase commits it — the protocol's first move of value into covenant addresses, and the first use of the op_return destination.

LockCollateral — publishing the offer

LockCollateral takes the collateral and all four NFTs from the borrower's wallet and moves them into covenant addresses. The collateral goes into the pre_lock UTXO; each NFT goes into a prelock_script_auth UTXO (the script_auth.simf wrapper compiled to PRE_LOCK_COV_HASH):

"LockCollateral": {
  "inputs": [
    { "id": "collateral_in",   "utxo_source": "wallet", "asset": "instance.COLLATERAL_ASSET_ID",
      "amount_sat": { "min_amount": "instance.COLLATERAL_AMOUNT" } },
    { "id": "borrower_nft_in", "utxo_source": "wallet", "asset": "instance.BORROWER_NFT_ASSET_ID", "amount_sat": 1 },
    { "id": "lender_nft_in",   "utxo_source": "wallet", "asset": "instance.LENDER_NFT_ASSET_ID",   "amount_sat": 1 },
    { "id": "first_params_in",  "utxo_source": "wallet", "asset": "instance.FIRST_PARAMETERS_NFT_ASSET_ID",  "amount_sat": "instance.FIRST_PARAMETERS_ENCODED" },
    { "id": "second_params_in", "utxo_source": "wallet", "asset": "instance.SECOND_PARAMETERS_NFT_ASSET_ID", "amount_sat": "instance.SECOND_PARAMETERS_ENCODED" },
    { "id": "fee_input", "utxo_source": "wallet", "asset": "lbtc" }
  ],
  "outputs": [
    { "id": "pre_lock_out",       "destination": { "utxo_type": "pre_lock" },            "asset": "instance.COLLATERAL_ASSET_ID", "amount_sat": "instance.COLLATERAL_AMOUNT" },
    { "id": "borrower_nft_locked","destination": { "utxo_type": "prelock_script_auth" }, "asset": "instance.BORROWER_NFT_ASSET_ID", "amount_sat": 1 },
    { "id": "lender_nft_locked",  "destination": { "utxo_type": "prelock_script_auth" }, "asset": "instance.LENDER_NFT_ASSET_ID",   "amount_sat": 1 },
    { "id": "first_params_locked",  "destination": { "utxo_type": "prelock_script_auth" }, "asset": "instance.FIRST_PARAMETERS_NFT_ASSET_ID",  "amount_sat": "instance.FIRST_PARAMETERS_ENCODED" },
    { "id": "second_params_locked", "destination": { "utxo_type": "prelock_script_auth" }, "asset": "instance.SECOND_PARAMETERS_NFT_ASSET_ID", "amount_sat": "instance.SECOND_PARAMETERS_ENCODED" },
    { "id": "indexer_op_return", "destination": { "type": "op_return" },
      "data": "concat(instance.BORROWER_PUB_KEY, instance.PRINCIPAL_ASSET_ID)" },
    { "id": "collateral_change", "destination": "change", "asset": "instance.COLLATERAL_ASSET_ID", "optional": true },
    { "id": "fee_change",        "destination": "change", "asset": "lbtc", "optional": true }
  ]
}

Two things worth pausing on:

  • The OP_RETURN advertisement. indexer_op_return writes concat(BORROWER_PUB_KEY, PRINCIPAL_ASSET_ID) into an unspendable output. It carries no value — it's a beacon so an indexer (or a prospective lender's wallet) can discover the open offer and the asset it wants by scanning for these markers. Outputs & destinations introduced the op_return destination; here it's used for discovery rather than burning.
  • The collateral amount is checked on-chain, and only on-chain. The pre_lock covenant re-derives COLLATERAL_AMOUNT and rejects a spend that does not match. Nothing checks it before the build, so a mis-sized input costs you a failed dry-run rather than a friendly prompt — the covenant is the law, and it is also the only check.

After this broadcasts, the contract is in offer_open: collateral and NFTs sit in covenant UTXOs that only the two pre_lock paths can move.

Run it

LockCollateral is run by the borrower, using the instance file written at construction:

# --- Borrower puts the offer on-chain ---
txw run examples/lending/txmanifest.json LockCollateral \
  --instance examples/lending/lending.instance.json --wallet borrower.json
txw sync --wallet borrower.json

With the collateral and NFTs locked behind pre_lock, the offer is live. Next, a lender accepts it — or the borrower withdraws it: Accepting or cancelling the offer.