Compile Python smart contracts to NEAR-deployable WASM using Monty.
# Install
cargo install --path .
# Compile a Python contract to WASM
monty-near-cli build contract.py -o contract.wasm
# Deploy to NEAR
near deploy myaccount.testnet contract.wasm
near call myaccount.testnet hello --accountId myaccount.testnetRust 1.91.0 and the wasm32-unknown-unknown target are installed automatically via rust-toolchain.toml.
The default build targets the upcoming Wasmtime-based runtime (nearcore 2.12+). To deploy to current testnet or mainnet (which still use NearVM), use --compat:
monty-near-cli build contract.py -o contract.wasm --compatThis uses nightly Rust with -Zbuild-std and -Ctarget-cpu=mvp to produce WASM without bulk-memory instructions that NearVM rejects. Requires rustup toolchain install nightly and the rust-src component (installed automatically via the generated rust-toolchain.toml). The CLI itself remains on stable Rust.
If wasm-tools is installed, the build automatically verifies the output contains no bulk-memory instructions.
| Flag | Effect |
|---|---|
--compat |
Build for current production NearVM (nightly + -Zbuild-std -Ctarget-cpu=mvp) |
--no-wasm-opt |
Skip wasm-opt -Oz post-processing (enabled by default if wasm-opt is in PATH) |
-o <path> |
Output path (default: contract.wasm) |
def hello():
value_return("Hello from Python on NEAR!")
def counter():
count = storage_read("count")
if count is None:
count = 0
else:
count = int(count)
count = count + 1
storage_write("count", str(count))
value_return(str(count))Every top-level def becomes an exported NEAR contract method. Functions starting with _ are private helpers. All NEAR host functions are available as Python builtins — no imports needed.
See examples/example.py for a contract exercising the core host functions.
Monty is a Python-to-Rust compiler by the Pydantic team. It takes a subset of Python, parses it with ruff's parser, and compiles it to a custom bytecode format. That bytecode runs on a small Rust VM (MontyRun) that can be compiled to wasm32-unknown-unknown — making it suitable for embedding in NEAR smart contracts.
This CLI automates the pipeline: parse Python source → compile to Monty bytecode → embed the bytecode in a Rust WASM project with a NEAR-compatible runtime → produce a deployable .wasm file.
Python source → monty-near-cli (host) → contract.wasm (deployable)
- Parse — find all top-level
deffunctions in the Python file. - Compile — compile the entire source plus a generated dispatcher into a single Monty bytecode blob using
MontyRun::new()+.dump(). The dispatcher is anif/elifchain that routes a_methodvariable to the correct function. - Scaffold — create a temporary Rust project in
target/monty-near-build/using embedded templates (Cargo.toml,lib.rs, toolchain config). - Splice — inject the serialized bytecode and
#[no_mangle] pub extern "C" fnexports into the template'slib.rsat marker comments. - Build —
cargo build --releasetargetingwasm32-unknown-unknown. LTO strips the Python parser entirely; only the VM and bytecode remain. - Optimize — run
wasm-opt -Ozon the output for size reduction (~11-12% savings). - Verify — in
--compatmode, runwasm-tools validate --features=-bulk-memoryto confirm the output is NearVM-safe.
Each exported method deserializes the shared bytecode, passes the method name as an input variable to the VM, and the dispatcher routes execution to the correct Python function.
Integration tests use bun and near-kit to deploy the compiled contract to a local NEAR sandbox:
cd tests && bun install && bun testThere are two test suites:
contract.test.ts— default build, runs against sandboxmaster(Wasmtime with bulk-memory support)contract.compat.test.ts—--compatbuild, runs against sandbox2.10.6(production NearVM)
To run just the compat tests: bun test contract.compat.test.ts
Starting with Rust 1.87, LLVM emits bulk-memory WASM instructions (memory.copy, memory.fill) by default. NEAR's current production VM (NearVM, a Wasmer 2.x fork) rejects these with PrepareError::Instantiate, so most NEAR contract tooling is pinned to Rust 1.86 or earlier.
This project can't pin to Rust 1.86 because Monty's dependencies require let_chains (stable since 1.87). Instead, two build modes are provided:
| Default | --compat |
|
|---|---|---|
| Target runtime | Wasmtime (nearcore 2.12+) | Current NearVM (Wasmer) |
| Rust toolchain | Stable 1.91.0 | Nightly |
| Bulk-memory instructions | Present | Stripped via -Ctarget-cpu=mvp |
| Deployable today | Sandbox only | Testnet and mainnet |
| Cargo flags | build --release |
build --release -Zbuild-std=std,panic_abort |
--compat) is not yet deployable on testnet or mainnet. The Wasmtime switch is part of nearcore 2.12, with mainnet deployment expected late March / early April 2026 (stabilization PR). Until then, use --compat for testnet/mainnet, or deploy to a local sandbox running master.
For background, see the contract-runtime > bulk memory support thread on near.zulipchat.com.
The build runs wasm-opt -Oz automatically after cargo build to reduce WASM size through dead code elimination, constant folding, and other optimizations. This typically saves ~11-12% (~100 KB). Pass --no-wasm-opt to skip this step, or install wasm-opt with cargo install wasm-opt if it's not already available.
Note: while wasm-opt can strip some post-MVP features like multi-value and reference-types, it cannot strip bulk-memory instructions. This is why --compat solves the problem at the compiler level (via -Ctarget-cpu=mvp) rather than relying on post-processing.
Monty depends on ahash, which depends on getrandom for hash randomization. getrandom doesn't compile for wasm32-unknown-unknown by default. Instead of using the no-rng feature flag (which would require forking monty's Cargo.toml), the template project implements a getrandom 0.3 custom backend that provides randomness from NEAR's VRF-based random_seed() host function.
- Python subset — Monty compiles a subset of Python. Classes, decorators, exceptions (
try/except), list comprehensions,*args/**kwargs, and the standard library are not supported. See Monty's documentation for the full list of supported features. - String-only storage — host functions pass data as strings. There is no built-in JSON serialization; parse and format manually.
- No panic handling — if the Monty VM encounters an error, the contract panics with a generic message. Python exceptions are not supported.
- WASM size — the output is ~790-830 KB (after wasm-opt) due to the embedded Monty VM. This is within NEAR's 1.5 MB contract size limit but larger than typical Rust SDK contracts.
monty-near-cli/
├── src/main.rs # CLI: parse → compile → scaffold → build → optimize
├── template/
│ ├── Cargo.toml # Generated project dependencies
│ ├── rust-toolchain.toml # Pins Rust 1.91.0 + wasm32 target
│ ├── .cargo/config.toml # WASM target, getrandom backend
│ └── src/lib.rs # NEAR runtime: FFI imports, host wrappers, VM loop
├── examples/
│ └── example.py # 13-method contract using all host functions
├── tests/
│ ├── contract.test.ts # Integration tests (bun + near-kit, sandbox master)
│ ├── contract.compat.test.ts # Compat mode tests (sandbox 2.10.6)
│ └── package.json
└── Cargo.toml
MIT