feat(mutual-aid): add creator withdrawal for funded requests (#407) - #421
Merged
3m1n3nc3 merged 1 commit intoJul 28, 2026
Merged
Conversation
…#407) Fully funded help requests had no payout path, leaving donations stuck in escrow. Creators can now withdraw the raised amount once a request reaches a release state, guarded against repeat withdrawals by both the Closed status and a one-shot claim record. Closes geevapp#407
augusthottie
force-pushed
the
feat/mutual-aid-creator-claim
branch
from
July 28, 2026 17:49
65a95c0 to
b43e1ee
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #407
Fully funded help requests had no payout path.
donateescrows donations and flips arequest to
FullyFundedonce the goal is met, but nothing ever moved that escrow to thecreator: the only outbound path was
claim_refund, which needs aCancelledrequest,and
cancel_requestneeds anOpenone. A funded request was a dead end where thecollected support sat in the contract permanently.
This adds a pull-based creator withdrawal, mirroring the giveaway
claim_prizelifecycle.
Changes
claim_help_request_funds(creator, request_id)(mutual_aid.rs) — creator-authenticatedwithdrawal. Verifies request ownership and a release status, transfers the full
raised_amountto the creator, marks the requestClosed, and writes a one-shot claimrecord. Runs inside
with_reentrancy_guard, like the giveaway payout path.DataKey::HelpRequestClaimed(u64)(types.rs) — the one-shot payout record. It isrequest-scoped rather than reusing the giveaway lifecycle's
Claimed(u64, Address),because giveaway ids and help request ids come from separate counters: the same
(id, address)pair can refer to both a giveaway winner and a help request creator, anda shared key would let one record shadow the other.
HelpRequestFundsClaimedevent — topicsaid,claim, request id; data[creator, amount], following the existingDonationReceivedshape so indexers canconsume donations and payouts the same way.
donatenow rejectsClosedrequests. Without this, a donation arriving after payoutwould be permanently trapped: it cannot be withdrawn again (the claim record is set) and
cannot be refunded (refunds need
Cancelled, cancelling needsOpen).contracts/geev-core/docs/superpowers/specs/2026-07-28-mutual-aid-creator-claim-design.md,following the convention from [Giveaway] Add unclaimed prize timeout and creator recovery #406.
No new
Errorvariants were needed — the guards reuseNotCreator,InvalidStatus,HelpRequestNotFound, andAlreadyClaimed.Design decisions worth a reviewer's eye
FullyFundedand fromResolvedRelease.The issue asked for "
FullyFundedor another documented release state", andResolvedReleaseis the dispute outcome already declared inHelpRequestStatusmeaningescrow was released to the creator. Accepting it now means the future dispute flow won't
need to change this function. Every other status is rejected with
InvalidStatus.Closedstatus, which no other entrypoint can undo (
cancel_requestand governance auto-suspension both requireOpen,resolve_appealrequiresUnderAppeal), plus the claim record, which still blocks apayout even if a request were somehow returned to a release state.
fee_bpscut on claim; mutual aid does not,so the creator receives the full amount donors saw raised. Happy to add fee parity if
maintainers prefer it.
raised_amountis left intact rather than zeroed, so the request keeps its fundinghistory for indexers and the UI after payout.
Acceptance criteria
Checklist
I have run— N/A, contracts-only changenpx prisma generateafter schema changesI have run— N/A, contracts-only changenpx prisma migrate devornpx prisma migrate deployTesting
11 new tests in
contracts/geev-core/src/test.rs; the full suite is 82 tests, all passing.Everything CI runs passes locally:
cargo fmt --all -- --check,cargo clippy --workspace --all-targets --all-features --locked -- -D warnings,cargo test --workspace --locked, and the releasewasm32-unknown-unknownbuild.Covered: successful creator claim (full escrow transferred, status
Closed, claim recordset); the emitted event's creator and amount; second claim rejected; claim record blocking a
payout even when the status is forced back to
FullyFunded; claim by a non-creator; claim ofan under-funded,
Cancelled, or non-existent request; claim fromResolvedRelease; donationto a
Closedrequest; and no refund path after payout.Post-Merge Steps for Maintainers
No Prisma schema changes, so no migration steps are required. The contract ABI does gain a
new
claim_help_request_fundsmethod, so any deployedgeev-coreinstance needs a redeployor upgrade for the withdrawal flow to be callable.