On-chain workplace mutual-aid pool: coworkers save together and borrow with two guarantors who accept partial liability on default.
The program enforces membership state, savings, loan limits (3× savings), co-sign before disbursement, repayment, cancellation, permissionless due-date default settlement, and exit only when obligations are clear. Who may join and when a default is justified stay off-chain; on-chain code enforces mechanics and fund safety.
Pool PDA
│
▼
Vault PDA
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Member PDA Member PDA Member PDA
│ │ │
▼ │ │
Loan PDA │ │
│ │
(guarantor members) ──────┘
| Account | Seeds | Role |
|---|---|---|
| Pool | ["pool", admin, pool_name] |
Config, aggregate counters |
| Vault | ["vault", pool] |
SPL token custody |
| Member | ["member", pool, owner] |
Savings ledger, obligations |
| Loan | ["loan", pool, borrower, loan_nonce_le] |
Pending → Active → closed on full repay/default |
PDA helpers: kzp_mini::utils::pda · CLI: cli/src/pda.rs.
| Instruction | Purpose |
|---|---|
initialize_pool |
Admin creates pool + SPL vault |
join_pool |
Pay entry fee, open member PDA |
deposit_savings |
Credit savings; tokens to vault |
request_loan |
Open pending loan (≤ 3× savings, two guarantors) |
co_sign_loan |
Guarantor co-signs; disburses when both sign |
repay_loan |
Partial or full repayment |
cancel_loan |
Borrower cancels pending loan |
withdraw_cosign |
Guarantor revokes partial co-sign |
settle_default |
Permissionless due default; 50/50 guarantor split |
exit_pool |
Withdraw savings; close member PDA |
Full account metas: INSTRUCTIONS.md. Field layouts: STATE.md.
| Traditional concept | On-chain implementation |
|---|---|
| Pool / cash box | Pool PDA + vault PDA |
| Member record | Member PDA (savings_balance, obligations) |
| Loan application | Loan PDA lifecycle |
| Two guarantors | guarantor_a / guarantor_b; both co-sign to disburse |
| Partial approval | guarantor_*_signed + pending_guarantee_count |
| Disbursement | co_sign_loan CPI when vault ≥ principal |
| Repayment | repay_loan CPI + counter updates |
| Default | settle_default - 50/50 reserved savings ledger settlement |
| Leave pool | exit_pool pays savings, closes PDA |
programs/kzp-mini/src/
├── lib.rs #[program] dispatch → instructions::*::handle
├── state.rs Pool, Member, Loan, LoanStatus
├── instructions/ Anchor account constraints + one handle per instruction
├── operations/ Pure rules/math (unit-tested)
└── utils/ PDA seeds and helpers
Design split: instructions/ = account validation and orchestration · operations/ = pure business rules testable without Anchor contexts.
The file instructions/withdraw_co_sign.rs maps to the withdraw_cosign instruction name.
Pending → Active → full repay (account closed)
↘ due default (account closed)
Pending → cancel_loan (account closed)
| Actor | Escape hatch while Pending |
|---|---|
| Borrower | cancel_loan |
| Guarantor | withdraw_cosign |
Pending vs active guarantee counts: partial co-sign only increments pending obligations; active counts and loan-local backing are set after both co-signs and successful disbursement.
See ACCOUNTING.md for ledger vs vault. See SECURITY.md for trust boundaries.