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: trueensures the contributor receives their own refund token.allowAmountScaling: truelets 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)
`set_valid_token_ids` β set [{ start: "1", end: "2" }]
`set_standards` β set ["Crowdfund"]
`set_invariants` β set { noCustomOwnershipTimes: true }
`add_approval` x4 β deposit-refund, deposit-progress, success, refund
`set_collection_metadata` β name, description, image
`set_token_metadata` x2 β Token 1 (Refund), Token 2 (Progress)
`set_permissions` β preset "fully-immutable"
`validate_transaction` β verify structure
`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