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:
python
balances = Hash(default_value=0)
owner = Variable()
metadata = Hash()
TransferEvent = LogEvent(
event="Transfer",
params={
"from": {"type": str, "idx": True},
"to": {"type": str, "idx": True},
"amount": {"type": (int, float, decimal)},
},
)Module scope is also where contract imports and runtime-stdlib imports live:
python
import currency
import hashlib
import datetimeDo not put executable setup logic at module scope. Initialization belongs in @construct.
Constructor
The @construct function runs once at submission time.
python
@construct
def seed(initial_supply: int = 1_000_000):
owner.set(ctx.caller)
balances[ctx.caller] = initial_supplyCurrent rules:
- 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.
python
@export
def transfer(to: str, amount: float):
assert amount > 0, "Amount must be positive"
assert balances[ctx.caller] >= amount, "Insufficient balance"
balances[ctx.caller] -= amount
balances[to] += amountCurrent 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
Whitelisted annotation types are:
python
str, int, float, bool, dict, list, Any
datetime.datetime, datetime.timedeltaPrivate Helpers
Functions without decorators are private to the contract module:
python
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 still 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 be lowercase
- the special identifier
rtis reserved