A GitHub Codespace workshop for writing and deploying Simplicity smart contracts on Liquid Testnet.
Simplicity is a low-level, formally-verified smart contract language designed for Bitcoin and Liquid. Programs are represented as typed combinator trees, giving them provable resource bounds and a clean formal semantics. SimplicityHL is a higher-level language that compiles down to Simplicity.
The Codespace includes:
| Tool | Purpose |
|---|---|
simc |
SimplicityHL compiler — turns .simf source into a compiled program |
hal-simplicity |
HAL tools — inspect programs, build PSETs, sign, broadcast |
lwk_cli |
Liquid Wallet Kit CLI — wallet and transaction utilities |
All tools are pre-built from source and available on PATH when the Codespace starts.
Work through the exercises in order. Each one introduces new concepts.
The simplest possible Simplicity contract: coins move only when the holder of a given public key produces a valid Schnorr signature.
Concepts: jet::bip_0340_verify, witness, jet::sig_all_hash
cd exercises/01-p2pk
simc p2pk.simf # compile and inspect
bash demo.sh # end-to-end testnet demoA 2-of-3 multisig contract. Three public keys are embedded in the contract; any two valid signatures unlock the coins.
Concepts: Option<T>, arrays, helper functions, checksig_add pattern
cd exercises/02-p2ms
simc p2ms.simf
bash demo.shEach demo.sh walks through the full lifecycle of a Simplicity contract on testnet:
1. Compile simc <program>.simf
2. Get address hal-simplicity simplicity info <compiled>
3. Fund Liquid Testnet faucet → contract address
4. Build PSET hal-simplicity simplicity pset create ...
5. Sign hal-simplicity simplicity sighash ...
6. Inject witness update .wit file with real signature(s)
7. Recompile simc <program>.simf -w <witness>.wit
8. Finalize hal-simplicity simplicity pset finalize ...
9. Broadcast curl → Liquid Testnet API
Run any script with an optional destination address, or omit it to return funds to the faucet:
bash demo.sh [your-liquid-testnet-address]- Create a new
.simffile:
fn main() {
// your logic here
}- Compile to check for errors:
simc mycontract.simf- Inspect the compiled program:
PROGRAM=$(simc mycontract.simf | sed '1d; 3,$d')
hal-simplicity simplicity info "$PROGRAM" | jq- Create a
.witfile for any witness values your contract needs, then compile with it:
simc mycontract.simf -w mycontract.wit- Follow the same PSET lifecycle as the demo scripts to fund and spend on testnet.
Witness values (e.g. signatures) are provided at spend time, not at contract creation. They live in .wit JSON files:
{
"MY_SIGNATURE": {
"value": "0x...",
"type": "Signature"
}
}Reference them in Simplicity as witness::MY_SIGNATURE.
All contracts use a BIP-341 NUMS (nothing-up-my-sleeve) internal key so that the Taproot key-path spend is provably disabled:
50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
The demo scripts use the secp256k1 generator multiples as private keys (1, 2, 3). The corresponding public keys are embedded in the contracts. These are well-known test vectors — never use them with real funds.