Rust quickstart
Rust is the canonical contract language for PulseVM. The VM itself is written in Rust, and the chain's own system contracts (token, system, governance) are built with pulse-cdt-rust — the same CDT you use for your own contracts.
New here? Do Get started first: CLI, keys, an account and an endpoint.
1. Start inside the workspace
The simplest setup is a new contract inside the pulse-cdt-rust workspace, next to the system contracts. The token contract's build.rs generates the ABI from your source, so copy it:
git clone https://github.com/MetalBlockchain/pulse-cdt-rust
cd pulse-cdt-rust
rustup target add wasm32-unknown-unknown
mkdir -p contracts/hello/src
cp contracts/pulse_token/build.rs contracts/hello/Add "contracts/hello" to members in the root Cargo.toml, then create contracts/hello/Cargo.toml:
[package]
name = "hello"
version = "0.1.0"
edition = "2024"
[dependencies]
pulse_cdt = { workspace = true }
[build-dependencies]
syn = { version = "1", features = ["full"] }
quote = "1.0"
serde_json = "1.0"
[lib]
crate-type = ["cdylib"]
[features]
default = ["contract-entry"]
contract-entry = []2. Write the contract
Save this as contracts/hello/src/lib.rs. The #[contract] macro provides get_self(), and actions are plain functions of their arguments.
#![no_std]
#![no_main]
extern crate alloc;
use alloc::string::String;
use pulse_cdt::{
NumBytes, Read, Write, action, contract,
contracts::require_auth,
core::{MultiIndexDefinition, Name, Table, check},
name, table,
};
#[derive(Read, Write, NumBytes, Clone, PartialEq)]
#[table(primary_key = row.account.raw())]
pub struct Greeting {
pub account: Name,
pub text: String,
}
const GREETINGS: MultiIndexDefinition<Greeting> = MultiIndexDefinition::new(name!("greetings"));
#[derive(Default)]
struct HelloContract;
#[contract]
impl HelloContract {
#[action]
fn greet(account: Name, text: String) {
require_auth(account);
check(text.len() <= 256, "greeting too long");
let greetings = GREETINGS.index(get_self(), get_self().raw());
greetings.emplace(account, Greeting { account, text });
}
}3. Build configuration
Contracts target wasm32-unknown-unknown. The host functions are imported, so the linker must allow undefined symbols. The workspace's .cargo/config.toml already sets this; for a contract outside the workspace, use:
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "link-arg=--import-undefined",
"-C", "link-arg=--stack-first",
"-C", "link-arg=-zstack-size=8192",
"-C", "link-arg=--no-merge-data-segments",
"-C", "link-arg=--gc-sections",
"-C", "link-arg=--strip-all",
]4. Build, deploy and read it back
cargo build --target wasm32-unknown-unknown --release -p hello
# wasm: target/wasm32-unknown-unknown/release/hello.wasm
# abi: contracts/hello/abi.json (written by build.rs)
pulse-ts set-code yourname1 ./target/wasm32-unknown-unknown/release/hello.wasm
pulse-ts set-abi yourname1 ./contracts/hello/abi.json
pulse-ts push-action yourname1 greet '{"account":"yourname1","text":"hi"}' -a yourname1
pulse-ts table yourname1 greetingsThe last command prints the row you just wrote. Next, bind a second key to greet only.
The best reference is the chain itself
pulse_token (token semantics, stat/accounts tables) and pulse_system (accounts, resources, permissions) in pulse-cdt-rust are the reference system contracts — the most authoritative examples of the CDT in real use.