Contract Structure
A Xian contract is a single Python module. The normal pattern is:
- module-level declarations
- an optional
@constructfunction - one or more
@exportfunctions - private helper functions
Module-Level Declarations
Persistent state and events are declared at module scope:
balances = Hash(default_value=0)
owner = Variable()
metadata = Hash()
TransferEvent = LogEvent("Transfer", {
"from": indexed(str),
"to": indexed(str),
"amount": (int, float, decimal),
})Module scope is also where contract imports live. Runtime modules such as hashlib, datetime, random, importlib, crypto, zk, and decimal are injected automatically and do not need import statements:
import currency
def commitment(label: str):
return hashlib.sha3_text(label)Do not put executable setup logic at module scope. Initialization belongs in @construct.
Constructor
The @construct function runs once at submission time.
@construct
def seed(initial_supply: int = 1_000_000):
owner.set(ctx.caller)
balances[ctx.caller] = initial_supplyRules:
- only one constructor is allowed
- constructor parameters do not need annotations
- the constructor is not callable after deployment
- constructor work is metered and must stay deterministic
Exported Functions
@export defines the public API of the contract.
@export
def transfer(amount: float, to: str):
assert amount > 0, "Amount must be positive"
assert balances[ctx.caller] >= amount, "Insufficient balance"
balances[ctx.caller] -= amount
balances[to] += amountYou can also opt into runtime type enforcement for a specific export:
@export(typecheck=True)
def summarize(items: list[int]) -> int:
return len(items)Rules:
- a contract must expose at least one
@export - every exported argument must have a whitelisted annotation
- exported return annotations are allowed when they use a whitelisted type
- only one decorator is allowed per function
@export(typecheck=True)is a valid single-decorator export form
Whitelisted annotation types are:
str, int, float, bool, bytes, bytearray, dict, list, set, frozenset, Any
datetime.datetime, datetime.timedeltaSubscripted container forms are allowed too, as long as the base type is one of those whitelisted types.
Private Helpers
Functions without decorators are private to the contract module:
def calculate_fee(amount: float) -> float:
return amount * 0.01
@export
def transfer_with_fee(to: str, amount: float):
fee = calculate_fee(amount)
balances[ctx.caller] -= amount
balances[to] += amount - feePrivate helpers:
- can be called only inside the same contract
- do not need annotations
- must obey the same sandbox rules
- cannot be nested inside another function
Naming Rules
The linter rejects names that start or end with _:
_privatename___dunder__
That rule exists to block Python internals and avoid sandbox escapes.
Other important naming rules:
- deployed contract names must start with
con_unless they are seeded system contracts - deployed contract names must start with a lowercase ASCII letter
- deployed contract names may contain only lowercase ASCII letters, digits, and underscores
- special characters such as
.,:,-, and spaces are rejected - the special identifier
rtis reserved