Thank you for your interest in contributing! This guide covers everything you need to go from zero to a merged pull request.
- Prerequisites
- Development Workflow
- Code Standards
- Branch Naming
- Pull Request Process
- Test Snapshots
- Security Considerations
Make sure the following are installed before you start:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shVerify with rustc --version. The project targets stable Rust — nightly is not required.
rustup target add wasm32-unknown-unknownThis is required to build the contract for Soroban deployment.
cargo install stellar-cliVerify with stellar --version. Used for building, deploying, and inspecting contracts.
A Makefile at the repository root provides shortcuts for all common tasks:
| Command | Description |
|---|---|
make build |
Build the WASM contract |
make test |
Run the full test suite |
make lint |
Run Clippy (warnings as errors) and check formatting |
make fmt |
Auto-format source files with rustfmt |
make deploy |
Deploy the contract via scripts/deploy.sh |
make clean |
Remove build artifacts |
make build
# or: cargo build --release --target wasm32-unknown-unknownmake test
# or: cargo testAll tests run against the Soroban in-process test environment — no live network required. The suite covers create_bounty, claim_bounty, complete_bounty, and the bounty counter.
cargo build --release --target wasm32-unknown-unknownOutput lands at target/wasm32-unknown-unknown/release/mergemint_contracts.wasm. The release profile is tuned for size (opt-level = "z", lto = true) with overflow checks enabled.
Both of the following must pass with zero warnings or errors before you open a PR. CI enforces both checks.
cargo fmtRun this before every commit. Do not disable rustfmt attributes without a clear reason.
cargo clippy -- -D warningsAll Clippy warnings are treated as errors. Fix every diagnostic rather than suppressing it with #[allow(...)] unless the lint is demonstrably a false positive and you explain why in the suppression comment.
Use one of these prefixes followed by a short kebab-case description:
| Prefix | When to use |
|---|---|
feat/ |
New contract functionality or behaviour |
fix/ |
Bug fixes |
docs/ |
Documentation-only changes |
test/ |
New or updated tests with no production code change |
refactor/ |
Internal restructuring with no behaviour change |
ci/ |
Changes to GitHub Actions workflows or scripts |
Examples: feat/claim-expiry, fix/double-claim-guard, docs/snapshot-guide
-
Open or find an issue first. Every PR should be traceable to a GitHub issue. If no issue exists for your change, open one before starting work so the approach can be discussed.
-
Link the issue in your PR description. Use GitHub's closing keyword so the issue closes automatically on merge:
Closes #<issue-number> -
Describe what changed and why. Include:
- A short summary of the change.
- The motivation or the problem it solves.
- Any trade-offs or alternatives you considered.
-
Paste test output. Copy the result of
cargo testinto the PR description so reviewers can confirm the suite passes locally without checking out your branch. -
Screenshots for UI changes. MergeMint Contracts is a pure on-chain library with no UI, but if your PR touches the deployment scripts or produces visual output (e.g.
stellar contract inspectoutput), include a screenshot or terminal capture. -
Keep PRs focused. One logical change per PR. Split unrelated fixes into separate branches.
-
Respond to review comments promptly. A PR that goes two weeks without a response may be closed and re-opened when you are ready to continue.
Every pull request that changes the contract interface must include a CHANGELOG.md entry under the [Unreleased] section. The format follows Keep a Changelog.
A "contract interface change" includes:
- Adding, removing, or renaming a public contract function
- Changing the parameters or return type of a public function
- Adding, removing, or reordering fields in
Bounty,Contributor,BountyMeta, orDataKey - Changing the set of events emitted by any function
Use the appropriate subsection (Added, Changed, Deprecated, Removed, Fixed, Security) inside [Unreleased]. Example:
## [Unreleased]
### Added
- `update_contributor_metadata` — lets contributors update their off-chain profile URI.
### Changed
- `Bounty` — added optional `deadline` field (ledger sequence number).PRs that touch only tests, documentation, CI, or tooling do not require a changelog entry, but one is welcome.
Files under test_snapshots/ capture the full Soroban ledger state produced by each test. They verify that storage layout, type encodings, and struct field order remain stable across code changes. A snapshot mismatch is a breaking change to the on-chain storage format.
The current snapshots and the structs they cover:
| Snapshot file | Primary struct tested |
|---|---|
test_bounty_count.1.json |
DataKey::BountyCount |
test_claim_bounty.1.json |
Bounty, Contributor |
test_complete_bounty_updates_status.1.json |
Bounty status transitions |
test_contributor_reputation.1.json |
Contributor |
test_create_bounty.1.json |
Bounty, DataKey |
test_status_index_tracks_bounty_lifecycle.1.json |
DataKey::StatusIndex |
Soroban's test infrastructure writes snapshot files automatically when a test that uses Env::default() completes. Running cargo test with a clean test_snapshots/ directory will regenerate all files. On subsequent runs the framework compares the live ledger state against the stored JSON; a mismatch fails the test.
Snapshots must be regenerated if:
- A field is added to
Bounty,Contributor, or other#[contracttype]structs - A field is removed or reordered
- A field type changes (e.g.,
u32→u64) - Field visibility or attributes change
- A new
DataKeyvariant is added
If you are unsure whether your change affects storage layout, run cargo test and check whether any snapshot diffs appear.
Delete the existing snapshots and rerun the test suite:
rm -f test_snapshots/test/*.json
cargo testThe test run will recreate all snapshot files from the current ledger state. Review the new JSON files with git diff before committing to confirm the changes are intentional.
If you only want to regenerate a single snapshot, delete that file and run the specific test:
rm test_snapshots/test/test_create_bounty.1.json
cargo test test_create_bounty- Review the diff with
git diff test_snapshots/and confirm each change is intentional. - Commit the updated snapshot files in the same commit as the struct change — never in a separate commit, because the snapshots and the code must stay in sync.
To ensure all snapshots are current and pass:
cargo testIf all tests pass without modification, the snapshots are valid against the current schema.
CHANGELOG.md records the history of the public contract interface. It follows the Keep a Changelog format.
Any PR that changes the public interface must update CHANGELOG.md.
This includes:
- Adding, removing, or renaming a contract function
- Changing function parameter types or order
- Adding, removing, or reordering fields in
Bounty,BountyMeta,Contributor, orDataKey - Adding or removing emitted events, or changing their payloads
Add your entry under the [Unreleased] section at the top of the file using
one of the standard categories: Added, Changed, Deprecated, Removed,
Fixed, or Security.
- Every state-mutating function must call
require_auth()on the relevantAddressargument before touching storage. - Validate all external inputs at the contract boundary — do not rely on the caller to pass well-formed data.
- Do not introduce arithmetic that bypasses the overflow protection provided by
overflow-checks = truein the release profile. - If your change introduces a new trust boundary or changes which address is authorised to perform an action, call it out explicitly in the PR description and link to the relevant section of
docs/security.md.