Skip to content

Linter

Xian has two related linting surfaces:

  • the core contract linter in xian-contracting
  • the optional standalone xian-linter HTTP service

Both ultimately enforce the same contract-language rules. The standalone package can also run an optional xian_vm_v1 validation mode for tooling that wants to check that a contract lowers to Xian VM IR before deployment.

The published PyPI package name for the standalone service is xian-tech-linter. The import package and console command remain xian_linter and xian-linter.

Using The Core Linter

Use contracting.compilation.linter.Linter when you are already inside Python:

python
from contracting.compilation.linter import Linter

source = """
balances = Hash(default_value=0)

@export
def transfer(to: str, amount: float):
    assert amount > 0, "Amount must be positive"
    balances[ctx.caller] -= amount
    balances[to] += amount
"""

linter = Linter()
errors = linter.check(source) or []

for error in errors:
    print(error.code.value, error.line, error.col, error.message)

The core linter returns structured LintError objects with:

  • code
  • message
  • line
  • col
  • end_line
  • end_col

Using The Standalone HTTP Service

Install the base package for inline use, or the server extra for the HTTP service:

bash
uv add xian-tech-linter
uv add "xian-tech-linter[server]"

Install the VM extra when you want native Xian VM IR validation as part of xian_vm_v1 mode:

bash
uv add "xian-tech-linter[vm]"
uv add "xian-tech-linter[server,vm]"

Start the service:

bash
xian-linter

The default listen address is http://localhost:8000.

You can also run the ASGI app explicitly:

bash
uvicorn xian_linter.server:create_app --factory --host 0.0.0.0 --port 8000

HTTP Endpoints

The service accepts raw, base64, or gzip request bodies:

  • POST /lint
  • POST /lint_base64
  • POST /lint_gzip

Request bodies are capped at 1,000,000 bytes before decoding. All endpoints also accept an optional comma-separated whitelist_patterns query parameter when a controlled integration needs to allow additional PyFlakes names:

text
POST /lint?whitelist_patterns=my_helper,con_*

All endpoints use the default source-linting mode unless a mode is selected:

text
POST /lint?mode=xian_vm_v1

The alias lint_mode=xian_vm_v1 is also accepted for integrations that already use mode for their own routing.

Lint Modes

The standalone package supports two modes:

ModeWhat it checks
pythondefault source linting with the shared xian-contracting rules plus PyFlakes warnings
xian_vm_v1source linting, VM-profile compatibility, Xian VM IR lowering, and native IR validation when xian_vm_core is installed

The VM mode is still linting only. It does not execute the contract or simulate storage, imports, environment values, or host syscalls. Use runtime tests or node preflight flows for execution behavior.

Inline usage:

python
from xian_linter import lint_code_sync

errors = lint_code_sync(source, mode="xian_vm_v1")

Raw Source Example

bash
curl -X POST "http://localhost:8000/lint?mode=xian_vm_v1" \
  -H "Content-Type: text/plain" \
  --data-binary $'import os\n\n@export\ndef hack():\n    os.system("rm -rf /")'

Base64 Example

bash
base64 < contract.py > contract.py.b64
curl -X POST http://localhost:8000/lint_base64 --data-binary @contract.py.b64

Gzip Example

bash
gzip -c contract.py > contract.py.gz
curl -X POST http://localhost:8000/lint_gzip \
  -H "Content-Type: application/gzip" \
  --data-binary @contract.py.gz

Response Shape

The HTTP service returns:

json
{
  "success": false,
  "errors": [
    {
      "code": "E005",
      "message": "Cannot import stdlib module 'os'",
      "severity": "error",
      "position": {
        "line": 1,
        "col": 0,
        "end_line": 1,
        "end_col": 9
      }
    }
  ]
}

xian-linter may also include PyFlakes warnings with code W001. Processing errors from the wrapper itself are returned as E000. In xian_vm_v1 mode, IR lowering failures are returned as XVM001; native VM IR validation failures are returned as XVM002.

Error Codes

CodeMeaning
E000standalone wrapper processing error
E001illegal syntax form
E002name starts or ends with underscore
E003import inside a function
E004from x import y used
E005stdlib import attempted
E006class definition
E007async function
E008invalid decorator
E009multiple constructors
E010multiple decorators on one function
E011forbidden ORM kwarg
E012tuple unpacking for ORM declarations
E013no exported function
E014forbidden builtin or reserved name
E015exported arg shadows ORM name
E016invalid exported argument annotation
E017missing exported argument annotation
E018invalid exported return annotation
E019nested function
E020syntax error
E021invalid decorator arguments
E022syntax not supported by the active validation profile
E023builtin not supported by the active validation profile
W001PyFlakes warning from the standalone service
XVM001Xian VM IR lowering failed
XVM002native Xian VM IR validation failed

See Valid Code & Restrictions for the current language surface.