You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let a stack be configured to run the rotation N times (each member claims N times) instead of exactly once. Requires contract changes in saving-circles plus a redeploy.
Motivation
A stack currently runs the rotation exactly once, and that's baked into the contract rather than being a product choice:
hasClaimed is a single bool per member (MemberState), never reset — a member structurally cannot claim twice
the round's claimer is circleMembers[_id][currentRound], and currentRound >= members.length means expired
So a group that wants to keep saving together has no path other than creating a whole new stack.
#170 gives that path without a contract change — replicate into a new circle id. This issue is the durable version: one stack, one continuous history, no per-cycle re-enrollment, configured up front. Worth doing when the contract is next redeployed, alongside the circleState() / start() terminal-state fix described in #160.
Required contract changes (saving-circles)
Circle gains a rounds / cycles field.Circle is a mapping value in an upgradeable contract — this needs an explicit storage-layout / upgrade-safety review, not an assumption that appending is free.
Per-cycle claim tracking.hasClaimed (bool) becomes per-cycle state; the round's claimer becomes circleMembers[_id][round % members.length]. roundDeposits is already keyed by round, so deposits should carry over as-is — worth confirming.
Timing.circleEnd = start + depositInterval * members.length * cycles; _currentRoundIndex, _roundEndTime and _isDecommissionable re-checked against a round index that now exceeds the roster length.
Gas.decommission()'s refund loop is already O(n²) over members; with cycles the refund surface grows. Check the ceiling at MAX_MEMBERS (25) × N cycles — this may cap how large N can be.
App scope
Creation form gains a rounds/cycles input (src/app/new/_components/form/schema.ts, form.tsx, overview.tsx)
Decouple everything assuming rounds == members — src/lib/get-user-circle-status.ts, src/hooks/use-circle-status.ts, src/lib/circle-state.ts
Deposit windows, decommission and refunds behave correctly in cycles 2..N, not just cycle 1
N = 1 behaves exactly as today (no regression for existing stacks)
Existing on-chain circles are unaffected by the upgrade
Open questions
Fixed N vs open-ended? A stack that "runs until the group stops" is the more natural product framing, but circleEnd and the decommission refund path both need a bound, and gas grows with N. Suggest scoping this issue to a fixed N chosen at creation, with a sensible upper limit.
Can N be changed after start? Suggest no for v1.
Existing circles — is anything migrated, or does multi-cycle only apply to circles created after the upgrade? Suggest the latter.
Summary
Let a stack be configured to run the rotation N times (each member claims N times) instead of exactly once. Requires contract changes in
saving-circlesplus a redeploy.Motivation
A stack currently runs the rotation exactly once, and that's baked into the contract rather than being a product choice:
hasClaimedis a single bool per member (MemberState), never reset — a member structurally cannot claim twicecircleMembers[_id][currentRound], andcurrentRound >= members.lengthmeans expiredcircleEnd = effectiveCircleStartTime + (depositInterval * members.length)depositAmount * members.lengthSo a group that wants to keep saving together has no path other than creating a whole new stack.
#170 gives that path without a contract change — replicate into a new circle id. This issue is the durable version: one stack, one continuous history, no per-cycle re-enrollment, configured up front. Worth doing when the contract is next redeployed, alongside the
circleState()/start()terminal-state fix described in #160.Required contract changes (
saving-circles)Circlegains arounds/cyclesfield.Circleis a mapping value in an upgradeable contract — this needs an explicit storage-layout / upgrade-safety review, not an assumption that appending is free.hasClaimed(bool) becomes per-cycle state; the round's claimer becomescircleMembers[_id][round % members.length].roundDepositsis already keyed by round, so deposits should carry over as-is — worth confirming.circleEnd = start + depositInterval * members.length * cycles;_currentRoundIndex,_roundEndTimeand_isDecommissionablere-checked against a round index that now exceeds the roster length.circleState()still has no completed value — fold the Completed stack shows "Not started" and Start button is clickable again #160 contract fix (terminal state +start()rejecting an already-run circle) into the same redeploy.decommission()'s refund loop is already O(n²) over members; with cycles the refund surface grows. Check the ceiling atMAX_MEMBERS(25) × N cycles — this may cap how large N can be.App scope
src/app/new/_components/form/schema.ts,form.tsx,overview.tsx)rounds == members—src/lib/get-user-circle-status.ts,src/hooks/use-circle-status.ts,src/lib/circle-state.tssrc/app/stacks/[id]/_components/claim-schedule/)saving-circles-cre) must understand multi-cycle rounds (Wire Chainlink automation for automatic deposits and claims in gnosis #118, Research about the automatic deposits logic regarding the contract and Chainlink #122)src/lib/abis) after redeployAcceptance criteria
Open questions
circleEndand the decommission refund path both need a bound, and gas grows with N. Suggest scoping this issue to a fixed N chosen at creation, with a sensible upper limit.Related / avoid duplication
circleState()/start()terminal-state fix into this redeploySource
Spun out of #160 — the restart path was found as a bug during that fix; the feature framing came out of that discussion (Fran, Jul 2026).