Baklava

The market

One item, one price, one offer. Listing, buying and taking it back — three transitions, and nothing here quotes, matches or discovers a price.

Selling one

list, and take it back
./baklava sell -keystore wallet.json     -collection <id> -item 42 -price 5000000 -days 30

./baklava unlist -keystore wallet.json -collection <id> -item 42

Or /sell <id> 42 5 from the console, where a bare number is BKLV. The item moves into the market escrow when it is listed and comes back when the listing goes away.

The escrow is a trick the chain already owned.

A listed item's owner becomes a third pool address — SHA-256("baklava/market/v1") — exactly as a registration stake moves into the stake pool. Nobody holds a key for it.

Marking the item as listed and leaving it with you would be the same mistake as marking a stake instead of moving it: you could hand the item to somebody else while the offer still stood, and a buy would pay for something the chain would then have to refuse to deliver. Moving it makes that unrepresentable rather than checked.

A buy is one transitionThe price goes to the seller, the item to the buyer, and the listing goes away — all at once or not at all. There is no settlement window and nothing to unwind, which is the whole of what makes a market simpler than a swap.
The price may be BKLV or a tokenA listing names an asset: a token id, or zero for BKLV. Tokens were built first precisely so that the modules after them would have something to trade. The fee is BKLV whichever the price is, so a seller with an item and no BKLV cannot list, and a buyer paying in a token still needs BKLV for the fee.
A listing is immutableNothing reprices one. A seller who wants a different number cancels and lists again — which is a different listing at the same key, and that is exactly what a purchase has to defend against.
A seller may not buy their own listingIt moves an item to where it already is and money from a pocket to the same pocket. The only thing it produces is a record of a sale that did not happen.

A buy names the price it expects

A listing's key is derived from the collection and the item number, not from its contents. So a seller can cancel and relist at a higher number at the same key, and a purchase already in flight would land against the new listing.

The buy therefore carries the price and the asset it expects, and the transition refuses if either has changed. It is the minimum-out field a swap needs, arriving one module early and for a smaller reason: not because a leader can reorder a curve, but because a key can be reused.

baklava buy reads the listing and signs the price it finds, so that protection holds without asking for anything. What -price adds is a check on the reading: it refuses unless the offer is still the one that was named. That is for a command somebody did not compose themselves — a page saw an offer at a number, and between then and running it the seller may have relisted.

take an offer, and only that offer
./baklava buy -keystore wallet.json     -collection <id> -item 42 -price 2500000

Expiry, and what it is actually for

A listing stands for a fixed time — 30 days by default, a year at most — measured by the block timestamp, the only clock in the protocol.

The reason is stale offers, not storage. A listing with no expiry is a standing offer that can be hit at any point in the future: a seller who wrote a number a year ago, and then stopped paying attention, is sold from at a price that stopped meaning anything. An expiry is what lets an offer lapse when nobody is watching it.

It does not bound state growth, and it is worth being plain about that rather than claiming otherwise. An expired listing is still a row until somebody removes it, and nobody is paid to remove somebody else's. What bounds the growth is the fee paid to make one.

Cancelling and reaping are one command.

The seller may take a listing down at any time. Once it has expired, anybody may — and the item goes back to the seller either way, because the transaction does not get to name where it goes. Somebody clearing an expired listing who could name a destination would be a thief with extra steps.

There is no sweep at an epoch boundary: that would be a walk over every listing on the chain, once per epoch, to do work nobody asked for.

Where a listing lives

In the same tree tokens added, as its fifth kind of leaf — a token record, a token balance, a collection record, an item's owner, and now an offer, at 61, 8, 92, 20 and 56 bytes. Still pairwise distinct, which is the only thing telling them apart, and a test holds them that way.

state_root folds exactly what rule version 3 already made it fold. Nothing about the fold moves again.

What is not here

No royaltiesA cut of every later sale paid to a collection's creator is a claim about a creator's ongoing rights the chain would have to enforce for ever — exactly the trust the token and NFT modules refused. It is also unenforceable at the edges: two people who want to trade around it can, with an ordinary transfer.
No auctions, offers or bidsEach is a second kind of listing with a second settlement rule, and a bid that can be withdrawn is an escrow of its own.
No listing of tokensSelling a quantity of something for a price is an order book, and an order book is a different project. What is listed here is one item.
No discoveryThe chain answers “is this item listed, and for what” and nothing else. “What is for sale” is an indexer's question, like an address's history.

Rule version 3, alongside tokens and NFTs on baklava-testnet-5.

A rule version is a batch rather than a rule, and no chain had activated version 3 when this was written — so the market went in beside tokens and NFTs rather than becoming a version of its own. That door has since shut: version 3 activated at height 2500, and the swap is a version of its own because of it. See Upgrades and readiness.

Browsing it

The market page lists what is on offer. The chain cannot answer “what is for sale” — there is no absence proof for a list, so a node that left a row out could not be caught — so that half comes from the asset index and every price, seller and expiry beside it comes from the node with a proof. The index never states a price; it stores none.

The commands the page composes carry -price, and that is the same rule as above one step earlier: the number you are looking at was true at a height, and the command checks it is still true before it signs.

The reasoning in full, including why royalties were refused and what a sweep at the epoch boundary would have cost, is under The market in the protocol document.