Overview
What is UTXO_Compiler
UTXO_Compiler (executable name utxo_compiler) is a smart contract compiler targeting the Bitcoin UTXO model. It accepts high-level contract source files with the .ct extension and compiles them into BVM bytecode that can be embedded in Bitcoin transaction locking scripts.
The contract language syntax is derived from Python — using indentation to delimit code blocks and def to define functions — but adds mandatory type declarations and a compile-time ownership check mechanism to help developers catch data misuse issues before deployment.
How Bitcoin Smart Contracts Work
The UTXO Model
Unlike Ethereum's account model, Bitcoin uses the UTXO (Unspent Transaction Output) model. Every Bitcoin transaction consumes some existing UTXOs and produces new ones.
Each UTXO carries two things:
- Satoshis: how much Bitcoin this output locks
- Locking script: a piece of bytecode that specifies "under what conditions this money can be spent"
To spend a UTXO, the initiator must provide an unlocking script in the transaction input. Only when the unlocking script and locking script execute together with a true result is the spend recognized by the network.
Previous transaction output Current transaction
┌──────────────────┐ ┌──────────────────────┐
│ value: 100000sat │ ─────────▶│ Input: unlocking script (key) │
│ Locking script (lock) │ │ Output: new locking script │
└──────────────────┘ └──────────────────────┘
Lock Key + New LockSmart contracts means writing custom logic into the locking script: not just verifying a signature, but also verifying time, data format, multi-party conditions, and any other programmable conditions.
What is BVM
BVM (Bitcoin Virtual Machine) is the underlying virtual machine that executes locking/unlocking scripts. It is a stack-based state machine with no heap, no global variables — only two stacks (main stack and alt stack) and a set of opcodes.
UTXO_Compiler translates your high-level contract code into opcode sequences that BVM can directly execute. Understanding BVM's stack model is key to understanding compiler behavior and the ownership system. See Bitcoin Basics for more.
What You Can Build
.ct contracts are useful for more than a single signature check. They can express many common UTXO spending rules:
| Scenario | What the contract verifies |
|---|---|
| Signature and identity locks | Signatures, public keys, and expected public-key hashes |
| Hash locks and time locks | Preimages, deadlines, refund paths, and branch-specific spending rules |
| Multi-party conditions | Multiple public keys, threshold logic, commitments, and fixed-size argument arrays |
| Stateful UTXO flows | Previous state from the spent UTXO and valid next-state outputs |
| Oracle settlement | Signed external messages, parsed fields, and payout selection |
| Transaction-level constraints | Input commitments, output layout, fees, refunds, and asset-transfer rules |
These scenarios share the same mental model: a contract does not mutate hidden storage. It proves that the current spend is valid and, when needed, that the new transaction recreates the right next UTXO.
How UTXO_Compiler Works
Contract file (.ct)
│
▼
Compile
│
▼
JSON output
- locking script bytecode
- function parameters
- self.X placeholders
- optional debug information
│
▼
Instantiate with real data
│
▼
Bitcoin locking script
│
▼
Create and spend UTXOsFrom the user's perspective, the compiler turns a typed .ct contract into JSON that can be used to instantiate a locking script. During compilation it checks syntax, types, variable ownership, function parameters, and the shape of data passed through the script.
The JSON output is the bridge to deployment: it tells you what bytecode to use, what arguments the unlocking script must provide, and which self.X values must be filled before the locking script is used on-chain.
Key Design Trade-offs of the Language
Explicit Type Declarations
All function parameters and struct fields must declare types. There is no automatic type inference, no var / auto. This may seem verbose, but in a zero-tolerance BVM environment, "what you see in code is what happens on the stack" is more important than brevity.
Compile-time Ownership Checks
After a local variable is passed to a built-in function or assigned to another variable, the original variable is "consumed" and cannot be referenced again. This mechanism directly corresponds to BVM's stack-pop semantics — at the bytecode level, using a variable means popping the value from the stack. The ownership system lets the compiler catch these errors at compile time rather than waiting for an on-chain execution failure.
Contract State Fixed in Bytecode
Contract member variables are compiled directly into the bytecode itself, requiring users to substitute different data in the bytecode to generate different locking scripts. This is a direct manifestation of the UTXO model's "state-as-code" characteristic, fundamentally different from Ethereum's "contract address + on-chain storage" model.
Public Functions Run Serially
Functions whose names do not start with _ are public spending entries in the ABI, but they are emitted in declaration order as one continuous locking-script flow. For mutually exclusive paths, use one public main(path, ...) function and dispatch internally to private helpers.
Single Contract Per File
Each .ct file contains exactly one contract. This naturally aligns with the model of "one UTXO corresponds to one locking script."
Standard Libraries and Templates
Contracts can reuse standard templates with imports such as import std.p2pkh, which resolves under the standard-library root. Imported library helpers are available at compile time and are treated like private helpers, so they are not exported as ABI spending entries. Instance data is usually passed explicitly, for example verifyP2PKH(sig, pubKey, self.pubKeyHash).
Development Workflow
- Write a
.ctfile with oneContract, typed parameters, and structs for any transaction fragments you need to inspect. - Use
self.Xplaceholders for instance data such as public-key hashes, deadlines, oracle keys, or protocol constants. - Compile with
utxo_compiler your_contract.ct; add debug output when you need step-by-step inspection. - Review the exported JSON, bytecode, function parameters, and placeholders.
- Instantiate the locking script by replacing
self.Xdata, then fund a UTXO with that script. - Build the spending transaction so the unlocking script supplies function arguments and the outputs satisfy checks such as
BVM.outputsHash.
For stateful contracts, the recurring pattern is: model the parent transaction with Structs, rebuild the spent preTXID, read old state from SuffixData, then verify the next output recreates the expected code and new state.
Comparison with Other Solutions
UTXO_Compiler .ct | sCrypt (BSV) | Bitcoin Script (native) | |
|---|---|---|---|
| Abstraction level | High-level language | High-level language | Assembly-level |
| Syntax style | Python-like | TypeScript-like | Opcode sequence |
| Type system | Strongly typed | Strongly typed | Untyped |
| Ownership check | ✓ Compile-time | ✗ | — |
| Debugging tools | Built-in interactive debugger | Plugin support | None |
| Instance data | self.X / suffix data | Constructor / properties | Manual byte pushes |
| Public paths | Serial flow; use path dispatch | Multiple public methods | Manual branch script |
Beginner Pitfalls
- Treating UTXO contracts like Ethereum contracts: UTXO contracts do not have mutable on-chain storage; state is usually carried by the next UTXO's script.
- Skipping
self.Xinstantiation: compiled output is a template until real instance data is filled in. - Assuming multiple public functions are selectable methods: they run continuously in declaration order.
- Reading only contract code and ignoring transaction outputs: stateful contracts must also check that the current transaction creates the correct next output.
Quick Review
- UTXO_Compiler compiles
.ctcontracts into locking-script bytecode for transaction outputs. - A contract does not mutate hidden storage; it verifies whether the current spend is valid.
self.Xis instance data embedded into the locking script before deployment.- Public functions run serially in declaration order; mutually exclusive paths usually use one entry plus a
pathparameter. - Stateful contracts must verify both the old UTXO and the new outputs created by the current transaction.
Next Steps
- Installation — Set up the compilation environment