Thank you for contributing to NebGov, the permissionless governance framework for Stellar.
- Rust (stable toolchain) with
wasm32-unknown-unknowntarget - Stellar CLI (
stellar) for contract building and testing - Node.js 20+ with pnpm 9+
- Git
# Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# Stellar CLI
cargo install --locked stellar-cli
# Node.js (via nvm)
nvm install 20
npm install -g pnpm@9git clone https://github.com/nebgov/nebgov && cd nebgov
# Build all Soroban contracts
stellar contract build
# Install JS dependencies
pnpm install
# Build the SDK
pnpm build:sdkRun everything CI checks, in one command:
make checkOr the two halves separately: make test-contracts (Rust) and make test-ts lint-ts build-ts (every pnpm workspace — sdk/, app/, backend/, packages/cli/, packages/indexer/, tools/simulation/).
Individual commands, if you only touched one package:
# Rust contract tests
cargo test --workspace
# SDK unit tests
pnpm test:sdk
# Frontend tests
pnpm test:app
# CLI unit tests (SDK clients are mocked; build the SDK first so its enums resolve)
pnpm build:sdk && pnpm test:cli
# E2E tests (requires running app)
cd app && npx playwright testWe use CoinFabrik Scout to automatically scan our Soroban contracts for security vulnerabilities.
To run Scout locally, you first need to install the scout-audit tool:
cargo install cargo-scout-auditThen, run the scanner from the repository root:
cargo scout-audit --output-format htmlThis will generate an audit_report.html file with the results.
- Critical/High: These findings will fail the CI build and must be addressed before merging.
- Medium/Low/Info: These findings do not fail the CI but should be reviewed and fixed if applicable.
If a finding is confirmed as a false positive, it can be suppressed in .scout.toml at the repository root. Each suppression must include a justification:
[[suppressions]]
detector = "detector_name"
reason = "Suppressed: [reason] — [date] — [author]"| Directory | Description |
|---|---|
contracts/governor |
Core governance contract (Rust/Soroban) |
contracts/timelock |
Delayed execution controller |
contracts/token-votes |
Voting power with checkpointing, including split delegation (docs) |
contracts/governor-factory |
Permissionless governor deployer |
contracts/treasury |
Multi-sig treasury |
contracts/treasury-strategies |
Governance-controlled yield strategy allocation (docs) |
contracts/proposal-bonds |
Refundable proposer bonds with slashing (docs) |
contracts/optimistic-governor |
Fast-track proposals with challenge window (docs) |
contracts/conviction-voting |
Self-executing voting based on conviction (docs) |
contracts/signal-anchor |
Off-chain signaling with on-chain anchoring (docs) |
sdk/ |
TypeScript SDK (@nebgov/sdk) |
app/ |
Next.js governance dashboard |
docs/ |
Architecture docs and ADRs |
NebGov's contracts are written in Rust targeting the Soroban VM. Contract changes carry higher risk than frontend or SDK changes because they affect immutable on-chain state. Follow the requirements below for any PR that touches contracts/.
# Rust stable toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# Soroban / Stellar CLI (pin to the version used by CI)
cargo install --locked stellar-cli
# Verify installation
stellar --version
cargo build --target wasm32-unknown-unknown --release# Build all contracts to WASM
stellar contract build
# Build a single contract
cd contracts/governor && cargo build --release --target wasm32-unknown-unknown
# Optimized release build (used for deployment)
stellar contract build --profile releaseAll three test layers are required before submitting a contract PR:
# Unit and integration tests for all contracts
cargo test --workspace
# Test a single contract in isolation
cargo test -p sorogov-governor
# Run a specific test by name
cargo test -p sorogov-liquidity test_swap_reverse_direction_finds_pool -- --nocaptureEvery new function must have at least one unit test. Cross-contract interactions require an integration test in the contract's integration_tests.rs or tests.rs module. Tests that involve the full governance lifecycle (propose → vote → queue → execute) should live in integration test files.
Soroban's test harness writes a JSON snapshot of every test's ledger state to contracts/<name>/test_snapshots/ on each cargo test run. These files are generated output: they are ignored by .gitignore, not tracked in the repository, and not read by CI.
- When a snapshot changes: do nothing. Leave the regenerated files out of your commit;
git statuswill not show them. - Do not force-add them (
git add -f) to "pin" behaviour. Assert the state you care about in the test itself. - If an old checkout still tracks some: run
git rm -r --cached contracts/*/test_snapshotsbefore committing.
Captured command output (for example cargo test > test_output.txt) likewise stays out of the tree. *.txt under contracts/*/src/ is ignored.
Authorization: Every function that mutates state on behalf of a caller must call caller.require_auth() as the very first statement. Never rely on the caller parameter alone to infer identity.
Events: Emit a structured event for every meaningful state transition. Use the helpers in events.rs and follow the existing naming pattern (emit_<action>).
Errors: Use the contract's typed error enum (e.g., LiquidityError, GovernorError) and env.panic_with_error(...). Never use bare panic!() strings for user-facing error paths — reserve those for programming errors caught in development.
Arithmetic: Use checked_add / checked_mul (or the contract's own helpers) wherever overflow is possible. Document the invariant if you deliberately use unchecked arithmetic.
No unsafe code: The #![no_std] environment does not allow unsafe. Any dependency that requires it must be justified in the PR description.
Before requesting review, verify:
-
require_auth()called for every privileged entry point - State mutations happen before external calls (checks-effects-interactions)
- All arithmetic uses overflow-safe operations
- Events emitted for every state change
- No new persistent storage keys that can conflict with existing keys
- Tests cover the happy path, at least one error path, and any edge cases mentioned in the issue
-
cargo scout-auditpasses locally (see Security Scanning below)
All contract changes require a security sign-off from a maintainer before merge. Add the label needs-security-review to your PR. Changes to access control logic, arithmetic, or storage layout receive mandatory dual review.
Deploy to Futurenet and run a smoke test before opening a mainnet-targeted PR:
stellar contract deploy \
--wasm target/wasm32v1-none/release/sorogov_governor.wasm \
--network futurenet \
--source <SECRET_KEY>Record the deployed address and verify the initialization flow end-to-end using stellar contract invoke.
- docs/architecture.md — contract interaction diagram and storage layout
- docs/security.md — known threat model and mitigations
- tools/simulation/README.md — governance simulation testing framework and how to add scenarios
Browse open issues. Each issue is tagged with:
complexity: trivial- small, well-scoped changecomplexity: medium- moderate implementation workcomplexity: high- significant feature or architectural change
When opening a new issue, please choose one of the repository's issue templates so your report includes all required details.
Issues tagged good first issue are recommended for first-time contributors.
feat/<description>for featuresfix/<description>for bug fixesdocs/<description>for documentation
Example: feat/governor-parameter-tuning, fix/quorum-calculation-edge-case, docs/contract-architecture
Use imperative mood with conventional prefixes:
feat: add vote delegationfix: correct quorum calculationdocs: update architecture diagramtest: add governor edge caseschore: update dependencies
- Fork the repo and create a branch from
main - Make your changes with tests for new features
- Ensure all CI checks pass:
make check(aggregates the Rust contract suite and every TypeScript workspace — seetest-ts/lint-ts/build-ts) - Update docs if you changed any public API
- Update the changelog: Add your changes to the
[Unreleased]section ofCHANGELOG.mdunder the appropriate subsection (Added,Changed,Fixed,Security, etc.). Breaking changes must be noted with a**Breaking:**prefix. Do not create a new version section — maintainers handle that at release time. - Open a PR referencing the issue:
Closes #<number> - Wait for maintainer review. Note that reviewers are automatically assigned based on our CODEOWNERS configuration. For more details on our branch protection rules, see Branch Protection.
If you find a security vulnerability, do not open a public issue. Follow the private reporting process in SECURITY.md instead.
- Format with
cargo fmt - No
unsafecode - All public functions must have doc comments
- Tests live in
#[cfg(test)]modules
- Strict TypeScript, no
anytypes - Run
pnpm lintbefore pushing - Use named exports
Each issue is scoped to be completable in under one week by a single contributor. If you find an issue is larger than expected, comment on it so it can be split.
Open a discussion on GitHub or comment on the relevant issue.