|
| 1 | +# Circle Lifecycle: Creation, Rounds, Payouts, and Dissolution |
| 2 | + |
| 3 | +## Circle Statuses |
| 4 | + |
| 5 | +| Status | Description | |
| 6 | +|--------|-------------| |
| 7 | +| `PENDING` | Circle created but not yet started | |
| 8 | +| `ACTIVE` | Circle is running; contributions and payouts are in progress | |
| 9 | +| `COMPLETED` | All rounds have finished | |
| 10 | +| `CANCELLED` | Circle was cancelled before completion | |
| 11 | + |
| 12 | +## Full Lifecycle |
| 13 | + |
| 14 | +### Stage 1 — Creation |
| 15 | +**Who**: Any authenticated user (becomes the organizer) |
| 16 | + |
| 17 | +**How**: `POST /api/circles` with: |
| 18 | +- `name`, `description` |
| 19 | +- `contributionAmount` — how much each member contributes per round |
| 20 | +- `contributionFrequencyDays` — how many days between rounds |
| 21 | +- `maxRounds` — total number of rounds (= total number of members who will receive a payout) |
| 22 | + |
| 23 | +**What happens**: |
| 24 | +- Circle is created with status: **ACTIVE** and `currentRound`: 1 |
| 25 | +- Organizer is automatically added as the first `CircleMember` with `rotationOrder`: 1 |
| 26 | +- If deploying the smart contract: `initialize_circle()` is called on-chain with the same parameters |
| 27 | + |
| 28 | +### Stage 2 — Member Recruitment |
| 29 | +**Who**: Organizer |
| 30 | + |
| 31 | +**How**: Share the circle ID or invite link. Members join via `POST /api/circles/:id/join`. |
| 32 | + |
| 33 | +**On-chain**: Organizer calls `add_member(organizer, newMemberAddress)` for each new member. |
| 34 | + |
| 35 | +**Constraints**: |
| 36 | +- Maximum 50 members by default (hard cap 100 on-chain) |
| 37 | +- Members cannot join once the circle is full |
| 38 | + |
| 39 | +**Rotation order**: Before the first round begins, the organizer should call `shuffle_rotation()` on the smart contract to randomise the payout order using a Fisher-Yates shuffle seeded by the ledger sequence number. |
| 40 | + |
| 41 | +### Stage 3 — Contribution Round |
| 42 | +**Who**: All members |
| 43 | + |
| 44 | +**How**: Each member calls `POST /api/circles/:id/contribute` with `{ amount }`. |
| 45 | + |
| 46 | +**What happens per contribution**: |
| 47 | +- A `Contribution` record is created with status: **COMPLETED** and the current round number |
| 48 | +- The member's `totalContributed` is incremented in `CircleMember` |
| 49 | +- All members who have **NOT** yet contributed this round receive a reminder email |
| 50 | +- If all members have now contributed, the member whose `rotationOrder` matches `currentRound` receives a payout alert email |
| 51 | + |
| 52 | +**On-chain**: The member calls `contribute(member, amount)` which transfers tokens from the member's wallet to the contract. The contract advances the round deadline when all members have contributed. |
| 53 | + |
| 54 | +### Stage 4 — Payout |
| 55 | +**Who**: The member whose turn it is (determined by `rotationOrder === currentRound`) |
| 56 | + |
| 57 | +**How**: Member calls `claim_payout(member)` on the smart contract. |
| 58 | + |
| 59 | +**Payout amount**: `contributionAmount × memberCount` |
| 60 | + |
| 61 | +**Constraints**: |
| 62 | +- If `shuffle_rotation()` was called, the contract enforces that only the correct member can claim |
| 63 | +- Each member can only receive one payout (`hasReceivedPayout` flag) |
| 64 | + |
| 65 | +**After payout**: `currentRound` increments and the next round begins. |
| 66 | + |
| 67 | +### Stage 5 — Repeat |
| 68 | +Stages 3 and 4 repeat for each round until `currentRound > maxRounds`. |
| 69 | + |
| 70 | +### Stage 6 — Completion |
| 71 | +When all `maxRounds` have been completed: |
| 72 | +- Circle status is updated to **COMPLETED** |
| 73 | +- All members have received exactly one payout |
| 74 | +- Total paid out = `contributionAmount × memberCount × maxRounds` |
| 75 | + |
| 76 | +## Partial Withdrawals |
| 77 | + |
| 78 | +Members can withdraw a portion of their contributed funds at any time during an active circle, subject to a **10% penalty**. |
| 79 | + |
| 80 | +**How**: Call `partial_withdraw(member, amount)` on the smart contract. |
| 81 | + |
| 82 | +**Available balance**: `totalContributed - totalWithdrawn` |
| 83 | + |
| 84 | +**Net received**: `amount - (amount × 10 / 100)` |
| 85 | + |
| 86 | +**Example**: Requesting a 100 XLM withdrawal returns 90 XLM; 10 XLM is kept as a penalty. |
| 87 | + |
| 88 | +**Note**: Partial withdrawals are blocked during **Panicked** state — use `emergency_refund` instead. |
| 89 | + |
| 90 | +## Emergency Scenarios |
| 91 | + |
| 92 | +### Organizer Panic |
| 93 | +If the organizer detects a critical issue, they can call `panic(organizer)` on the smart contract. This: |
| 94 | +- Immediately sets circle status to **Panicked** |
| 95 | +- Blocks all contributions, payouts, and partial withdrawals |
| 96 | +- Enables `emergency_refund` for every member (**no penalty**) |
| 97 | + |
| 98 | +### Member-Initiated Dissolution |
| 99 | +Any member can start a dissolution vote via `start_dissolution_vote`. If the vote passes: |
| 100 | +- Circle status transitions to **Dissolved** |
| 101 | +- Each member calls `dissolve_and_refund` to receive `totalContributed - totalWithdrawn` (**no penalty**) |
| 102 | + |
| 103 | +## Member Rotation Order |
| 104 | + |
| 105 | +The payout rotation determines who receives the pooled funds each round. |
| 106 | + |
| 107 | +**Default**: Members are added in join order with sequential `rotationOrder` values. |
| 108 | + |
| 109 | +**Shuffled**: The organizer calls `shuffle_rotation()` before round 1 to randomise the order. The shuffle uses Fisher-Yates with a seed derived from the ledger sequence number, making it verifiably random and tamper-resistant. |
| 110 | + |
| 111 | +**Example** with 4 members and 100 XLM contribution: |
| 112 | + |
| 113 | +| Round | Payout Recipient | Amount Received | |
| 114 | +|-------|------------------|-----------------| |
| 115 | +| 1 | Member at rotationOrder 1 | 400 XLM | |
| 116 | +| 2 | Member at rotationOrder 2 | 400 XLM | |
| 117 | +| 3 | Member at rotationOrder 3 | 400 XLM | |
| 118 | +| 4 | Member at rotationOrder 4 | 400 XLM | |
| 119 | + |
| 120 | +Each member contributes 100 XLM × 4 rounds = 400 XLM total, and receives 400 XLM once. **Net result**: zero loss, but each member gets access to a lump sum when it's their turn. |
| 121 | + |
| 122 | +## Circle Statistics (Dashboard) |
| 123 | + |
| 124 | +The dashboard displays these computed values for each circle: |
| 125 | + |
| 126 | +| Stat | Calculation | |
| 127 | +|------|-------------| |
| 128 | +| **Members** | `circle.members.length` | |
| 129 | +| **Current Round** | `circle.currentRound / circle.maxRounds` | |
| 130 | +| **Per Contribution** | `circle.contributionAmount XLM` | |
| 131 | +| **Payout Amount** | `circle.contributionAmount × circle.members.length XLM` | |
| 132 | + |
0 commit comments