Skip to content

token-fundraiser: let a maker retire a failed raise and raise again#107

Open
mikemaccana wants to merge 2 commits into
mainfrom
claude/solana-fintech-book-toc-rz4lpd
Open

token-fundraiser: let a maker retire a failed raise and raise again#107
mikemaccana wants to merge 2 commits into
mainfrom
claude/solana-fintech-book-toc-rz4lpd

Conversation

@mikemaccana

Copy link
Copy Markdown
Collaborator

What

Adds a close_fundraiser instruction handler to the Anchor token-fundraiser, plus tests.

Why

The Fundraiser PDA is derived from b"fundraiser" + the maker's key alone. After a failed raise nothing ever closed that account: check_contributions (the only closing handler) is permanently barred by TargetNotMet once refunds drain current_amount, so a maker whose raise failed was locked out of ever raising again from that wallet — and the vault plus fundraiser rent was stranded with it.

How

close_fundraiser (maker-signed) requires, in order:

  1. elapsed_days >= duration — else FundraiserNotEnded
  2. current_amount < amount_to_raise — else TargetMet (a successful raise exits through check_contributions, which already closes these accounts)
  3. current_amount == 0 — else RefundsOutstanding (new error variant; closing the vault earlier would strand the remaining refunds)

Anything left in the vault at that point is a direct donation outside the program's accounting; it is swept to maker_ata rather than burned with the account. The vault closes via close_account, the fundraiser via close = maker, and the maker can then initialize a fresh fundraiser at the same PDA.

Tests

  • test_close_fundraiser_after_failed_raise_allows_a_new_raise — the full retire-then-raise-again lifecycle
  • test_close_fundraiser_before_deadline_fails
  • test_close_fundraiser_when_target_met_fails
  • test_close_fundraiser_with_unrefunded_contributions_fails
  • test_close_fundraiser_sweeps_direct_donations_to_maker
  • Plus the two previously missing contribution-cap tests: test_contribute_above_cap_fails and test_cumulative_contributions_above_cap_fail

cargo check --tests passes locally; the sandbox's network policy blocks the platform-tools download so the SBF build and LiteSVM run could not be executed locally — the Anchor CI workflow is the test gate for this PR.

README (new handler documented in lifecycle prose, testing section updated) and CHANGELOG updated. The Quasar port is not touched — it has materially different validation semantics (no caps, exact-second deadlines) and is tracked separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MjDPoqwPYDGnLoMoXsto8r


Generated by Claude Code

claude added 2 commits July 10, 2026 02:57
The Fundraiser PDA is derived from the maker's key alone, so a failed
fundraiser's surviving account locked its maker out of ever raising
again. Add a close_fundraiser handler: after the deadline, when the
target was missed and every contribution has been refunded, the maker
sweeps any direct vault donations to their own token account, closes
the vault and the fundraiser (recovering both rent deposits), and can
then initialize a fresh fundraiser. New error variant
RefundsOutstanding guards against stranding unrefunded contributions.

Also add the two missing contribution-cap tests (single contribution
over the 10% cap; cumulative contributions over it) and five tests
covering every branch of the close path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjDPoqwPYDGnLoMoXsto8r
The second initialize in the close-then-raise-again test was
byte-identical to the first (same accounts, data, and blockhash), so
LiteSVM rejected it as already processed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjDPoqwPYDGnLoMoXsto8r
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.

2 participants