Thanks for taking a look. This document covers how to get the repository building, what a commit has to look like, and what happens to a pull request.
Read SECURITY.md before reporting anything that looks like a vulnerability. Do not open a public issue for one.
Prerequisites are Rust 1.93.0 and the Stellar CLI 27.1.0. The toolchain version and the
wasm32v1-none target are declared in rust-toolchain.toml, so rustup installs both the first
time you build.
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install the Stellar CLI as a prebuilt binary
curl -sSfL https://github.com/stellar/stellar-cli/releases/download/v27.1.0/stellar-cli-27.1.0-x86_64-unknown-linux-gnu.tar.gz \
| sudo tar xz -C /usr/local/bin stellarDo not run cargo install --locked stellar-cli from inside this repository. It compiles the CLI
from source under our pinned toolchain, which takes 10–20 minutes and ties the CLI's minimum Rust
version to ours.
stellar contract build # compiles all contracts to wasm32v1-none
cargo test --all # runs the full suiteBuild with stellar contract build, never cargo build. wasm32v1-none is the only target the
Soroban runtime supports, and the CLI applies build settings the runtime requires. The CLI also
refuses Rust 1.81, 1.82, 1.83 and 1.91.0, which have known bad wasm codegen — that is why the pin
is 1.93.0.
Before you push, all four of these must be clean:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all
stellar contract buildCI runs exactly these, in this order.
Tests are #[cfg(test)] modules using Env::default() and generated test contracts. Advancing
ledgers in a test is done through env.ledger(). Soroban writes snapshots under test_snapshots/;
those are gitignored and regenerate on every run, so do not commit them.
Conventional Commits, type(scope): description.
- Types:
feat,fix,test,docs,chore,refactor,ci - Scopes:
workspace,maintainable,registry,vault,example,scripts
The scope is the part of the repository the change belongs to, not the file you edited. A change to
crates/maintainable is maintainable; a change to contracts/rent_vault is vault.
One logical unit per commit — one function, one type, one block of tests. A commit that adds a function and its tests and also renames something unrelated should be two or three commits.
Stage files by path. Do not use git add .; it is how gitignored-but-not-yet-ignored artifacts and
half-finished work end up in history.
Every commit must compile. Run cargo check before committing, and cargo test before committing
anything that touches logic.
Examples from this repository's history:
feat(registry): add deregister with swap-remove
test(vault): claim path, keeper/timing rejections, balance accounting
feat(example): add register_with cross-call to registry
ci(workspace): use prebuilt stellar-cli and move off blocklisted Rust 1.91.0
Work happens on a branch and lands through a pull request. There are no direct pushes to main.
- Fork, or branch if you have write access. Branch names are not enforced;
type/short-descriptionis what we use. - Make your changes as a series of scoped commits.
- Confirm the four checks above pass locally.
- Open a pull request. If it closes an issue, write
Closes #123in the body. - CI must be green and the pull request must be approved before it merges.
Describe what changed and why. If you made a design decision that a reader might disagree with, say what you decided and what the alternative was — that is more useful than a summary of the diff.
These are enforced by review, not by a linter:
#![no_std]in every contract crate.- No
unwrap(),expect(),panic!,assert!orunreachable!outside#[cfg(test)]. - No floats. Amounts are
i128in stroops; rates are basis points. - No unbounded iteration over storage. Every loop is bounded before it is entered.
overflow-checks = truestays on in the release profile.- Rustdoc on every public function: what it does, who is allowed to call it, and what it errors with.
- Errors are
#[contracterror]enums with explicit codes. The ranges are fixed and must not be reused: registry1xx, rent_vault2xx, maintainable3xx, escrow4xx. - Events use
#[contractevent], notenv.events().publish(). - Write contract state before any external token call.
require_auth()is the first statement in a mutating function.
Two things are structurally impossible in Soroban and no amount of review will let them through: a
contract cannot read its own entries' TTL at runtime, and keys_xdr is never decoded on-chain. If
a change depends on either, it is going in the wrong direction.
examples/long_escrow is a fixture that demonstrates adoption. It is deliberately not a production
escrow, and pull requests that add escrow features to it will be declined.
Open an issue with the version or commit you are on, what you ran, what you expected, and what happened. For on-chain behaviour, include the contract ID, the network, and the transaction hash — those make a report reproducible in a way a description cannot.