Member join — an invitee joins the circle and locks their deposit. Allowed while Recruiting (the cohort forms + funds before Start) and while Active (members may join midway). Rejected when Paused or Dissolved.
fn join(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
deposit_amount: i128,
) -> Result<(), soroban_sdk::Error>
Vote on a proposal
fn vote(
env: soroban_sdk::Env,
voter: soroban_sdk::Address,
proposal_id: u64,
choice: VoteChoice,
) -> Result<(), soroban_sdk::Error>
Withdraw everything owed to member — round payouts, exit refunds, dissolution
shares — all accrue to a claimable balance and are pulled here. Permissionless:
funds always go to member's own address, so the member OR the platform (on their
behalf) can trigger it, and a frozen member only ever blocks its own claim
(invariant I3 — no single account can brick others' funds).
fn claim(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
) -> Result
A violator repays their share of a past round's beneficiary shortfall. The funds flow straight to that round's beneficiary (credited, pull-based). Clears the caller's debt for that round; once their total Owed reaches 0 AND the lockout is served they leave the penalty box. I1 holds throughout: the contract balance and Σ claimable both rise by the repaid amount in the same call (money in → beneficiary's claim funded).
fn repay(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
round: u64,
) -> Result<(), soroban_sdk::Error>
Start the circle — Recruiting → Active. Organizer action, executed
platform-side (admin-authorized). This is the ONLY place StartTime is
stamped, so the round clock begins at Start (not at create). Requires at
least min_members members to have already joined (deposited).
fn start(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
Create a new proposal
fn propose(
env: soroban_sdk::Env,
proposer: soroban_sdk::Address,
proposal_type: ProposalType,
) -> Result
Sponsor a candidate for joining (sponsor must be an Active member)
fn sponsor(
env: soroban_sdk::Env,
sponsor: soroban_sdk::Address,
candidate: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Upgrade contract WASM code (admin only) Contract address and all storage data are preserved.
fn upgrade(
env: soroban_sdk::Env,
new_wasm_hash: soroban_sdk::BytesN<32>,
) -> Result<(), soroban_sdk::Error>
Dissolve the circle and refund every member their deposit (+ positive net balance) immediately — no settle_round wait, no governance vote. Organizer action, executed platform-side (admin-authorized). Works while Recruiting ("cancel before start"), Active, or Paused. dissolve_internal rejects an already-Dissolved circle.
fn dissolve(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
View: total unpaid beneficiary-shortfall debt the member must repay() to leave the box.
fn get_owed(env: soroban_sdk::Env, member: soroban_sdk::Address) -> i128
Get vote record (C2: from persistent storage)
fn get_vote(
env: soroban_sdk::Env,
proposal_id: u64,
voter: soroban_sdk::Address,
) -> Result
Get round information (C2: from persistent storage)
fn get_round(env: soroban_sdk::Env, round_id: u64) -> Result
Contribution
fn contribute(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Get configuration
fn get_config(env: soroban_sdk::Env) -> Result
Get member information
fn get_member(
env: soroban_sdk::Env,
address: soroban_sdk::Address,
) -> Result
Permissionless TTL refresh. A dormant circle (e.g. a Recruiting circle that sits with no joins, or a Dissolved circle whose members haven't all claimed) would otherwise let its instance storage archive after ~30 idle days, freezing access to the circle and its funds. ANYONE — a platform keep-alive cron, or the admin-ui "extend" button — can call this to push the TTL out. Pure maintenance: moves no money, changes no logical state.
fn keep_alive(env: soroban_sdk::Env)
Get list of all member addresses
fn get_members(
env: soroban_sdk::Env,
) -> Result, soroban_sdk::Error>
Step 2: Accept admin transfer (pending admin only)
fn accept_admin(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
Get proposal information (C2: from persistent storage)
fn get_proposal(
env: soroban_sdk::Env,
proposal_id: u64,
) -> Result
Check if a proposal has been cancelled (public query)
fn is_cancelled(env: soroban_sdk::Env, proposal_id: u64) -> bool
Request exit (two-step: sets ExitPending, actual exit happens at settle_round)
fn request_exit(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Settle round (including recipient selection and payout) C3: Permissionless — anyone can call after round ends + grace period
fn settle_round(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
Create a circle: deploy + initialize + (optionally) the creator's join & deposit — ALL atomic in the deploy transaction. Runs once, at deploy.
If creator_deposit > 0 the creator becomes member #1 with their deposit
locked in the SAME transaction; if that transfer fails (e.g. insufficient
balance) the constructor traps and the whole deploy reverts — no half-built
circle, no orphaned funds. Pass creator_deposit == 0 for a pure-organizer
circle (the creator funds later, or never participates).
Auth: only creator signs — they authorize creating their own circle and
moving their own deposit. admin is stored for protocol upgrades only; it
does NOT gate creation, start(), or dissolve() (those are the creator's
authority — there is no system-admin power over a circle's lifecycle).
fn __constructor(
env: soroban_sdk::Env,
admin: soroban_sdk::Address,
creator: soroban_sdk::Address,
min_members: u32,
config: RoscaConfig,
creator_deposit: i128,
platform_revenue: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
View: money currently owed to member, withdrawable via claim().
fn get_claimable(env: soroban_sdk::Env, member: soroban_sdk::Address) -> i128
View: the member's repay share for a specific round (0 if none / already repaid).
fn get_owed_round(
env: soroban_sdk::Env,
round: u64,
member: soroban_sdk::Address,
) -> i128
Get statistics
fn get_statistics(env: soroban_sdk::Env) -> Result
Top up deposit (replenish after violation deductions) NOTE: Intentionally allows top-up during Paused status. This is by design — members should be able to replenish their deposit (e.g. after violation deductions) even while the ROSCA is paused, so they are ready when it resumes. Only Dissolved is blocked.
fn top_up_deposit(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
amount: i128,
) -> Result<(), soroban_sdk::Error>
Step 1: Propose admin transfer (current admin only)
fn transfer_admin(
env: soroban_sdk::Env,
new_admin: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Cancel a proposal (only the proposer can cancel, before voting period ends)
fn cancel_proposal(
env: soroban_sdk::Env,
proposer: soroban_sdk::Address,
proposal_id: u64,
) -> Result<(), soroban_sdk::Error>
Late contribution (with late fee)
fn contribute_late(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Execute a proposal after voting ends
fn execute_proposal(
env: soroban_sdk::Env,
executor: soroban_sdk::Address,
proposal_id: u64,
) -> Result<(), soroban_sdk::Error>
Get current round number
fn get_current_round(env: soroban_sdk::Env) -> Result
Get insurance pool balance
fn get_insurance_pool(env: soroban_sdk::Env) -> Result
Calculate recipient for current round (priority-based, deterministic query) This function is for querying who has highest priority, non-consuming, for reference only
fn calculate_recipient(
env: soroban_sdk::Env,
) -> Result
View: a round's outstanding beneficiary-shortfall record (None if none / fully filled).
fn get_round_shortfall(env: soroban_sdk::Env, round: u64) -> Option
Process exit refund while ROSCA is Paused. During Pause, settle_round cannot run, so ExitPending members would be stuck. This function allows any ExitPending member to claim their refund directly.
fn process_paused_exit(
env: soroban_sdk::Env,
member: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
Permissionless safety net (invariant I2 — no fund is hostage to one party): if a Recruiting circle is never started or cancelled (organizer offline / lost key), ANYONE can dissolve it after RECRUITING_TIMEOUT, making every deposit claimable.
fn dissolve_recruiting_timeout(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>