Thank you for your interest in contributing to Lafiya! Lafiya is an open-source Digital Public Good (DPG) aiming to bring verified, patient-controlled emergency health cards to the last mile. Your contributions help make this trust layer more robust, secure, and accessible.
This repository holds the Soroban (Stellar) smart contracts. Because Lafiya is a multi-repo ecosystem, contributions here often have ripple effects across other repositories. This guide outlines the setup, conventions, and workflows required to contribute safely.
- Rust (stable), installed via rustup
- The
wasm32v1-nonetarget:rustup target add wasm32v1-none pre-commit(required for local git hooks): Install viapip install pre-commitorbrew install pre-commit, then runpre-commit installin the repository root.
- Local Setup
- Branching & Commit Conventions
- Cross-Repo Coordination & Shared Contracts
- Database & Supabase Migrations
- Smart Contract Development & Quality Standards
- Pull Request Process
The core development environment requires Rust and the Soroban SDK.
For the quick-start commands, see the Getting Started section in the README.md.
- Rust (stable): Install via rustup.
- Wasm target:
rustup target add wasm32v1-none(pinned viarust-toolchain.toml). - Stellar CLI: Needed for deploying and interacting with the testnet. Install it via Cargo:
cargo install --locked stellar-cli --features opt
To maintain a clean, navigable history for auditability and open-source collaboration, we follow these conventions:
feature/short-descriptionfor new features or smart contract functions.bugfix/short-descriptionfor bug fixes.docs/short-descriptionfor documentation-only changes.chore/short-descriptionfor build tasks, dependencies, etc.
We encourage Conventional Commits:
feat(registry): add batch attestation supportfix(allowlist): correct signature verification checktest(contracts): add tests for admin transferdocs: update contributing guide for cross-repo changes
Lafiya is composed of five distinct repositories in the Lafiya-xyz organization:
- lafiya-web: Next.js web application (patient records, QR, allowlist management interface).
- lafiya-contracts (this repo): Soroban smart contracts (attester allowlist, attestation registry).
- lafiya-docs: Architectural documentation, threat model, and references.
- .github: Organization-level files.
- lafiya-verifier: Standalone verification tool.
The on-chain attestation schema (a 32-byte record hash, attester Address, and timestamp) acts as a shared contract between lafiya-contracts and lafiya-web.
Important
If you modify a smart contract function signature, event payload, or the return shape of get_attestation, you must flag this change. It will break the off-chain patient profile and verification displays in lafiya-web.
How to flag cross-repo changes:
- Check the Cross-Repo Impact section in the PR template.
- Link the corresponding issue/PR in the
lafiya-webrepository. - Coordinate with maintainers to ensure both repositories are updated and deployed in tandem.
While lafiya-contracts is a Rust smart contract repository and contains no database code:
- The main web application
lafiya-webuses Supabase for its encrypted off-chain storage. - If your contribution spans both the smart contracts and the database schema (e.g., adding field tracking for attestation IDs off-chain):
- Supabase CLI: Use the Supabase CLI to generate a new migration:
supabase migration new your_migration_name
- Hand-Authored Types: We use hand-authored types for database safety and strict runtime boundaries. The types are documented and maintained in:
lafiya-web/lib/supabase/types.ts
[!WARNING] Do not auto-generate database types and overwrite
lib/supabase/types.tsblindly. Any schema change must have its typescript types updated by hand following the existing patterns to preserve custom wrappers, type guards, and safety boundaries.
To maintain high security and minimize gas/storage costs on Soroban, all contract code must adhere to:
- Interact with other contracts (e.g.,
attestation-registrycallingattester-registry) through a client trait interface using the#[contractclient]macro. - Do not add direct crate dependencies between contracts to prevent linking duplicate symbols and bloating WASM binary sizes.
- Every public contract function must have accompanying unit tests in its crate's
src/test.rs. - Tests must cover:
- Success paths: Standard execution flow.
- Authorization paths: Proper validation of admin or user signatures (
require_auth()). - Failure paths: Rejection of double-initialization, invalid inputs, and unauthorized calls.
- Events: Verify that correct events (like
AttesterAdded,AttestationRecorded) are emitted.
attester-registry and attestation-registry each have a fuzz_test.rs module (built with proptest, already a dev-dependency in both crates) alongside their regular test.rs:
attester-registry::fuzz_testgenerates arbitrary sequences ofadd_attester/remove_attester/suspend_attester/reinstate_attestercalls over a small pool of addresses and checks, after every step, thatis_attesteragrees with a plain-Rust model — i.e. it never observes an address as simultaneously allowlisted and not.attestation-registry::fuzz_testcallsattestwith arbitrary/adversarial 32-byterecord_hashvalues (including all-zero and all-0xFF) and in unusual orderings relative toinitialize, asserting it only ever returns a typedResultand never panics.
CI runs these with a small, time-bounded case count (PROPTEST_CASES=256, see .github/workflows/ci.yml) as a non-blocking job — a regression there is a signal to investigate, not a merge blocker, since proptest's case count/seed is inherently variable run to run.
To fuzz much harder locally (uncapped, until you stop it or hit a shrink-and-report), raise the case count and optionally the max shrink iterations:
PROPTEST_CASES=100000 cargo test -p attester-registry fuzz_test -- --nocapture
PROPTEST_CASES=100000 cargo test -p attestation-registry fuzz_test -- --nocaptureIf proptest finds a failing case, it shrinks it to a minimal repro and writes it to contracts/<crate>/proptest-regressions/fuzz_test.txt; commit that file alongside your fix so the minimal input becomes a permanent regression test (proptest replays entries from that file automatically on every run).
If a crash or invariant violation surfaces while working on either fuzz target, file it as its own bug report and fix it — don't fold an unrelated fix into a feature PR.
Always run the validation suite locally before committing:
make checkThis runs:
make fmt(code formatting verification)make clippy(linter checks; warnings are treated as errors)make test(all cargo tests)make wasm(building target WASM binaries)
- Every new contract function needs unit tests covering both the success
path and the failure/authorization paths (see
contracts/*/src/test.rsfor existing patterns usingsoroban_sdk::testutils). - Cross-contract calls should go through a
#[contractclient]trait interface (seeattestation-registry'sAttesterRegistryInterface), not a direct crate dependency on the callee — depending on the whole crate links its contract implementation into your wasm build too. - Any pull request (PR) that changes contract behavior, storage schemas, or public function signatures must include a corresponding entry in
CHANGELOG.mdunder the[Unreleased]section. Refer to releasing.md for details. - Run
make checklocally before pushing; it's the same set of checks CI runs. - Keep
Cargo.lockcommitted and up to date so builds are reproducible.
- Fork the repository and create your branch from
main. - Ensure your changes compile and pass all quality checks locally (
make check). - Fill out the Pull Request Template completely, paying extra attention to the Cross-Repo Impact section if your changes touch shared interfaces.
- An admin will review your PR. All checks in CI must pass before merging.