KZP Mini is a Solana implementation of a workplace mutual-aid fund (Kasa Zapomogowa Pracownicza).
Members:
- join a shared pool
- deposit savings
- request loans with two guarantors
- repay obligations
- withdraw savings and leave
The protocol enforces financial mechanics on-chain while allowing membership policy and governance to remain off-chain. Built in Rust using Anchor.
Superteam Poland submission - entry for Build everyday real-world systems as on-chain Rust programs on Superteam Earn.
Repository: github.com/vvylym/kzp-mini
| Property | Value |
|---|---|
| Blockchain | Solana (devnet deployed) |
| Language | Rust stable for host builds |
| Framework | Anchor crates 1.1.2 |
| Solana crates | solana-client 4.1.0 · solana-program-test 4.1.0 · solana-sdk 4.0.1 |
| Loan model | Two guarantors, 3× savings cap |
| Savings model | Shared SPL vault + ledger |
| Client | kzp CLI |
| License | MIT |
Traditional workplace savings associations rely on spreadsheets, manual bookkeeping, paper signatures, and committee decisions. That creates opaque balances, delayed settlement, manual verification, and dispute risk.
KZP Mini replaces administrative coordination with on-chain enforcement.
| Concern | Traditional handling | Friction |
|---|---|---|
| Membership | HR roster, payroll deductions | Manual roster upkeep |
| Savings | Spreadsheet per member | Opaque balances |
| Loans | Committee + verbal guarantees | Signatures disconnected from money |
| Default | Social pressure, delayed settlement | No shared rule engine |
| Exit | Manual “all clear” check | Error-prone |
| Traditional concept | On-chain implementation |
|---|---|
| Pool / cash box | Pool + Vault PDAs |
| Member record | Member PDA |
| Loan | Loan PDA (Pending → Active → closed on repay/default) |
| Two guarantors | Both must co-sign before disbursement |
| Default | Permissionless settle_default after due date; 50/50 reserved guarantor liability |
Deeper domain and mapping: docs/ARCHITECTURE.md.
- One-time entry fee on join
- Member deposits to shared vault
- Per-member savings ledger
- Loan requests with two nominated guarantors
- Partial then full co-sign
- Automatic disbursement when both guarantors approve
- Loan cap at 3× member savings
- One unresolved loan per borrower
- Up to five guarantees per member
- Exit blocked while borrowing or guaranteeing
- Pool initialization and SPL mint binding
- Permissionless due-date default settlement from reserved guarantor savings liability
Program ID: GsjUnBFvYtcxNwCrydPUjQTngGTqdx5v7APnWahnqwkx
The same program ID is preserved across releases. The original bounty submission remains available as release/0.1.0 and v0.1.0. The current hardened revision is release/0.2.0 / v0.2.0.
- Upgrade program in place
- Initialize pool
- Join members (admin + two guarantors)
- Deposit borrower savings and guarantor backing
- Request loan
- Co-sign (guarantor A, then B)
- Disburse principal
- Repay loan and close terminal loan account
- Exit pool
| Step | Transaction |
|---|---|
| Program upgrade | explorer |
| Initialize pool | explorer |
| Admin join | explorer |
| Guarantor A join | explorer |
| Guarantor B join | explorer |
| Admin deposit savings | explorer |
| Guarantor A deposit savings | explorer |
| Guarantor B deposit savings | explorer |
| Request loan | explorer |
| Co-sign (guarantor A) | explorer |
| Co-sign + disburse (B) | explorer |
| Repay loan | explorer |
| Exit pool | explorer |
0.2.0 demo accounts: pool 5CFCSvhNdCdGYCAZYcP7taA87vAmKsSLtiHPP8TBWeng · loan 2LRcvkpTdkNQn5gVpJPEfgj4imkj8bTm5JeEHGvHKZPP · mint 8GKf5BwMfFEZmkoGYuH4vNwGy1qgdxpSesq6jGp55J7a
bash scripts/devnet-demo.sh # full scripted lifecycleRust stable, Solana CLI 4.x, Anchor CLI 1.1.2, Anchor crates 1.1.2, Solana crates 4.x, cargo-nextest. Full toolchain notes: docs/GETTING_STARTED.md.
git clone https://github.com/vvylym/kzp-mini.git && cd kzp-mini
mkdir -p target/deploy && cp keys/program.json target/deploy/kzp_mini-keypair.json
NO_DNA=1 anchor build --ignore-keys
cargo build -p kzp-cli --releaseKeypairs are local-only - see keys/README.md.
bash scripts/deploy.sh devnetcargo run -p kzp-cli --release -- configCommands grouped by role. Full reference: docs/CLI.md.
| Role | Commands |
|---|---|
| Pool setup / cranks | pool initialize · permissionless loan settle-default |
| Members | pool join · pool deposit · pool exit |
| Borrowers | loan request · loan repay · loan cancel |
| Guarantors | loan cosign · loan withdraw-cosign |
kzp pool initialize --name "My Pool" --entry-fee 50 --mint <MINT>
kzp pool join --pool <POOL> --entry-fee 50
kzp pool deposit --pool <POOL> --amount 1000000
kzp loan request --pool <POOL> --nonce 1 --amount 500000 \
--guarantor-a <PUBKEY_A> --guarantor-b <PUBKEY_B>
kzp loan cosign --loan <LOAN>
kzp loan repay --loan <LOAN> --amount 500000
kzp pool exit --pool <POOL>Global flags: --cluster, --rpc-url, --wallet, --dry-run.
Pool PDA
│
▼
Vault PDA
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Member PDA Member PDA Member PDA
│
▼
Loan PDA
| Account | Purpose |
|---|---|
| Pool | Admin, mint, counters |
| Vault | SPL token custody |
| Member | Savings, loan/guarantee obligations |
| Loan | Principal, guarantors, status |
Instructions (10): initialize_pool · join_pool · deposit_savings · request_loan · co_sign_loan · repay_loan · cancel_loan · withdraw_cosign · settle_default · exit_pool
Details: docs/ARCHITECTURE.md · docs/INSTRUCTIONS.md · docs/STATE.md
Two parallel views:
- SPL vault - actual token balance
- Ledger -
pool.total_savingsandmember.savings_balance
Entry fees fund the vault but not member savings (liquidity buffer). Disburse and exit are fail-closed if vault balance is insufficient.
Full model: docs/ACCOUNTING.md.
Pending → Active → full repay (loan account closed)
↘ due default (loan account closed)
Pending → cancel_loan
| State | Guarantor tracking | Escape hatches |
|---|---|---|
| Pending | pending_guarantee_count |
Borrower: cancel_loan · Guarantor: withdraw_cosign |
| Active | active_guarantee_count + loan-local backing |
Repay or permissionless settle_default after due date |
| Closed | Counts and backing cleared | Guarantors may exit when obligations are cleared |
- Membership and savings state
- Loan limits and guarantor rules
- Co-sign before disbursement
- Vault liquidity checks
- Arithmetic safety (
checked_add/checked_sub)
- Who may join the pool (workplace policy)
- Whether a due loan should be cranked as defaulted (off-chain policy)
- SPL mint legitimacy at pool creation
Full threat model: docs/SECURITY.md.
programs/kzp-mini/src/
├── instructions/ Anchor account constraints + one handle per instruction
├── operations/ Pure business rules (unit-tested)
├── state.rs Pool, Member, Loan
└── utils/ PDA helpers
cli/ kzp command-line client
tests/ solana-program-test integration suite
docs/ Architecture, CLI, accounting, security
scripts/ ci.sh, deploy.sh, devnet-demo.sh
keys/ Local keypairs (gitignored)
Philosophy: instructions = account validation and orchestration · operations = testable rules without Anchor contexts.
| Layer | Coverage |
|---|---|
| Unit tests | programs/kzp-mini/src/operations/ |
| Integration | 62 solana-program-test scenarios |
| Specs | BDD criteria in docs/USE_CASES.md |
bash scripts/ci.shConventions: tests/README.md.
| Document | Audience | Purpose |
|---|---|---|
| README.md | Everyone | Overview and navigation |
| docs/GETTING_STARTED.md | Developers | Install, deploy, devnet setup |
| docs/CLI.md | Operators | Full CLI by role |
| docs/ARCHITECTURE.md | Reviewers | Accounts, instructions, mapping |
| docs/ACCOUNTING.md | Auditors | Ledger vs vault, limits |
| docs/SECURITY.md | Auditors | Threat model |
| docs/INSTRUCTIONS.md | Integrators | Instruction account metas |
| docs/STATE.md | Integrators | Account layouts |
| docs/USE_CASES.md | Contributors | Acceptance criteria |
| docs/ERRORS.md | Integrators | Error codes |
Index: docs/README.md.
| Topic | Choice | Implication |
|---|---|---|
| Policy vs code | Eligibility/default policy off-chain | Any signer can settle_default after due_ts; off-chain policy decides when to crank |
| Single admin | One pool.admin |
No multisig in this release |
| Default split | 50/50 (div_ceil on odd amounts) |
First guarantor pays extra token on odd sums |
| Loan cap | 3× savings, 5 guarantees | May block edge cases |
| Ledger vs vault | Both tracked; fail-closed exit/disburse | Safer; monitor for drift |
| Pending loans | Only borrower can cancel | Abandoned PDAs pay rent until cancelled |
| Upgrades | No migration instruction | Schema changes need fresh pool deploy |
| Toolchain | Stable host Rust, Anchor crates 1.1.2, Solana crates 4.x | SBF builds still use Solana's bundled compiler; do not set a workspace rust-version above that compiler's support |
- Multisig pool administration
- On-chain governance voting for defaults
- Accrued yield on member savings (pool-level interest distribution)
- Dynamic guarantee models (e.g. variable split)
- Account migration support
bash scripts/ci.shBefore opening a PR: cargo fmt, cargo clippy, cargo nextest, anchor build. See docs/GETTING_STARTED.md for toolchain details.
MIT