Documentation
How Savora works — for members, and for anyone verifying the claim.
Using Savora
Ajo, onchain
Ajo (also esusu, adashe, or a rotating savings and credit association) is a group that agrees on a fixed amount, contributes it every round, and hands the whole pot to one member per round — turn by turn, until everyone has collected once.
Traditionally an organiser holds the money between rounds. Savora removes that role: the pot sits in a program-owned vault on Solana, and the program — not a person — decides who receives each payout.
Starting a circle
Set the contribution amount, the security deposit (at least one contribution), the number of seats (2–12), how long each round runs, the grace window after each deadline, how many full rotations to run, and a name. You become the first member and lock your deposit. The circle is in Forming until every seat is taken.
Joining & the deposit
Every circle has a link — /g/<address>. Anyone with it can join while seats remain. Joining locks a security deposit plus your first round’s contribution. When the last seat fills, the circle goes Active; the firstopen_cycle shuffles the order and opens round 1. Before a circle seals, any member except the creator can leave and get their deposit back; the creator can close an empty circle.
The deposit is refunded when the circle finishes and you withdraw. It is forfeited only if you miss a round. One round’s deposit covers exactly one missed round — it does not cover the loan a member walks away with if they stop paying after collecting, so a circle that wants more assurance can set a larger deposit.
Contributing each round
Each round you send the fixed amount to the vault. Late is fine up to the deadline plus the grace window; after that the door closes and you can no longer pay in. You cannot contribute twice to the same round, and the round’s recipient owes nothing that round.
Collecting your turn
When it is your round, you receive the pool. You do not have to be online: the last contribution the round needs pays you out in the same transaction. If the round instead reaches its deadline short, anyone can trigger the payout with whatever was pooled. The program sends it to whoever the rotation says — it cannot be redirected.
Grace, defaults & ejection
After a round’s deadline, a grace window (set per circle) gives stragglers time to pay. Once grace closes with money still missing, the crank ejects every no-show: their deposit is forfeited into the round they missed — so that round’s recipient is still made whole — and they are removed from the rotation, their slot marked permanently. If ejections leave fewer than two active members, the circle ends as Failed and the rest withdraw their deposits.
Running it again
A circle runs the number of rotations set at creation, then goes Completed — but that is not the end. The creator can propose more rotations; the extension starts only once every live member opts in. An extension is all-or-nothing: if one member declines — by withdrawing their deposit — the whole proposal is cancelled and the circle returns to Completed. At any completion you can simply withdraw your deposit and walk away.
Getting a wallet & devnet funds
You do not need a wallet installed. Sign in with email or Google and one is created for you in the background; if you already use a Solana wallet, connect that instead.
This deployment runs on Solana devnet. A new wallet has no SOL for transaction fees — your profile page has an airdrop button — and test USDC comes from Circle’s faucet.
Under the hood
Where the money sits
The vault is an associated token account whose authority is the Group PDA — a program-derived address with no private key. Funds can only move via a program instruction, and the only instruction that moves them out is the payout, which is constrained to the rotation’s recipient.
There is no admin authority anywhere in the program: no pause, no upgrade-only withdrawal, no close-to-creator. The one sweep is close_group, and only the creator, only while the circle is empty and forming, can call it. A token sent straight to the vault after that is stranded — nobody can move it.
The rotation shuffle
At the start of each rotation, open_cycle derives a seed from a recent slot hash (the SlotHashes sysvar) mixed with the group address and salted with the rotation index, expands it with SplitMix64, and runs a Fisher–Yates shuffle over the live member slots. The result is written to Group.rotation and used for that whole rotation.
No party chooses the order, and anyone can recompute it from the same public inputs. The caveat, stated plainly: whoever lands the boundary open_cycle transaction chooses the slot hash and could grind it — once per rotation, not just at seal. For a circle of people who know each other we accept that rather than take on a VRF’s complexity — but you should know it.
Auto-payout & the crank
When the last outstanding contribution lands, contribute disburses the payout in the same transaction — no separate step — as long as the recipient’s token account already exists.
Otherwise disburse_payout, callable by any signer, handles it: once the round is funded, or once the grace window has closed. The recipient is group.members[cycle.recipient_index] and the recipient token account is constrained to that owner and the group’s mint, so the caller cannot point the payout elsewhere. No-shows are ejected and their deposits forfeited into the pool; the transfer is signed by the group PDA; the rotation advances.
Accounts
Group PDA ["group", creator, seed: u64]
creator, seed, mint pinned at creation
contribution, deposit: u64 per round / locked at join (deposit >= contribution)
cycle_secs, grace_secs: i64 round length / post-deadline window
capacity, seat_count: u8 seats / assigned
members: [Pubkey; 12] slot -> wallet; tombstoned, never compacted
rotation: [u8; 12] live slots for the current rotation, reshuffled each pass
rotation_len, rotation_pos payouts this rotation / position in it
rotations_target/_done: u8 agreed rotations (grows on extension) / completed
ejected, defaulted: u16 bitmasks — out of the circle / ejected for a miss
optin_mask: u16 extension opt-ins
status Forming | Active | Completed | Extending | Failed
current_cycle: u16 global, monotonic — the Cycle PDA seed
Cycle PDA ["cycle", group, index: u16]
recipient_index: u8 slot that collects; owes nothing this round
deadline: i64 = opened_at + cycle_secs
pooled: u64 contributions + forfeited deposits
contributed, required: u16 settled / on the hook (live_mask at open)
ejected_here: u16 slots this crank ejected
disbursed: bool
payout: u64
Vault associated token account, authority = Group PDA
holds every live deposit + the open round's poolInstructions & PDAs
create_group(seed, name, contribution, deposit, cycle_secs, grace_secs, capacity, rotations) join_group() locks the deposit; goes Active when the last seat fills leave_group() Forming only, non-creator; refunds the deposit open_cycle() permissionless; inits the Cycle; reshuffles at a rotation boundary contribute() member -> vault; auto-disburses if it completes the round disburse_payout() permissionless crank; ejects no-shows past grace propose_extension() creator, Completed; propose more rotations opt_in_extension() seals the extension once every live member has opted in cancel_extension() creator any time, or anyone once the opt-in window closes close_position() withdraw the deposit + exit; during Extending it cancels the proposal close_group() creator, Forming, seat_count == 1; refunds + closes
Program BbXwxUfyF2xZydVZRhFZ5Fp5KALf9bgYEZvi7b3bhtG2
Program data JAvBqYpG9MoiR6iwYherdHchWDjg7CzMo3indGYCDk5w
Upgrade authority AL3LxYBsFcShcGq7kuQSA4mN8dSVKyvNQdHsQE9WT7VX
IDL account GVjyn6Gbn9dTr8AVdJzArD6YGr9e8xzndWcryghfstMD
Devnet USDC mint 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
Deployed to devnet. The program is upgradeable by the authority above; a production release would set it to a multisig or burn it. Only classic SPL Token mints are accepted — a Token-2022 mint with a transfer hook or fee would break the vault accounting, so it is rejected at creation.
What Savora cannot do
- Take custody — the vault authority is a PDA; no Savora key can sign for it.
- Pause or freeze a circle — there is no admin instruction.
- Change the collection order — the shuffle is fixed for each rotation.
- Redirect a payout — the recipient account is checked on every payout.
- Touch a live member's deposit — a payout only ever draws the round's pool.
- Sweep the vault — except a creator closing their own empty, unfilled circle.