name: compact-core:compact-transaction-model description: This skill should be used when the user asks about Midnight transaction execution, guaranteed vs fallible phases, kernel.checkpoint(), transaction composition, state conflicts, DUST fees, gas limits, proof verification, partial transaction success, transaction merging, atomic swaps, or how Compact circuits map to on-chain execution. Also triggered by mentions of "transaction semantics", "fallible phase", "guaranteed phase", "checkpoint", "well-formedness", "Impact VM", or "Zswap offers". version: 0.1.0
Compact Transaction Model & Execution Semantics
This skill covers how Compact circuits map to on-chain transaction execution: the three-stage lifecycle (well-formedness, guaranteed, fallible), kernel.checkpoint() placement, transaction composition, merging for atomic swaps, the state model, and the fee/gas system. For contract anatomy and circuit/witness design, see compact-structure. For ledger ADT types and state design, see compact-ledger. For token minting, sending, and receiving, see compact-tokens.
Transaction Lifecycle Overview
Every Midnight transaction passes through three sequential stages. Failure at each stage has different consequences.
| Stage | Type | What Happens | On Failure |
|---|---|---|---|
| Well-formedness | Stateless | ZK proofs in Zswap offers verified, Schnorr proof on contract section verified, guaranteed offer balanced (minus fees, plus mints), fallible offer balanced, coin claims checked for uniqueness | Transaction rejected entirely |
| Guaranteed phase | Stateful | Contract verifier keys looked up, ZK proofs verified against them, fees collected for ALL phases, Zswap offers applied (commitments inserted, nullifiers checked), guaranteed transcripts executed | Transaction rejected (not included in ledger) |
| Fallible phase | Stateful | Fallible transcripts executed, contract state stored if "strong", contract deployments applied | Partial success -- guaranteed effects still apply, fees still consumed |
Execution flow from the developer's perspective:
Compact circuit call
-> Proof generation (client-side)
-> Well-formedness check (stateless validation)
-> Guaranteed phase (proof verification, fees, Zswap)
-> Fallible phase (contract state mutations)
-> State update (if "strong")
The key insight: a transaction that fails in the fallible phase is a partial success, not a full rejection. The guaranteed-phase effects (fee collection, Zswap coin movements, guaranteed transcript operations) persist on the ledger. Only the fallible section's effects are rolled back.
Guaranteed vs Fallible Phases
Guaranteed Phase
Operations in the guaranteed phase either all succeed or the transaction is not included in the ledger at all.
| What Executes in Guaranteed | Details |
|---|---|
| ZK proof verification | Contract call proofs verified against stored verifier keys |
| Fee collection | Fees for the ENTIRE transaction (both phases) collected here |
| Zswap offer application | Coin commitments inserted into Merkle tree, nullifiers added to nullifier set, Merkle roots validated |
| Guaranteed transcripts | Impact programs from guaranteed section of each contract call |
| Fallible Zswap pre-application | The fallible Zswap offer is also applied during the guaranteed phase; this prevents an attacker from merging in an invalid spend that would invalidate only the fallible section |
Fallible Phase
Operations in the fallible phase may fail without reversing guaranteed-phase effects.
| What Executes in Fallible | Details |
|---|---|
| Fallible transcripts | Impact programs from the fallible section of each contract call |
| Contract deployments | New contract creation executes entirely in the fallible phase |
| State storage | Contract state written to ledger only if marked "strong" |
Key Rules
- Fees for ALL phases are collected in the guaranteed phase. If the fallible phase fails, fees are still consumed (forfeited).
- ZK proofs are verified only in the guaranteed phase.
- Contract deployments execute entirely in the fallible phase. A deployment failure is a partial success: fees are spent but the contract is not created.
- If a circuit contains no
kernel.checkpoint()call, the entire circuit body maps to the guaranteed phase only. There is no fallible section.
kernel.checkpoint()
kernel.checkpoint() splits a circuit into guaranteed and fallible sections. Everything before the call executes in the guaranteed phase; everything after executes in the fallible phase. It maps to the ckpt opcode in the Impact VM.
// Assumes ledger declarations:
// export ledger balance: Map<Bytes<32>, Uint<64>>;
// export ledger transferCount: Counter;
// witness localSecretKey(): Bytes<32>;
export circuit transfer(to: Bytes<32>, amount: Uint<64>): [] {
// --- GUARANTEED PHASE ---
// These operations are atomic: all succeed or the transaction is rejected
const sk = localSecretKey();
const sender = persistentHash<Bytes<32>>(sk);
const current = balance.lookup(disclose(sender));
assert(disclose(current >= amount), "Insufficient balance");
transferCount.increment(1);
kernel.checkpoint();
// --- FALLIBLE PHASE ---
// These operations may fail without reversing the guaranteed phase
balance.insert(disclose(sender), disclose((current - amount) as Uint<64>));
balance.insert(disclose(to), disclose((balance.lookup(disclose(to)) + amount) as Uint<64>));
}
Checkpoint Rules
| Rule | Detail |
|---|---|
| Typically one per circuit | A circuit usually contains zero or one kernel.checkpoint() calls. The compiler does not enforce a limit, but only the first checkpoint is semantically meaningful for phase separation |
| Determines phase boundary | Code before checkpoint = guaranteed; code after = fallible |
| No checkpoint = guaranteed-only | Without kernel.checkpoint(), the entire circuit body is guaranteed |
Maps to ckpt opcode |
Compiles to a single Impact VM instruction that marks the phase boundary |
| Phase separation | The guaranteed and fallible sections are compiled as distinct segments in the Impact program |
When to Use checkpoint
Place kernel.checkpoint() when you need operations that:
- Depend on state that may conflict with concurrent transactions
- Perform complex state mutations that you can tolerate failing
- Deploy contracts as part of a larger workflow
Keep critical invariants (authorization checks, fee collection, proof-dependent logic) before the checkpoint in the guaranteed section.
Transaction Composition
A single Midnight transaction can contain multiple contract calls or deployments. These execute sequentially.
| Property | Behavior |
|---|---|
| Sequential execution | Each call sees state changes from previous calls in the same transaction |
| Mixed calls and deploys | A transaction can contain both contract calls and contract deployments |
| Deployment fallibility | Contract deployments are always fallible-phase operations |
| Binding commitment | A cryptographic binding commitment ties all contract calls to the transaction, preventing tampering |
| Schnorr proof | A Schnorr proof binds the contract call section and ensures it carries no hidden value |
Cross-contract calls (one contract calling another contract's circuit within the same execution) are not yet available. To compose operations across contracts, include multiple contract calls in a single transaction. Each call executes independently against its own contract state, but within the shared transaction context.
Transaction Merging
Midnight supports transaction merging to enable atomic swaps and multi-party transactions.
| Property | Rule |
|---|---|
| Merge condition | Two transactions can merge if at least one has an empty contract call section |
| Composite result | The merged transaction has the combined effects of both input transactions |
| Integrity | Pedersen commitment binding ensures each party's funds are spent as originally intended |
| Homomorphic summing | Commitment values are homomorphically summed before composite integrity check |
| Opening knowledge | Only the original creators know the opening randomnesses needed to decompose the transaction |
Atomic Swap Pattern
Transaction merging enables atomic swaps: Alice creates a transaction spending her coins, Bob creates a transaction spending his coins, and the two merge into a single atomic transaction. Because at least one party's transaction has no contract calls, the merge is valid. The Pedersen commitment binding guarantees neither party can alter the other's contribution.
State Model
Each contract on Midnight maintains state as two components:
| Component | Description |
|---|---|
| Impact state value | The contract's data (maps, arrays, cells, Merkle trees) stored as an Impact VM state value |
| Entry point map | A map from entry point names to verifier keys, selecting which ZK proof to verify for each circuit |
State Lifecycle During Execution
- The contract's current state is loaded from the ledger before each call.
- A context object (contract address, newly allocated coins, block time, block hash, block's 32-bit max timestamp divergence) and an empty effects set are placed on the Impact VM stack alongside the state. Both context and effects are flagged as weak.
Caution: Currently, only the first two of these are correctly initialized!
- The Impact program executes, mutating the state on the stack. The propagation rules for weakness are opcode-specific.
- The resulting effects are compared to the declared effects in the transcript; mismatches cause failure.
- The resulting state is stored only if it is "strong" (not weak). If the state has been weakened — for example, by copying context or effects data into it — the call fails.
Concurrency and Conflicts
- Within a block, transactions are applied sequentially. If two transactions write to the same contract state, the second executes against the state left by the first.
- A transaction that reads or writes state another transaction also modifies may conflict if the second transaction's proof was generated against the pre-first-transaction state.
- Practical advice: use append-only data structures (Merkle trees, counters, sets with insert-only patterns) to minimize conflict windows. Avoid read-modify-write patterns on shared mutable state when possible.
Fees & Gas
DUST is Midnight's shielded network resource used exclusively to pay for transaction fees. It is a non-transferable resource derived from NIGHT.
| Property | Detail |
|---|---|
| DUST source | Generated over time from held NIGHT UTXOs; value grows to a cap, decays when NIGHT is spent |
| Non-transferable | DUST can only be spent on fees, never sent to another user |
| Gas bound | Each contract call transcript declares a gas bound limiting Impact VM execution |
| Gas to fee | The gas bound is converted to a DUST fee via dynamic, multi-dimensional pricing |
| SyntheticCost dimensions | read_time, compute_time, block_usage, bytes_written, bytes_churned |
| Dynamic pricing | Per-dimension price factors adjust toward 50% block fullness; a global scalar further adjusts overall cost |
| Collection timing | Fees for ALL phases (guaranteed + fallible) are collected in the guaranteed phase |
| Fallible failure | If the fallible phase fails, fees are still consumed -- the user pays for both phases regardless |
Developers do not set gas prices directly. The declared gas bound in each transcript determines the maximum cost, and the network's dynamic pricing converts this to a DUST amount at inclusion time.
Proof Verification
ZK proof verification happens automatically and is not directly controlled by the contract developer.
| Proof Type | Verified When | Purpose |
|---|---|---|
| Zswap offer proofs | Well-formedness check | Verify coin inputs/outputs are valid (commitments, nullifiers, value vectors) |
| Contract call proofs | Guaranteed phase | Verify the circuit execution is correct against the contract's stored verifier key |
| Schnorr proof | Well-formedness check | Bind the contract call section to the transaction and ensure it carries no value vector |
The proof for each contract call is generated client-side when the user invokes a circuit. The verifier key is stored in the contract state (set at deployment) and looked up during the guaranteed phase. If verification fails, the entire transaction is rejected (not included in the ledger). Developers write Compact circuits; the compiler and runtime handle proof generation and verification.
Common Mistakes
| Wrong | Correct | Why |
|---|---|---|
Putting critical state changes after kernel.checkpoint() |
Move essential state changes before checkpoint | Post-checkpoint operations are fallible and may not execute |
| Assuming all transaction effects are atomic | Understand guaranteed vs fallible split | Guaranteed effects persist even when the fallible section fails |
| Treating contract deployments as guaranteed | Design for deployment being fallible | Deployments execute entirely in the fallible phase |
| Ignoring gas costs for complex circuits | Keep circuits lean; minimize state operations | More operations = higher gas = higher DUST fees |
| Expecting cross-contract calls to work | Use transaction composition (multiple calls in one transaction) instead | Cross-contract calls are not yet available |
| Not handling partial success in the DApp | Check transaction status in TypeScript client code | A "partial success" means guaranteed worked but fallible failed |
Omitting kernel.checkpoint() when fallible operations are needed |
Add kernel.checkpoint() before fallible section |
Without it, the entire circuit is guaranteed-only and any failure rejects the whole transaction |
| Copying context or effects into contract state | Keep context/effects data on-stack only | Context and effects are weak; weakness propagation is opcode-specific, and weak state cannot be stored |
Reference Routing
| Topic | Reference File |
|---|---|
| Three execution stages, phase semantics, state lifecycle, weak vs strong values | references/execution-phases.md |
| Contract state model, concurrency, conflict minimization, append-only patterns | references/state-and-conflicts.md |
| DUST generation, SyntheticCost dimensions, gas-to-fee conversion, dynamic pricing | references/fees-and-gas.md |
| Zswap offers, inputs/outputs, balance vectors, transaction merging, Pedersen binding | references/zswap-and-offers.md |
| Example | File |
|---|---|
Guaranteed/fallible split with kernel.checkpoint() |
examples/CheckpointUsage.compact |
| Multi-call transaction composition | examples/TransactionComposition.compact |
| Fee-aware contract with gas considerations | examples/FeeAwareContract.compact |
| Atomic swap via transaction merging | examples/AtomicSwap.compact |