Skip to content

feat(mutual-aid): add creator withdrawal for funded requests (#407) - #421

Merged
3m1n3nc3 merged 1 commit into
geevapp:mainfrom
augusthottie:feat/mutual-aid-creator-claim
Jul 28, 2026
Merged

feat(mutual-aid): add creator withdrawal for funded requests (#407)#421
3m1n3nc3 merged 1 commit into
geevapp:mainfrom
augusthottie:feat/mutual-aid-creator-claim

Conversation

@augusthottie

Copy link
Copy Markdown
Contributor

Description

Closes #407

Fully funded help requests had no payout path. donate escrows donations and flips a
request to FullyFunded once the goal is met, but nothing ever moved that escrow to the
creator: the only outbound path was claim_refund, which needs a Cancelled request,
and cancel_request needs an Open one. A funded request was a dead end where the
collected support sat in the contract permanently.

This adds a pull-based creator withdrawal, mirroring the giveaway claim_prize
lifecycle.

Changes

  • claim_help_request_funds(creator, request_id) (mutual_aid.rs) — creator-authenticated
    withdrawal. Verifies request ownership and a release status, transfers the full
    raised_amount to the creator, marks the request Closed, and writes a one-shot claim
    record. Runs inside with_reentrancy_guard, like the giveaway payout path.
  • DataKey::HelpRequestClaimed(u64) (types.rs) — the one-shot payout record. It is
    request-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, and
    a shared key would let one record shadow the other.
  • HelpRequestFundsClaimed event — topics aid, claim, request id; data
    [creator, amount], following the existing DonationReceived shape so indexers can
    consume donations and payouts the same way.
  • donate now rejects Closed requests. Without this, a donation arriving after payout
    would be permanently trapped: it cannot be withdrawn again (the claim record is set) and
    cannot be refunded (refunds need Cancelled, cancelling needs Open).
  • Design spec at 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 Error variants were needed — the guards reuse NotCreator, InvalidStatus,
HelpRequestNotFound, and AlreadyClaimed.

Design decisions worth a reviewer's eye

  • Release states: withdrawal is allowed from FullyFunded and from ResolvedRelease.
    The issue asked for "FullyFunded or another documented release state", and
    ResolvedRelease is the dispute outcome already declared in HelpRequestStatus meaning
    escrow 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.
  • Double-withdrawal is blocked twice over: the Closed status, which no other entry
    point can undo (cancel_request and governance auto-suspension both require Open,
    resolve_appeal requires UnderAppeal), plus the claim record, which still blocks a
    payout even if a request were somehow returned to a release state.
  • No protocol fee. Giveaway prizes take a fee_bps cut 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_amount is left intact rather than zeroed, so the request keeps its funding
    history for indexers and the UI after payout.

Acceptance criteria

  • A fully funded request can be claimed by its creator
  • Funds cannot be withdrawn twice
  • Request state reflects successful payout and passes tests

Checklist

  • I have tested my changes locally
  • I have updated documentation as needed
  • I have run npx prisma generate after schema changes — N/A, contracts-only change
  • I have run npx prisma migrate dev or npx prisma migrate deploy — N/A, contracts-only change

Testing

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 release wasm32-unknown-unknown build.

Covered: successful creator claim (full escrow transferred, status Closed, claim record
set); 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 of
an under-funded, Cancelled, or non-existent request; claim from ResolvedRelease; donation
to a Closed request; 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_funds method, so any deployed geev-core instance needs a redeploy
or upgrade for the withdrawal flow to be callable.

…#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
augusthottie force-pushed the feat/mutual-aid-creator-claim branch from 65a95c0 to b43e1ee Compare July 28, 2026 17:49
@3m1n3nc3
3m1n3nc3 merged commit e9c0e53 into geevapp:main Jul 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MutualAid] Add creator withdrawal for fully funded help requests

2 participants