A comprehensive guide to learning ChiaLisp for Chia blockchain development
In this chapter we move beyond toy examples and build five practical systems that demonstrate how ChiaLisp puzzles solve real problems. Each example includes:
.clsp puzzlePrerequisites: You should be comfortable with currying, inner puzzles, conditions, announcements, and Python drivers (Chapters 1-5).
Files: examples/escrow/escrow.clsp |
examples/escrow/escrow_driver.py |
An escrow is a financial arrangement where a third party holds funds on behalf of two transacting parties. The funds are released only when predefined conditions are met. This is the backbone of marketplace transactions, freelance payments, and real-estate closings.
On Chia, we implement this as a coin whose puzzle enforces three possible resolution paths:
| Path | Condition | Result |
|---|---|---|
| Mutual agreement | Buyer AND Seller both sign | Funds go to seller (or refund to buyer) |
| Arbiter decides | Arbiter signs + one party signs | Arbiter directs funds |
| Timeout | Block height exceeds deadline | Buyer gets automatic refund |
The escrow puzzle is curried with five parameters:
BUYER_PUBKEY – the buyer who deposits fundsSELLER_PUBKEY – the seller who will receive funds on completionARBITER_PUBKEY – a trusted third party for disputesSELLER_PUZZLE_HASH – where funds go on successful completionBUYER_PUZZLE_HASH – where funds go on refundTIMEOUT_HEIGHT – block height after which the buyer can reclaim fundsThe solution provides a mode argument:
Mode 0 – Mutual Release: Both buyer and seller agree. The puzzle requires AGG_SIG_ME from both BUYER_PUBKEY and SELLER_PUBKEY. Funds are sent to SELLER_PUZZLE_HASH.
Mode 1 – Arbiter Decision: The arbiter steps in. The puzzle requires AGG_SIG_ME from ARBITER_PUBKEY and the solution specifies the destination puzzle hash. This allows the arbiter to send funds to either party.
Mode 2 – Timeout Refund: No signatures required beyond the buyer’s. The puzzle checks ASSERT_HEIGHT_RELATIVE against TIMEOUT_HEIGHT and sends funds back to BUYER_PUZZLE_HASH.
Announcements are used so that when the escrow is resolved, the buyer’s or seller’s wallet coin can ASSERT_COIN_ANNOUNCEMENT to confirm the escrow coin was spent in the same bundle.
AGG_SIG_ME ties signatures to this specific coin, preventing replay on other escrow coins.The Python driver (escrow_driver.py) handles:
The driver assembles the correct CoinSpend and pairs it with the appropriate aggregated signature.
Files: examples/savings/savings_account.clsp |
examples/savings/savings_driver.py |
A savings account that enforces withdrawal discipline:
This is useful for personal savings discipline, treasury management for DAOs, or any scenario where you want to prevent impulsive large withdrawals.
The puzzle uses a singleton-like pattern to maintain state. The coin always recreates itself with updated parameters.
Curried parameters:
OWNER_PUBKEY – the account ownerWITHDRAWAL_LIMIT_PERCENT – max percentage per withdrawal (e.g., 10 = 10%)WITHDRAWAL_COOLDOWN – minimum blocks between withdrawals (e.g., 4608 blocks ~ 1 day)EMERGENCY_TIMELOCK – blocks to wait for full emergency withdrawal (e.g., 138240 ~ 30 days)LAST_WITHDRAWAL_HEIGHT – the block height of the last withdrawal (state)Solution modes:
Mode 0 – Deposit: The coin is spent and recreated with the same puzzle hash but a higher amount. The difference is the deposit. Owner signature required.
WITHDRAWAL_LIMIT_PERCENT of the current balance. The puzzle enforces:
ASSERT_HEIGHT_RELATIVE for the cooldown periodLAST_WITHDRAWAL_HEIGHTASSERT_HEIGHT_RELATIVE with the longer timelock.(amount * WITHDRAWAL_LIMIT_PERCENT) / 100.The driver tracks the singleton-like coin, computes allowed withdrawal amounts, and builds spend bundles for each operation mode.
Files: examples/lottery/lottery.clsp |
examples/lottery/lottery_driver.py |
A decentralized lottery where:
This can be used for raffles, giveaways, or any fair random selection process.
The lottery uses two phases, each with its own puzzle:
Phase 1 – Ticket Sales (the lottery coin)
Curried parameters:
OPERATOR_PUBKEY – the lottery operatorTICKET_PRICE – price per ticket in mojosDEADLINE_HEIGHT – block height when ticket sales closeMAX_TICKETS – maximum number of ticketsTICKET_LIST – list of (puzzle_hash) for each ticket holderWhen a player buys a ticket, the coin is spent and recreated with the player’s puzzle hash appended to TICKET_LIST and the amount increased by TICKET_PRICE.
Phase 2 – Draw
After DEADLINE_HEIGHT, anyone can trigger the draw. The puzzle:
ASSERT_HEIGHT_RELATIVE to confirm the deadline has passed.(sha256 MY_COIN_ID) mod (length TICKET_LIST).ASSERT_BEFORE_HEIGHT_RELATIVE.The driver handles ticket purchases (spending and recreating the lottery coin), triggering the draw, and distributing prizes. It tracks the evolving lottery coin as tickets are purchased.
Files: examples/vesting/vesting.clsp |
examples/vesting/vesting_driver.py |
Token vesting is standard in crypto projects: team members, investors, or advisors receive tokens that unlock gradually over time. This prevents dumping and aligns long-term incentives.
Our vesting schedule:
Curried parameters:
BENEFICIARY_PUBKEY – who receives the vesting tokensTOTAL_AMOUNT – total tokens to vestCLIFF_HEIGHT – block height when the cliff endsVESTING_START_HEIGHT – the block height when vesting began (usually same as coin creation)TOTAL_VESTING_PERIOD – total blocks for full vesting (cliff + linear period)CLAIMED_AMOUNT – how many tokens have already been claimed (state)Solution provides the claim_amount and the current block_height.
The puzzle logic:
Before cliff: Spending is completely blocked (ASSERT_HEIGHT_RELATIVE would fail since puzzle requires height >= CLIFF_HEIGHT).
elapsed = current_height - VESTING_START_HEIGHTvested = (TOTAL_AMOUNT * elapsed) / TOTAL_VESTING_PERIODavailable = vested - CLAIMED_AMOUNTclaim_amount <= availableCLAIMED_AMOUNT and reduced balanceCLAIMED_AMOUNT is curried into each new coin, preventing double-claiming.AGG_SIG_ME.The driver calculates the current vested amount based on block height, determines how much is claimable, and builds the spend bundle. It also provides a vesting_schedule() method that shows a human-readable timeline.
Files: examples/voting/voting.clsp |
examples/voting/voting_driver.py |
A simple on-chain voting system where:
This is useful for DAO governance, community decisions, or any scenario requiring transparent, verifiable voting.
The system has two puzzle types:
Proposal Puzzle (the ballot box)
Curried parameters:
PROPOSAL_HASH – hash of the proposal text (for verification)OPTIONS – list of option hashes (e.g., “yes”, “no”, “abstain”)DEADLINE_HEIGHT – when voting endsTALLY – list of vote counts per option (state, starts at all zeros)CREATOR_PUBKEY – who created the proposalThe proposal coin accepts votes and recreates itself with updated tallies. After the deadline, it can be finalized to announce the result.
Vote Puzzle (individual vote)
When a voter wants to cast a vote, they create a coin announcement containing:
The proposal coin uses ASSERT_COIN_ANNOUNCEMENT to verify each vote in the same spend bundle, then recreates itself with updated tallies.
The driver creates proposals, submits votes (coordinating the vote coin spend with the proposal coin spend in the same bundle), and reads the final tally from the last proposal coin state.
Each example is self-contained. To study them:
.clsp file and its comments to understand the on-chain logic._driver.py file to understand how a wallet or application interacts with the puzzle.In the next chapter, we will combine many of these patterns into a full CAT Staking System as the capstone project.