Valid Code & Restrictions
Xian contracts are ordinary Python syntax inside a restricted execution model. The linter enforces that restricted subset before code is accepted.
Allowed Syntax
| Feature | Example | Notes |
|---|---|---|
| assignment | x = 1 | |
| arithmetic | +, -, *, /, //, %, ** | |
| comparisons | ==, !=, <, >, <=, >=, in, not in, is, is not | |
| boolean logic | and, or, not | |
if / elif / else | if x > 0: | |
for / while | loops | metered like everything else |
| functions | def f(): | top-level only |
return | return value | |
assert | assert ok, "message" | main validation pattern |
| collections | list, dict, tuple, set(), frozenset() | set literals and set comprehensions are blocked |
| list comprehensions | [x for x in items] | generator expressions are not allowed |
| subscripts / slices | x[0], x[1:3] | |
| imports | import currency | module-level only for deployed contracts |
Forbidden Syntax
| Feature | Error Code | Why |
|---|---|---|
try/except, with, lambda, ternary expressions, yield, yield from, nonlocal, @, set literals, set comprehensions, semicolons, one-line compound statements | E001 | blocked syntax in the sandbox or rejected to keep line-bucket metering predictable |
names starting or ending with _ | E002 | blocks Python internals / escape paths |
| import inside a function | E003 | imports must be explicit and module-level |
from x import y | E004 | use import x then x.y |
| stdlib module import | E005 | only deployed contracts and runtime modules are allowed |
| class definitions | E006 | contracts are module/function based |
| async functions | E007 | no async execution in contracts |
| invalid decorators | E008 | only @export and @construct |
| multiple constructors | E009 | only one constructor allowed |
| multiple decorators on one function | E010 | single-decorator model |
| forbidden ORM kwargs | E011 | runtime owns those names |
| tuple unpacking of ORM declarations | E012 | storage declarations must be explicit |
no @export function present | E013 | contract needs a public API |
| forbidden builtin or reserved name | E014 | unsafe builtins and reserved identifiers are blocked |
| exported arg shadows ORM name | E015 | avoids confusing state collisions |
| invalid argument annotation | E016 | exported args must use allowed types |
| missing export annotation | E017 | exported args must be typed |
| invalid export return annotation | E018 | export returns may only use allowed types |
| nested function definition | E019 | avoids closures and hidden state |
| parse error | E020 | ordinary syntax error |
| invalid decorator arguments | E021 | @export only supports typecheck=True/False; @construct takes no arguments |
| profile-blocked syntax | E022 | rejected by the active validation profile |
| profile-blocked builtin | E023 | builtin is not supported by the active validation profile |
Allowed Builtins
The current builtin allowlist is intentionally small:
Exception False None True abs all any ascii bin bool bytearray bytes chr
dict divmod filter float format frozenset hex int isinstance issubclass len
list map max min oct ord pow range reversed round set sorted str sum tuple zipEverything else is treated as forbidden.
Allocation Guards
Allowed builtins are still bounded by deterministic runtime limits:
range(...)may create at most131,072entries.bytes(n)andbytearray(n)may allocate at most131,072bytes.- string, bytes, bytearray, list, and tuple repetition with
*may not exceed the same sequence or byte limits. - returned contract values may not exceed
131,072bytes when serialized. - submitted contract source may not exceed
65,536bytes.
Numeric multiplication is unaffected. These guards prevent contracts from allocating large in-memory objects while keeping validation deterministic.
Allowed Export Signature Annotations
The same allowlist applies to exported arguments and exported return annotations:
str, int, float, bool, bytes, bytearray, dict, list, set, frozenset, Any
datetime.datetime, datetime.timedeltaIn Xian, float in an export signature means a deterministic decimal-backed value at runtime. Use float for user-facing decimal amounts, not Python Decimal.
Examples:
@export
def balance_of(address: str) -> float:
return balances[address]
@export
def decode_batch(items: list[int]) -> dict[str, int]:
return {
"count": len(items),
}Subscripted container annotations are valid when the base type is allowed, for example list[int] and dict[str, int].
Export Decorator Options
@export may be used in either of these forms:
@export
def balance_of(address: str) -> float:
return balances[address]
@export(typecheck=True)
def transfer(to: str, amount: float):
balances[ctx.caller] -= amount
balances[to] += amountNo positional decorator arguments are allowed. The only supported keyword is typecheck, and it must be True or False.
Invalid annotations still fail:
@export
def bad(value: Decimal):
return valueImport Rules
Allowed imports are deployed contracts, for example import currency.
Runtime-provided modules such as hashlib, datetime, random, importlib, crypto, zk, and decimal are injected directly into contract scope and do not need import statements.
The Python standard library is not generally available to contracts beyond that runtime surface.
Practical Guidance
When in doubt:
- keep contract code flat and explicit
- keep one statement per line
- use
assertinstead of exception handling - keep all imports at module scope
- prefer simple data structures
- test the contract locally with
ContractingClient