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 pool

Instructions & 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.