Mist Language Reference
Complete reference for the Mist smart contract language — every keyword, type, operator, statement, and expression form.
Overview
Mist is an indentation-based, statically typed language purpose-built for value-safe smart contracts on Rivellum. Contracts written in Mist produce PVI (Proof-Verified Invocation) graphs — typed DAGs of value operations verified by the UVL engine before any state change occurs.
Mist Source → Parser → AST → Type Check → Value IR → Static Analysis → MCE → PVI Graph → Bundle
Mist enforces value safety at the language level. You cannot directly mutate balances — all value movement flows through seven primitives (hold, pay, release, refund, split, mint, burn) that the compiler and runtime verify for conservation, linearity, and correctness.
Contract Structure
Every Mist file defines exactly one contract. A contract contains state declarations, function definitions, and event declarations:
contract MyContract
state count: Int
state owner: Address
event CountChanged(new_count: Int, who: Address)
function init()
count = 0
owner = caller
emit CountChanged(count, caller)
function increment()
require caller == owner
count = count + 1
emit CountChanged(count, caller)
Indentation Rules
Mist uses Python-style significant whitespace. Blocks are defined by indentation level, not braces or end keywords.
- Only spaces are allowed — tabs cause a compile error (
MixedIndentation) - Increasing indentation opens a new block
- Decreasing indentation closes the block
- The indentation level must match a previously opened block — mismatched levels cause an
IndentationMismatcherror - Blank lines and comment-only lines are ignored during indentation processing
contract Example
# This is indented one level — the contract body
function foo()
# Two levels — the function body
if count > 0
# Three levels — the if body
count = count - 1
else
# Three levels — the else body
count = 0
Comments
Line comments start with # and extend to the end of the line:
# This is a comment on its own line
count = count + 1 # This is an inline comment
There are no block comments.
Types
Mist has 11 first-class types in the current bounded-execution subset:
| Type | Keyword(s) | Description | Size |
|---|---|---|---|
Int | Int, U128 | Unsigned 128-bit integer | 16 bytes |
Bool | Bool | Boolean (true / false) | 1 byte |
String | String | UTF-8 string | Variable |
Address | Address | 32-byte account address | 32 bytes |
Timestamp | Timestamp, U64 | Unsigned 64-bit integer (milliseconds) | 8 bytes |
Value | Value, VaultRef | Reference to a value vault (linear type) | Opaque |
Asset | Asset | Asset identifier | 32 bytes |
Array | Array(T, N) | Fixed-size typed array | Fixed |
List | List<T, CAP> | Bounded ordered collection | Bounded |
Set | Set<T, CAP> | Bounded membership set | Bounded |
Map | Map<K, V, CAP> | Bounded key-value store | Bounded |
SortedMap | SortedMap<K, V, CAP> | Canonically key-sorted bounded map | Bounded |
Queue | Queue<T, CAP> | Canonical FIFO bounded queue | Bounded |
Bounded Collections
Mist now supports deterministic bounded collections for coordination-heavy contracts such as lobbies, scoreboards, staged registries, and payout tracking:
state players: List<Address, 16>
state ready: Map<Address, Bool, 16>
state claimed: Set<Address, 16>
Rules for bounded collections:
- Every collection must declare an explicit positive capacity.
List<T, CAP>preserves insertion order and supports bounded iteration.Set<T, CAP>supports membership operations, but set iteration is forbidden.Map<K, V, CAP>supports key-based lookup/update, but map iteration is forbidden.SortedMap<K, V, CAP>provides canonical key-sorted access for market price levels.Queue<T, CAP>provides deterministic FIFO semantics for turn-order and per-level order queues.- Capacity is enforced by the compiler and runtime.
List<Int>orMap<Address, Int>without a capacity is rejected.
Sorted map ordering rules are fixed by key type and are not customizable:
- numeric keys (
Int) sort ascending numeric order Boolsortsfalse < trueAddresssorts lexicographically on canonical bytesStringsorts lexicographically (UTF-8 byte order)- custom comparators are forbidden
Collection Builtins
| Builtin | Return Type | Description |
|---|---|---|
list_len(list) | Int | Current length of a bounded list |
list_get(list, index) | T | Read a list element by index |
list_push(list, value) | Int | Append to a list; fails at capacity |
set_len(set) | Int | Current size of a bounded set |
set_contains(set, value) | Bool | Test membership |
set_insert(set, value) | Int | Insert into a set; capacity-enforced |
map_len(map) | Int | Current size of a bounded map |
map_contains(map, key) | Bool | Test key presence |
map_get(map, key) | V | Read a value by key |
map_put(map, key, value) | Bool | Insert or update an entry |
sorted_map_len(map) | Int | Current size of a sorted map |
sorted_map_contains(map, key) | Bool | Test key presence in sorted map |
sorted_map_get(map, key) | V | Read sorted-map value by key |
sorted_map_put(map, key, value) | Bool | Insert/update sorted-map entry |
sorted_map_remove(map, key) | Bool | Remove sorted-map entry by key |
sorted_map_first_key(map) | K | Smallest key in sorted map |
sorted_map_last_key(map) | K | Largest key in sorted map |
sorted_map_key_at(map, index) | K | Key at deterministic sorted index |
queue_len(queue) | Int | Current queue length |
queue_enqueue(queue, value) | Int | Enqueue value at tail |
queue_dequeue(queue) | T | Dequeue value from head |
queue_peek_front(queue) | T | Read head without mutation |
queue_peek_back(queue) | T | Read tail without mutation |
queue_get(queue, index) | T | Read queue element by bounded index |
queue_clear(queue) | Bool | Clear queue contents |
Atomic Bounded Batch Primitives
Mist supports first-class bounded batch markers used by matching and settlement flows:
batch_begin(max_ops)starts a batch region with compile-time bounded workbatch_step(batch_token)accounts one deterministic stepbatch_end(batch_token)closes the region and returns steps consumed
If batch_step exceeds max_ops, execution fails atomically.
Integer Semantics
All integers in Mist are unsigned 128-bit (u128). There are no signed integers, floating-point numbers, or smaller integer types at the language level. Arithmetic overflow causes a runtime error (MIST_E023), and division by zero causes MIST_E024.
The Value Type
Value (also written VaultRef) is a linear type — it represents a vault holding assets. Vaults must be consumed exactly once (via pay, release, refund, split, or burn). The compiler's static analysis phase will reject contracts that leave vaults unconsumed (MIST_E_LOCKED_FUNDS) or consume them more than once.
Gradual Typing
The type checker uses a Unknown type for values whose type cannot be statically determined (oracle reads, state reads, field accesses). Unknown is compatible with any concrete type — this allows oracle-dependent logic to typecheck without explicit casts.
State Declarations
State variables persist across function calls and are declared at the top of the contract body:
contract Vault
state balance: Int
state owner: Address
state name: String
state locked: Bool
state escrow_vault: Value
State variables are accessed and assigned by name within functions:
function deposit(amount: Int)
balance = balance + amount
Collections are valid state declarations too:
contract Lobby
state players: List<Address, 16>
state ready: Map<Address, Bool, 16>
state claimed_rewards: Set<Address, 16>
Event Declarations
Events are declared with a name and typed fields, then emitted from functions:
contract Token
event Transfer(from: Address, to: Address, amount: Int)
event Approval(owner: Address, spender: Address, amount: Int)
function transfer(to: Address, amount: Int)
require amount > 0
emit Transfer(caller, to, amount)
Events become on-chain log entries searchable via the events API.
Event schemas are exported in bundle metadata as typed, versioned declarations (version defaults to 1). SDKs consume this schema to decode event payloads deterministically.
Functions
Functions are declared with function, an optional parameter list, and an optional return type:
# No parameters, no return
function init()
count = 0
# With parameters
function transfer(to: Address, amount: Int)
require amount > 0
# With return type
function get_count() -> Int
return count
# With parameters and return type
function add(a: Int, b: Int) -> Int
return a + b
The init Function
The init function is called exactly once when the contract is deployed. It initializes state and can take constructor arguments:
function init(initial_supply: Int)
require initial_supply > 0
owner = caller
total_supply = initial_supply
The caller Intrinsic
caller returns the Address of the account that submitted the current intent. It is the primary mechanism for access control:
function admin_only()
require caller == owner, "Only owner can call this"
Statements
Variable Binding (let)
let x = 42
let name = "hello"
let is_valid = true
let addr = caller
Type annotations are optional — the type is inferred from the expression:
let x: Int = 42
let name: String = "hello"
Assignment
count = count + 1
owner = caller
name = "updated"
Conditional (if / else)
if count > 0
count = count - 1
else
count = 0
else is optional. Conditions must be Bool.
Bounded for Loops
Mist allows for loops only when the compiler can prove a finite upper bound:
for i in 0..10
total = total + 1
for i in 0..list_len(players)
let player = list_get(players, i)
if map_contains(ready, player)
let status = map_get(ready, player)
Accepted loop bounds are:
- literals such as
0..10 - bounded collection lengths such as
0..list_len(players)
Rejected loop forms include:
while ...for i in 0..nwherenis not provably bounded- direct iteration over maps or sets
while Is Forbidden
while loops are rejected in Mist's deterministic bounded subset because the compiler must prove an execution bound up front.
Guards (require / assert)
require and assert halt execution if the condition is false. An optional message string provides context:
require caller == owner
require amount > 0, "Amount must be positive"
assert balance >= amount, "Insufficient balance"
Both compile to the same VIR pattern: a branch that panics on the false path. At runtime, a failed require produces error code MIST_E021 and a failed assert produces MIST_E022.
Return
function get_count() -> Int
return count
function early_exit()
if done == true
return
count = count + 1
Emit
emit Transfer(caller, recipient, amount)
emit Initialized(owner)
The event name must match a declared event, and arguments must match the event's field count and types.
Expressions
Literals
42 # Int (u128)
0 # Int
1000000000 # Int (1 RIVL in base units, 9 decimals)
"hello world" # String (double quotes)
'hello world' # String (single quotes — identical to double)
true # Bool
false # Bool
Escape sequences in strings: \\, \n, \t, \", \'
Integer literals are decimal only — no hex (0x), octal, binary, or underscore separators.
Arithmetic Operators
| Operator | Name | Example | Notes |
|---|---|---|---|
+ | Addition | a + b | Both operands must be Int |
- | Subtraction | a - b | Both operands must be Int |
* | Multiplication | a * b | Both operands must be Int |
/ | Division | a / b | Integer division (truncates). Division by zero → MIST_E024 |
% | Modulo | a % b | Remainder after division |
Comparison Operators
| Operator | Name | Example |
|---|---|---|
== | Equal | a == b |
!= | Not equal | a != b |
< | Less than | a < b |
<= | Less or equal | a <= b |
> | Greater than | a > b |
>= | Greater or equal | a >= b |
Arithmetic comparisons (<, <=, >, >=) require Int operands. Equality (==, !=) requires the same type on both sides.
Logical Operators
| Operator | Name | Example |
|---|---|---|
&& | Logical AND | a && b |
| ` | ` | |
! | Logical NOT | !flag |
All operands must be Bool.
Unary Operators
| Operator | Name | Example |
|---|---|---|
- | Negation | -x |
! | Logical NOT | !flag |
Operator Precedence (Lowest to Highest)
| Level | Operators | Associativity |
|---|---|---|
| 1 | ` | |
| 2 | && | Left |
| 3 | ==, != | Left |
| 4 | <, <=, >, >= | Left |
| 5 | +, - | Left |
| 6 | *, /, % | Left |
| 7 | !, - (unary) | Right |
| 8 | .field, [index], (args) | Left |
Use parentheses to override precedence:
let result = (a + b) * c
let check = (x > 0) && (y < 100)
Field Access and Indexing
let price = obj.field
let item = arr[0]
Function Calls
let result = compute(x, y)
Function calls use simple identifier syntax — method-style calls (obj.method()) are not supported.
Intrinsics
Built-in values and functions available in any function body:
| Intrinsic | Type | Description |
|---|---|---|
caller | Address | Address of the intent sender |
now | Timestamp | Current block commit time (milliseconds since epoch) |
oracle.price("PAIR") | Int | Oracle spot price for the given pair (1e18 fixed-point) |
vrf_seed() | Unknown | Deterministic VRF seed bytes for the current execution context |
vrf_random_u64() | Int | Deterministic 64-bit VRF-derived number |
vrf_random_range(min, max) | Int | Deterministic VRF-derived number in [min, max] |
batch_begin(max_ops) | Int | Start an atomic bounded batch section |
batch_step(batch_token) | Int | Consume one declared batch step |
batch_end(batch_token) | Int | End batch section and return used steps |
block_height | Int | Current block height |
block_time | Int | Current block timestamp |
Oracle Reads
Two equivalent syntax forms:
let btc_price = oracle.price("BTC_USD")
let eth_price = oracle("ETH_USD").price
Oracle prices are 1e18 fixed-point integers. For example, if BTC is $60,000, then oracle.price("BTC_USD") returns 60000000000000000000000 (60000 × 10^18).
Oracle data is committed to the execution receipt. If the oracle snapshot is stale (more than 256 blocks old), UVL verification rejects the intent with UVL_F010.
Value Operations
Value operations are the core of Mist — they define how assets transfer between accounts through typed, verifiable primitives. The compiler and UVL engine guarantee that every value operation conserves assets ( $\sum \text{inputs} = \sum \text{outputs} + \text{fees}$ ).
hold — Lock Assets in a Vault
hold my_vault = 1000 asset "RIVL" from caller
Creates a new vault named my_vault containing 1000 units of asset "RIVL" locked from the caller's balance. The vault must be consumed before the function returns.
Syntax: hold <vault_name> = <amount> asset <asset_expr> from <address_expr>
pay — Transfer Assets to an Address
# Direct pay (from caller's balance)
pay 500 asset "RIVL" to recipient
# Pay from a vault
pay 500 asset "RIVL" to recipient from my_vault
Transfers assets to the specified address, either from the caller's balance or from a named vault.
Syntax: pay <amount> asset <asset_expr> to <address_expr> [from <vault_name>]
release — Return Vault Contents to an Address
release vault escrow_vault to seller
Releases the entire contents of a vault to the specified address. Consumes the vault.
Syntax: release vault <vault_name> to <address_expr>
refund — Return Vault Contents (Refund Semantics)
refund vault escrow_vault to buyer
Semantically identical to release but indicates a refund flow. The PVI graph records it differently for auditability.
Syntax: refund vault <vault_name> to <address_expr>
split — Divide Vault Among Recipients
split vault payment into [800 to seller, 200 to platform_fee_addr]
Splits a vault's contents among multiple recipients. The sum of split amounts must equal the vault's total — the compiler checks this statically when possible. Rounding uses TruncateRemainderToLast policy.
Syntax: split vault <vault_name> into [<amount> to <addr>, <amount> to <addr>, ...]
mint — Create New Assets
mint token_vault = 1000 asset asset_id authority owner
Creates new asset units. Requires a valid mint authority — UVL verification rejects unauthorized mints with UVL_F007.
Syntax: mint <vault_name> = <amount> asset <asset_expr> authority <authority_expr>
Mint authority must be declared via can_mint policy (see Policies section).
burn — Destroy Assets
burn vault token_vault
Permanently destroys the contents of a vault. Requires burn authority.
Syntax: burn vault <vault_name>
Value Operation Guarantees
The Mist compiler and UVL engine enforce these invariants at compile time and settlement:
| Invariant | Check | Error Code |
|---|---|---|
| Conservation — total value in equals total value out plus fees | Compile + UVL | UVL_F002 |
| No negative balances — no account goes below zero | UVL | UVL_F003 |
| No locked funds — all vaults consumed before function returns | Compile + UVL | UVL_F004 |
| Split correctness — split amounts sum to vault total | Compile + UVL | UVL_F005 |
Split rounding — rounding follows TruncateRemainderToLast | UVL | UVL_F006 |
| Mint authority — only authorized accounts can mint | UVL | UVL_F007 |
| Determinism — same inputs always produce the same PVI graph | Compile | Static analysis |
Keywords Reference
Structure Keywords
| Keyword | Usage |
|---|---|
contract | Declares a contract: contract Name |
state | Declares state variable: state x: Int |
function | Declares a function: function name(params) -> ReturnType |
event | Declares an event: event Name(fields) |
Control Flow Keywords
| Keyword | Usage |
|---|---|
if | Conditional: if condition |
else | Alternative branch: else |
return | Return value: return expr |
let | Variable binding: let x = expr |
require | Guard: require condition, "message" |
assert | Guard: assert condition, "message" |
emit | Emit event: emit EventName(args) |
Value Operation Keywords
| Keyword | Usage |
|---|---|
hold | Lock assets: hold v = amount asset "X" from addr |
pay | Transfer: pay amount asset "X" to addr |
release | Release vault: release vault v to addr |
refund | Refund vault: refund vault v to addr |
split | Split vault: split vault v into [...] |
mint | Mint assets: mint v = amount asset id authority auth |
burn | Burn assets: burn vault v |
Intrinsic Keywords
| Keyword | Usage |
|---|---|
now | Current timestamp |
oracle | Oracle read: oracle.price("PAIR") |
caller | Intent sender address |
in | Loop range syntax: for i in 0..list_len(items) |
Contextual Keywords
These are used in specific syntactic positions within value operations:
asset, from, to, vault, into, authority, price
Error Codes
Compile-Time Errors
| Code | Meaning |
|---|---|
MIST_E_LOCKED_FUNDS | A vault is created but never consumed (pay/release/refund/split/burn) |
MIST_E_CONSERVATION | Value inputs don't equal value outputs plus fees |
MIST_E_SPLIT_SUM | Split amounts don't sum to the vault total |
MIST_E_MINT_AUTHORITY | Mint without valid authority |
MIST_E_BURN_INVALID | Burn on invalid vault |
MIST_E_NON_DETERMINISTIC | Contract uses non-deterministic operations |
MIST_E_UNUSED_VAULT | Vault created but never used |
MIST_E_DIRECT_BALANCE_MUTATION | Attempted to mutate balances without value operations |
MIST_E_NON_DETERMINISTIC_FEE | Fee computation is not deterministic |
MIST_E_UNBOUNDED_LOOP | Loop without provable bound |
Runtime Errors
| Code | Meaning |
|---|---|
MIST_E001 | Invalid bundle format |
MIST_E003 | Code hash mismatch (bundle tampered) |
MIST_E005 | Contract already deployed at this address |
MIST_E010 | Contract not found |
MIST_E011 | Function not found on contract |
MIST_E012 | Wrong number of arguments |
MIST_E020 | General execution failure |
MIST_E021 | require condition failed |
MIST_E022 | assert condition failed |
MIST_E023 | Integer overflow |
MIST_E024 | Division by zero |
UVL Settlement Errors
| Code | Meaning |
|---|---|
UVL_F001 | PVI commitment mismatch |
UVL_F002 | Value conservation violation |
UVL_F003 | Negative balance would result |
UVL_F004 | Locked funds detected (unconsumed vault) |
UVL_F005 | Split sum mismatch |
UVL_F006 | Split rounding violation |
UVL_F007 | Unauthorized mint |
UVL_F008 | Fee commitment mismatch |
UVL_F009 | Oracle commitment mismatch |
UVL_F010 | Oracle snapshot stale (>256 blocks old) |
UVL_F011 | Unsupported protocol version |
UVL_F012 | Compiler not in allowlist |
Use mistc explain <CODE> to see detailed explanations for any error code.