For the complete documentation index, see llms.txt. This page is also available as Markdown.

Crowdfund

On-chain crowdfunding with goal tracking via mustOwnTokens. Contributors deposit funds, receive refund tokens. Crowdfunder withdraws if goal met, contributors refund if not.

Category: Token Types

Summary

Required standards: ["Crowdfund"]

  • 2 token IDs: Token 1 = Refund token (contributor holds), Token 2 = Progress token (crowdfunder accumulates)

  • 4 collection-level approvals: deposit-refund, deposit-progress, success (withdraw), refund

  • Contributors deposit coins β†’ receive Token 1 (refund token). Paired approval mints Token 2 to crowdfunder (progress tracking).

  • Success: crowdfunder withdraws if mustOwnTokens confirms they hold >= goal of Token 2 (collectionId: 0 = self-reference)

  • Refund: after deadline, contributors burn Token 1 β†’ escrow pays them back (only if goal NOT met via mustOwnTokens check)

  • allowAmountScaling: true on ALL 4 approvals (contributors choose deposit size, everything scales proportionally)

  • maxScalingMultiplier: MAX_UINT for unrestricted scaling

  • Deposit coinTransfer.to = "Mint" (auto-resolves to escrow)

  • requireToEqualsInitiatedBy: true on deposit-refund (contributor receives their own refund token)

  • invariants: `noForcefulPostMintTransfers: true` β€” the refund approval (non-mint) MUST NOT set `overridesFromOutgoingApprovals` or `overridesToIncomingApprovals` (both must be false or omitted). It relies on `defaultBalances.autoApproveSelfInitiatedOutgoingTransfers: true` for the outgoing side and on the burn destination for the incoming side. The deposit-refund / deposit-progress / success approvals ARE Mint-side and keep `overridesFromOutgoingApprovals: true` as the chain requires, with `overridesToIncomingApprovals: false`

  • All permissions frozen after creation

  • DON'T use votingChallenges β€” goal tracking is via mustOwnTokens, not voting

  • DON'T forget allowAmountScaling on ALL 4 approvals

  • DON'T set overrideFromWithApproverAddress on deposit (contributor pays, not escrow)

  • DO set overrideFromWithApproverAddress: true on success and refund (escrow pays out)

  • DO set overrideToWithInitiator: true on refund (contributor receives their own refund)

  • DO use collectionId: "0" in mustOwnTokens for self-reference

Instructions

Crowdfund Configuration

Mental Model

On-chain crowdfunding with automatic goal tracking. Contributors deposit coins and receive refund tokens. A progress token tracks total raised. If the goal is met, the crowdfunder withdraws all funds. If not, contributors burn their refund tokens to get deposits back.

Collection Structure

  • Token ID 1 = Refund token (contributor holds β€” burn to refund)

  • Token ID 2 = Progress token (crowdfunder accumulates β€” tracks total raised)

  • Standard: "Crowdfund"

  • validTokenIds: [{ start: "1", end: "2" }]

  • invariants: { noCustomOwnershipTimes: true }

  • All permissions frozen after creation

4 Required Approvals

1. Deposit-Refund (contributor pays coins β†’ receives Token 1)

CRITICAL: requireToEqualsInitiatedBy: true ensures the contributor receives their own refund token. allowAmountScaling: true lets contributors choose their deposit size β€” the coin payment and token amount scale together.

2. Deposit-Progress (paired: mints Token 2 to crowdfunder, no coinTransfer)

toListId is the crowdfunder's specific address (not "All"). No coinTransfer β€” this is the paired counterpart to deposit-refund.

3. Success / Withdraw (crowdfunder withdraws if goal met)

mustOwnTokens with collectionId: "0" = self-reference. Checks that the crowdfunder owns >= goal amount of Token 2 (progress token). Only available after deadline.

4. Refund (contributor burns Token 1 β†’ gets deposit back, only if goal NOT met)

Refund uses overrideFromWithApproverAddress: true (escrow pays) + overrideToWithInitiator: true (contributor receives). mustOwnTokens checks crowdfunder has LESS than goal of Token 2 (amountRange.end = goal - 1). allowAmountScaling: true so refund scales with deposit size. maxNumTransfers = MAX_UINT (needs non-zero with overrideFromWithApproverAddress).

Creation Flow (Tool Calls)

  1. `set_valid_token_ids` β€” set [{ start: "1", end: "2" }]

  2. `set_standards` β€” set ["Crowdfund"]

  3. `set_invariants` β€” set { noCustomOwnershipTimes: true }

  4. `add_approval` x4 β€” deposit-refund, deposit-progress, success, refund

  5. `set_collection_metadata` β€” name, description, image

  6. `set_token_metadata` x2 β€” Token 1 (Refund), Token 2 (Progress)

  7. `set_permissions` β€” preset "fully-immutable"

  8. `validate_transaction` β€” verify structure

  9. `simulate_transaction` β€” dry run

Common Mistakes

  • DON'T forget allowAmountScaling: true on ALL 4 approvals β€” without it, all deposits are fixed at 1 base unit

  • DON'T use votingChallenges β€” goal tracking uses mustOwnTokens, not voting

  • DON'T forget maxScalingMultiplier: MAX_UINT β€” without it, scaling is capped at 0 (no scaling)

  • DON'T set overrideFromWithApproverAddress on deposit-refund or deposit-progress (contributor pays, not escrow)

  • DON'T forget requireToEqualsInitiatedBy: true on deposit-refund

  • DON'T forget the paired deposit-progress approval β€” it tracks total raised

  • DON'T set collectionId to the actual collection ID in mustOwnTokens β€” use "0" for self-reference

  • DON'T forget that success transferTimes must start AFTER the deadline (deadline + 1)

  • DON'T forget that refund mustOwnTokens amountRange.end = goal - 1 (strictly less than goal)

  • DON'T set maxNumTransfers = 0 on refund approval β€” overrideFromWithApproverAddress requires non-zero

Last updated