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 7 concrete types:
| 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 |
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
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.
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.
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) |
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 move 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 |
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.