The nexus-contract-template repository is the fastest way to start writing a NEXUS smart contract. It includes a production-ready Cargo workspace, the nexus-sdk vendored locally, and a full test harness — no network access required to build or test.
Setup
Clone the template
git clone https://github.com/yattacorp/nexus-contract-template
cd nexus-contract-template
Install the WASM target
rustup target add wasm32-unknown-unknown
Install WABT tools (required for WASM memory patching in tests)
# macOS
brew install wabt
# Linux
apt install wabt
Project Structure
nexus-contract-template/
├── src/
│ └── lib.rs # Your contract code
├── contract-tests/
│ └── src/
│ └── lib.rs # Integration tests
├── vendor/
│ ├── nexus-sdk/ # SDK (no network needed)
│ └── nexus-xtask/ # WASM build tooling
├── Cargo.toml
└── rust-toolchain.toml
Cargo.toml
The template Cargo.toml is pre-configured for contract builds:
[package]
name = "my-nexus-contract"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # Required: produces a .wasm file
[dependencies]
nexus-sdk = { path = "vendor/nexus-sdk", default-features = false, features = ["contract"] }
[dev-dependencies]
nexus-sdk = { path = "vendor/nexus-sdk", features = ["testing"] }
[profile.release]
opt-level = "s" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1 # Single codegen unit for better optimization
strip = true # Strip debug symbols
panic = "abort" # Smaller panic handler (required for no_std WASM)
crate-type = ["cdylib"] is mandatory. Without it, Cargo will not produce a .wasm file.
Writing Your First Contract
Edit src/lib.rs:
#![no_std]
extern crate alloc;
use nexus_sdk::contract_api::{ez::prelude::*, ez::ret};
// Persistent storage — survives between calls
pub static COUNTER: Mapping<&[u8], u64> = Mapping::new(b"count");
nexus_fn! {
fn increment() {
let current = COUNTER.get(&b"val".as_slice());
COUNTER.set(&b"val".as_slice(), current + 1);
emit("Incremented", &[]);
ret::u64(current + 1)
}
}
nexus_fn! {
fn get_count() {
let count = COUNTER.get(&b"val".as_slice());
ret::u64(count)
}
}
Key rules:
#![no_std] — contracts run in a bare WASM environment, no std library
extern crate alloc — gives you String, Vec, etc. via the alloc crate
nexus_fn! — the macro that exposes your function as a contract ABI entry point
ret::* — how you return values from a contract function
emit() — fire an event that indexers can listen to
Building
# Debug build (fast, larger output)
cargo build --target wasm32-unknown-unknown
# Release build (optimized, production-ready)
cargo build --target wasm32-unknown-unknown --release
Output: target/wasm32-unknown-unknown/release/my_nexus_contract.wasm
Testing
Write tests in contract-tests/src/lib.rs:
use nexus_sdk::testing::{TestEnv, TestArg, accounts};
const WASM: &[u8] = include_bytes!(
"../../target/wasm32-unknown-unknown/release/my_nexus_contract.wasm"
);
#[test]
fn test_counter() {
let mut env = TestEnv::new();
let contract = env.deploy(WASM).expect("deploy failed");
// Call increment
env.set_caller(accounts::ALICE);
let result = env.call(&contract, "increment", &[]).unwrap();
result.assert_success();
// Verify count
let count = env.call(&contract, "get_count", &[]).unwrap();
assert_eq!(count.as_u64(), 1);
}
Run tests:
# Build WASM first, then run tests
cargo build --target wasm32-unknown-unknown --release && cargo test
TestEnv::deploy() automatically patches the WASM memory export — the same transformation used in production builds — so your tests run against a valid artifact.
Deploying to NEXUS
Once your contract is built and tested, deploy it using the CLI:
nexus contract deploy \
target/wasm32-unknown-unknown/release/my_nexus_contract.wasm \
--rpc http://localhost:9945 \
--wallet ~/.nexus/wallet.json
Or via the TypeScript SDK:
import { NexusClient, Wallet } from '@yattacorp/nexus-sdk';
import { readFileSync } from 'fs';
const wallet = Wallet.fromMnemonic(process.env.MNEMONIC!, 'mainnet', undefined, 'zcash');
const client = new NexusClient(
{ rpcUrl: 'https://api.yattacorp.xyz', network: 'mainnet', chain: 'zcash' },
wallet
);
const wasm = readFileSync('target/wasm32-unknown-unknown/release/my_nexus_contract.wasm');
const { contractId, txHash, gasUsed } = await client.deployContract(wasm);
console.log('Contract deployed at:', contractId);
console.log('Gas used:', gasUsed);
Available SDK Primitives
| Primitive | Purpose |
|---|
Mapping<K, V> | Key-value persistent storage |
DoubleMapping<K1, K2, V> | Two-key mapping (e.g. allowances) |
Blockchain::msg.sender() | Get the calling address |
Blockchain::block.height() | Current NEXUS block height |
ReentrancyGuard::new() | Prevent reentrant calls |
require!(condition, msg) | Assert with revert message |
emit(event, data) | Emit an indexed event |
xcc::call(addr, fn, args) | Call another contract |
xcc::delegate_call(addr, fn, args) | Delegate call |
xcc::deploy(wasm, args, salt) | Deploy contract from contract |
The template pins a specific Rust nightly version to ensure reproducible builds:
[toolchain]
channel = "nightly-2024-01-15"
targets = ["wasm32-unknown-unknown"]
Do not change the toolchain version without testing. NEXUS contracts use #![no_std] and some nightly features — a different toolchain version may cause compiler intrinsic errors.