Thanks for your interest in contributing! This guide covers everything you need to go from zero to a merged PR.
TrustLink uses pre-commit to enforce formatting and linting before every commit.
Install the hooks once after cloning:
pip install pre-commit # or: brew install pre-commit
pre-commit installAfter that, every git commit automatically runs:
| Hook | What it checks |
|---|---|
cargo fmt --all -- --check |
Rust formatting (Rustfmt) |
cargo clippy --all-targets --all-features -- -D warnings |
Rust lints (Clippy) |
check-yaml |
Valid YAML syntax |
end-of-file-fixer |
Files end with a newline |
trailing-whitespace |
No trailing spaces |
If a hook fails the commit is blocked. Fix the reported issues and git commit again.
Run hooks manually at any time:
pre-commit run --all-files # check everything
pre-commit run cargo-fmt # check one hook by idBefore diving in, read docs/stellar-concepts.md for a beginner-friendly explanation of ledger timestamps, storage TTL, require_auth, and the WASM deployment model — concepts that come up throughout the codebase.
| Tool | Version | Install |
|---|---|---|
| Rust | stable (see rust-toolchain.toml) |
https://rustup.rs |
| wasm32 target | — | rustup target add wasm32-unknown-unknown |
| Soroban CLI | latest | cargo install --locked soroban-cli |
Verify your setup:
rustc --version
cargo --version
soroban --version
rustup target list --installed | grep wasm32# 1. Fork and clone
git clone https://github.com/<your-username>/TrustLink.git
cd TrustLink
# 2. Install the wasm target (rust-toolchain.toml handles the Rust version)
rustup target add wasm32-unknown-unknown
# 3. Confirm the project compiles
cargo check# Run all unit and integration tests
cargo test
# Or via make
make testAll tests must pass before submitting a PR.
Use a local Stellar Quickstart node when iterating on deployment and invoke flows to avoid testnet rate limits.
docker compose up -d
# or: docker-compose up -dThis starts the stellar/quickstart standalone network from docker-compose.yml.
make local-deployWhat this does:
- Builds the contract WASM.
- Ensures local Soroban network + identity are configured.
- Funds the local identity via Friendbot.
- Deploys the contract.
- Invokes
initialize. - Writes the deployed contract ID to
.local.contract-id.
Use this RPC URL for local calls and scripts:
http://localhost:8000/soroban/rpc
Default local network values used by scripts/setup_local.sh:
- Network name:
local - Network passphrase:
Standalone Network ; February 2017
docker compose down# Debug build
make build
# Optimized release build (requires soroban-cli)
make optimizeThis project enforces formatting and lint rules in CI.
# Format code (must be clean before committing)
make fmt # or: cargo fmt
# Run linter — zero warnings allowed
make clippy # or: cargo clippy --all-targets -- -D warningsRun both before every commit.
This project uses Conventional Commits to enable automated versioning and changelog generation. Every commit message must follow this format:
<type>(<scope>): <subject>
<body>
<footer>
Required. Must be one of:
| Type | Purpose | Semver Impact |
|---|---|---|
feat |
A new feature | Minor (0.x.0) |
fix |
A bug fix | Patch (0.0.x) |
docs |
Documentation only | None |
test |
Tests only | None |
refactor |
Code refactoring (no feature/fix) | None |
perf |
Performance improvement | Patch (0.0.x) |
chore |
Build, CI, dependencies | None |
Optional. Narrow the change to a specific area:
storage— storage layer changesvalidation— authorization/validation logicevents— event emissionindexer— off-chain indexersdk— TypeScript SDKci— CI/CD workflowsdocs— documentation
Examples: feat(storage), fix(validation), docs(indexer)
Required. Short description (50 chars max):
- Start with lowercase
- Use imperative mood ("add" not "adds" or "added")
- No period at the end
- Be specific: ✅ "add fee collection to attestation creation" vs ❌ "update code"
Optional. Explain why the change was made (not what — that's in the subject):
feat(storage): add dual indexing for subject and issuer lookups
The previous single index on subject made issuer-based queries O(n).
This adds a parallel index on issuer to enable fast lookups in both
directions. Queries now complete in O(log n) time.
Optional. Reference issues or breaking changes:
Closes #42
Closes #99
BREAKING CHANGE: removed the `get_all_attestations` function
Good commits:
feat(storage): add dual indexing for subject and issuer lookups
fix(validation): reject attestations with valid_from in the past
Previously, valid_from was only checked against the current time.
Now we also reject any valid_from that is before the current ledger
timestamp, preventing backdated attestations.
Closes #123
docs: update deployment guide with testnet contract IDs
test(events): add test for audit log append-only property
refactor: extract fee calculation into separate function
Bad commits:
❌ Updated stuff
❌ Fix bug
❌ feat: Add new feature.
❌ FEAT: ADD FEATURE
❌ feat(storage): added dual indexing
When you merge commits to main:
- Release Please reads your commit messages
- Determines the next version (major.minor.patch) based on commit types
- Creates a Release PR that:
- Updates
Cargo.tomlversion - Generates
CHANGELOG.mdfrom commits - Groups commits by type (Features, Bug Fixes, etc.)
- Updates
- When the Release PR is merged:
- A GitHub Release is created with the tag
- WASM artifacts are built and attached automatically
Example: If you merge feat: ... and fix: ... commits, the next release will be a minor version bump (0.1.0 → 0.2.0).
-
Branch off
mainwith a descriptive name:git checkout -b feat/your-feature # or git checkout -b fix/your-bugfix -
Commit with clear messages following Conventional Commits.
-
Before pushing, make sure:
-
cargo testpasses -
cargo fmt -- --checkis clean -
cargo clippy --all-targets -- -D warningsis clean - Commit messages follow Conventional Commits format
-
-
Open a PR against
main. Include:- What the change does and why
- Any relevant issue numbers (
Closes #123) - Notes for reviewers if the change is non-obvious
-
Commit validation: The PR title must follow Conventional Commits format. This is checked automatically by CI.
-
Review: at least one approval is required before merging. Address all review comments; force-push to the same branch to update the PR.
-
Merge: Use "Squash and merge" or "Create a merge commit" (not "Rebase and merge") to preserve commit history for changelog generation.
TrustLink runs automated security audits on every push and weekly via scheduled scans. When vulnerabilities are detected:
- On every push:
cargo audit --deny warningsruns in CI and blocks merges if vulnerabilities are found - Weekly: Scheduled audit runs Monday at 00:00 UTC; failures create a GitHub issue with label
security
When a vulnerability is reported:
| Severity | Action | Timeline |
|---|---|---|
| Critical | Blocks all merges; must fix immediately | Same day |
| High | Blocks merges; fix within 48 hours | 2 days |
| Medium | Blocks merges; fix within 1 week | 7 days |
| Low | Can be accepted if justified; document in Cargo.audit |
Case-by-case |
Option A: Update the dependency
# Update to a patched version
cargo update <crate-name>
# Verify the fix
cargo audit
# Test thoroughly
cargo testOption B: Accept the vulnerability (Low severity only)
If the vulnerability does not affect TrustLink's usage pattern:
- Open
Cargo.auditand add an entry:
[[advisories]]
id = "RUSTSEC-YYYY-NNNNN"
reason = "Vulnerability does not affect our usage - we do not use feature X"
date = "2024-01-15"
reviewer = "your-github-username"- Run audit to verify it's accepted:
cargo audit- Commit with clear message:
git add Cargo.audit
git commit -m "security: accept RUSTSEC-YYYY-NNNNN - documented in Cargo.audit"- All vulnerability fixes require at least one approval
- Reviewer must verify:
- The fix doesn't introduce breaking changes
- Tests still pass
- No new vulnerabilities are introduced
- Document the decision in the PR description
For critical vulnerabilities affecting production:
- Create a private security advisory (GitHub Settings → Security → Advisories)
- Notify maintainers immediately
- Prepare a patch release
- Do not disclose publicly until patch is available
# Check for vulnerabilities
cargo audit
# Deny any warnings (same as CI)
cargo audit --deny warnings
# Generate a JSON report
cargo audit --json > audit-report.json
# Check specific advisory
cargo audit --advisory RUSTSEC-YYYY-NNNNN- Keep dependencies up-to-date with security patches
- Review changelogs before major version updates
- Test thoroughly after updates
- Document breaking changes in PR description
Open a GitHub issue with:
- A clear description of the problem or feature request
- Steps to reproduce (for bugs)
- Expected vs actual behaviour
TypeScript bindings for the contract ABI live in bindings/typescript/ and are
generated from the compiled WASM using the Stellar CLI.
Prerequisites:
cargo install --locked stellar-cli --features opt
rustup target add wasm32-unknown-unknownRegenerate after any contract interface change:
make bindingsThis builds the WASM and runs:
stellar contract bindings typescript \
--wasm target/wasm32-unknown-unknown/release/trustlink.wasm \
--contract-id 0000000000000000000000000000000000000000000000000000000000000001 \
--network testnet \
--output-dir bindings/typescriptCommit the updated bindings/typescript/ directory alongside your contract
changes. CI runs make check-bindings and will fail if the committed bindings
do not match the current WASM.