Token-gated fundraising uses access control on a smart contract to restrict participation in a crowdfunding round to holders of a specific token. This mechanism is commonly used by DAOs for community rounds, by NFT projects for allowlisted mints, or by protocols to reward early supporters. The core logic involves checking an investor's token balance—often an ERC-20 governance token or an NFT—against a predefined minimum threshold before allowing them to contribute ETH or stablecoins to the sale. This creates a programmable, trustless alternative to manual KYC or allowlist management.
Setting Up Token-Gated Crowdfunding Rounds
Setting Up Token-Gated Crowdfunding Rounds
A technical guide to implementing smart contracts for token-gated fundraising, enabling projects to restrict investment access to verified token holders.
The implementation typically involves two main contracts: the token contract (e.g., an ERC-20) and the sale contract. The sale contract's contribute() function must include a check using the IERC20.balanceOf(msg.sender) or IERC721.balanceOf(msg.sender) function call. A basic Solidity modifier for this check might look like:
soliditymodifier onlyTokenHolders(address tokenAddress, uint256 minBalance) { require(IERC20(tokenAddress).balanceOf(msg.sender) >= minBalance, "Insufficient token balance"); _; }
This modifier would then be applied to the contribution function, gating access.
Key design considerations include snapshot timing and gas optimization. A naive balance check uses the user's live balance, which can be manipulated by transferring tokens in and out. For fairness, many projects use a merkle proof system based on a snapshot of token holdings taken at a specific block. Libraries like OpenZeppelin's MerkleProof verify proofs efficiently on-chain. Furthermore, you must decide on sale parameters: a fixed or dynamic price, individual contribution caps, a total fundraising hard cap, and a vesting schedule for any tokens distributed to investors.
Security is paramount. The sale contract must guard against common vulnerabilities like reentrancy (use the Checks-Effects-Interactions pattern), ensure proper access control for admin functions (using Ownable or similar), and securely handle the raised funds. All funds should be withdrawn to a multisig wallet or DAO treasury after the sale concludes. Thorough testing with frameworks like Foundry or Hardhat, including simulations of token-gating logic and edge cases, is essential before mainnet deployment.
Real-world examples include Juicebox's token-gated funding cycles, where a DAO's token holders vote to enable a new round, and Llama's LaunchFactory for DAO community sales. These platforms abstract the contract deployment but operate on the same principles. By implementing token-gated crowdfunding, projects can align incentives, reward their community, and conduct capital formation in a decentralized, compliant manner, moving beyond simple first-come-first-serve models.
Prerequisites and Setup
This guide details the technical and conceptual prerequisites for launching a token-gated fundraising round, covering wallet setup, smart contract understanding, and initial project configuration.
Before deploying a token-gated crowdfunding round, you need a foundational understanding of the core components. This includes a working knowledge of smart contracts, as the entire fundraising logic will be encoded on-chain. You should be familiar with concepts like access control, ERC-20 token standards, and state variables. For development, you'll need a code editor like VS Code, Node.js (v18+), and a package manager such as npm or yarn. The primary tool for this guide is the Foundry development framework, chosen for its speed and direct Solidity testing capabilities.
You must have a cryptocurrency wallet to interact with the blockchain. Install a browser extension like MetaMask and create a new wallet, securely storing your seed phrase. For testing, you will need testnet ETH. You can obtain Sepolia ETH from a faucet like Alchemy's Sepolia Faucet. Configure your wallet to connect to the Sepolia test network. This setup allows you to deploy contracts and simulate user interactions without spending real funds, which is critical for thorough testing.
The smart contract system for token-gated crowdfunding typically involves at least two contracts: a sale contract that manages the round's logic (timing, caps, contributions) and a token contract that defines the gating asset. You will need the address of an existing ERC-20 token to use as the gating key, or you must deploy your own. Using OpenZeppelin's contracts library is highly recommended for secure, audited implementations of standards like ERC20, Ownable, and ReentrancyGuard. Import these into your project with forge install OpenZeppelin/openzeppelin-contracts.
A critical prerequisite is defining your round's parameters with precision. This includes the sale duration (start and end timestamps), the funding goal (in wei or ETH), and the contribution limits (minimum and maximum per participant). You must also decide on the token-gating logic: will contributors need to hold a minimum balance (e.g., 100 tokens), or will a balance of any amount greater than zero suffice? These rules are enforced in the smart contract's buyTokens or contribute function using a require statement that checks the caller's balance.
Finally, set up your local development environment. Initialize a new Foundry project with forge init. Install necessary dependencies like OpenZeppelin. Write your initial test suite in Solidity using Forge's testing framework; a good first test is to assert that a user without the required token balance cannot contribute. Run tests with forge test. Having a failing test first validates your gating logic before you write the implementation. This test-driven approach ensures your contract behaves as intended from the start.
Core Smart Contract Concepts
Learn the foundational smart contract patterns required to build secure, permissioned fundraising rounds where access is controlled by token ownership.
Setting Up Token-Gated Crowdfunding Rounds
This guide explains how to design and deploy a secure smart contract for token-gated fundraising, where access to investment rounds is restricted to holders of a specific NFT or governance token.
Token-gated crowdfunding allows projects to create exclusive fundraising rounds for their community. The core architecture involves two primary contracts: a token contract (like an ERC-721 NFT or ERC-20 token) that acts as the access key, and a crowdfunding vault contract that holds the logic for the sale. The vault must verify a user's token balance before allowing them to contribute. This is typically done by checking the caller's balance on the token contract using the IERC721.balanceOf() or IERC20.balanceOf() interface within a function modifier like onlyTokenHolder.
The sale mechanics are defined in the vault. Key parameters you must set include the fundingToken (e.g., USDC, ETH), the salePrice per project token, the hardCap for total funds to raise, and the saleDuration. Crucially, the contract must track contributions per address using a mapping like mapping(address => uint256) public contributions. Funds should be held in escrow within the contract until the sale concludes, at which point they can be withdrawn by the project owner via a withdrawRaisedFunds() function, often guarded by a timelock or multisig for security.
A critical security pattern is using a pull-over-push mechanism for distributing purchased tokens. Instead of sending tokens to buyers during the sale (a push), which can fail and lock funds, record their entitlement. After the sale, implement a claim() function that lets users withdraw their purchased tokens. This prevents issues with non-receiving contracts and gas wars. Always include a safety mechanism like emergencyRefund() that allows users to reclaim their payment if the sale fails to meet its minimum (softCap) or is canceled, ensuring funds are never permanently stuck.
For production deployment, integrate with a decentralized price feed like Chainlink Oracles if your sale price is pegged to a volatile asset. Use OpenZeppelin's ReentrancyGuard and Ownable contracts to prevent reentrancy attacks and manage ownership. Thoroughly test the interaction between the token contract and the sale vault using frameworks like Foundry or Hardhat, simulating edge cases such as users selling their gating token mid-sale. Finally, verify and publish your contract source code on block explorers like Etherscan to build trust with potential contributors.
Implementing Token-Based Whitelisting
A technical guide to setting up token-gated crowdfunding rounds using smart contracts, allowing you to restrict participation to holders of a specific NFT or token.
Token-based whitelisting is a mechanism for creating permissioned access to smart contract functions, such as participating in a fundraising event. Instead of a manually managed list of addresses, access is granted programmatically based on whether a user's wallet holds a qualifying asset. This is commonly used for community-focused sales, DAO fundraising, or exclusive NFT mints where you want to reward or prioritize existing token holders. The core logic checks the caller's balance of a predefined token contract before allowing a transaction to proceed.
To implement this, you will need a whitelisting contract that interfaces with your token's contract. The standard approach uses the ERC-20 balanceOf or ERC-721 ownerOf function. For an ERC-20 gated sale, your contract's contribute() function would first query the gating token contract: uint256 userBalance = IERC20(gateToken).balanceOf(msg.sender);. It then requires that userBalance >= requiredAmount before executing the rest of the function. This check is gas-efficient and ensures real-time, on-chain verification of eligibility.
For a practical example, consider a TokenGatedCrowdsale contract. Its constructor would accept the address of the gate token and the minimum required balance. The critical modifier would be:
soliditymodifier onlyTokenHolder() { require(IERC20(gateToken).balanceOf(msg.sender) >= minBalance, "Insufficient gate token balance"); _; }
This modifier is then applied to your investment function. Key design considerations include deciding if the check is for a snapshot (a specific block) or live balance, and whether to support staking vaults where tokens are deposited into another contract.
Security is paramount. Always verify the token contract address to prevent malicious inputs. Use OpenZeppelin's SafeERC20 for safe transfers if your contract handles funds. Be aware of reentrancy risks in your main sale function and protect state changes with checks-effects-interactions. For NFTs, remember that balanceOf can be manipulated by flash loans if the NFT is tradable on a lending platform; a snapshot or a commit-reveal scheme may be more secure for high-value sales.
Testing your implementation is crucial. Use a framework like Hardhat or Foundry to deploy mock ERC-20/721 tokens and simulate users with different balances. Write tests for edge cases: users with zero balance, exactly the minimum balance, and balances held in smart contracts. Tools like Chainlink VRF can be integrated for randomized allocation if you need to whitelist a subset of eligible holders, adding a fair lottery layer to your gated round.
This pattern is widely used by protocols like Uniswap (for governance token airdrop claims) and various NFT projects. By moving whitelist management on-chain, you eliminate administrative overhead and central points of failure, creating a transparent and trust-minimized fundraising mechanism. The complete code for a basic implementation can be found in the OpenZeppelin Contracts Wizard.
Setting Tiered Investment Caps
Implement tiered investment caps to control participation based on token holdings, creating structured fundraising rounds for different investor classes.
Tiered investment caps are a core mechanism in token-gated crowdfunding, allowing project founders to define different maximum contribution limits for various investor tiers. This structure is enforced by smart contracts that check a participant's token balance—often a governance or access token—against predefined thresholds before allowing an investment. For example, a project might set a 10 ETH cap for holders of 100+ project tokens, a 5 ETH cap for holders of 50+ tokens, and restrict access entirely for non-holders. This approach rewards early supporters and community members with preferential access to funding rounds, aligning incentives and managing capital inflow.
To implement this, you first define the tiers and their corresponding caps in your fundraising smart contract. A typical setup involves creating a mapping or an array of structs that link a minimum token balance to a maximum investment amount in the sale's base currency (e.g., ETH, USDC). The contract's investment function must then perform two critical checks: it verifies the caller's balance of the designated gating token using the IERC20(balanceOf) function, and it ensures the sum of their new investment and any previous contributions does not exceed the cap for their qualified tier. Failed checks should revert the transaction.
Here is a simplified Solidity code snippet illustrating the logic for a two-tier system:
solidity// Assume: IERC20 gatingToken, uint256 public salePrice mapping(address => uint256) public investedAmount; struct Tier { uint256 minTokenBalance; uint256 investmentCap; } Tier[] public tiers; // Populated in constructor function invest(uint256 investmentAmount) external payable { require(msg.value == investmentAmount * salePrice, "Incorrect payment"); uint256 userTokenBalance = gatingToken.balanceOf(msg.sender); uint256 applicableCap = 0; // Find the highest tier the user qualifies for for (uint i = 0; i < tiers.length; i++) { if (userTokenBalance >= tiers[i].minTokenBalance) { applicableCap = tiers[i].investmentCap; } } require(applicableCap > 0, "Insufficient token balance for any tier"); uint256 totalInvested = investedAmount[msg.sender] + investmentAmount; require(totalInvested <= applicableCap, "Investment exceeds tier cap"); investedAmount[msg.sender] = totalInvested; // ... process investment }
When configuring tiers, consider both economic and security factors. Caps should reflect realistic fundraising goals and prevent any single wallet from dominating the round. It's also crucial to use a time-weighted snapshot or a block number lock for token balances to prevent gaming, where users borrow or transfer tokens momentarily to qualify. Tools like OpenZeppelin's ERC20Snapshot or simply recording balances at a specific block can mitigate this. Always test tier logic extensively on a testnet, simulating edge cases where a user's balance falls between tier thresholds or changes mid-transaction.
Integrating tiered caps with a frontend improves user experience. Your dApp should query the smart contract to determine the user's eligible tier and remaining cap before they attempt a transaction, displaying clear messages like "Tier 1: Up to 10 ETH remaining." This prevents failed transactions and gas waste. Furthermore, consider making tier parameters—like minimum balances and caps—upgradable via a multisig or governance vote to allow for adjustments between funding rounds based on community feedback and tokenomics evolution.
In practice, platforms like Juicebox and Llama offer templatized contracts for token-gated funding rounds. However, a custom implementation provides maximum flexibility for unique vesting schedules, hybrid public/private rounds, or multi-token gating logic. The key audit points for such a system are ensuring the gating token address is immutable (or securely changeable), cap calculations are safe from overflow, and the contract correctly handles the native currency or ERC-20 payments specified for the investment.
Token Verification Method Comparison
Comparison of methods to verify token holdings for gating access to a crowdfunding round.
| Verification Feature | On-Chain Snapshot | Real-Time Check | Signed Message |
|---|---|---|---|
User Experience | Single verification at start | Check on every interaction | Sign once, verify many times |
Gas Cost for User | None (read-only) | ~$0.50-2.00 per check | ~$0.10 for initial signature |
Gas Cost for Platform | ~$50-200 (one-time snapshot) | ~$0.50-2.00 per user check | None (off-chain verification) |
Data Freshness | Frozen at snapshot block | Real-time, up-to-date | Real-time at signature moment |
Sybil Resistance | High (proves historical holding) | High (proves current holding) | Medium (proves possession at sign time) |
Implementation Complexity | Medium (requires indexer/archive node) | Low (simple contract call) | High (requires signature validation logic) |
Supports Dynamic Gating | |||
Recommended Use Case | Static round with fixed participant list | Dynamic round with rolling entry | Whitelist for known community members |
Setting Up Token-Gated Crowdfunding Rounds
A technical guide to implementing token-gated access for fundraising rounds on your launchpad, using smart contracts and frontend verification.
Token-gated crowdfunding restricts participation in a fundraising round to holders of a specific NFT or ERC-20 token. This mechanism is used for community-focused raises, whitelists, or rewarding early supporters. From a technical perspective, the access control logic is enforced on-chain within the crowdfunding smart contract, typically using a require statement that checks the caller's balance of the gating token before allowing a contribution. The frontend's role is to seamlessly integrate with this contract, verify a user's eligibility, and provide a clear UX for both eligible and ineligible participants.
The core integration involves two main components: the smart contract and the frontend verification. Your crowdfunding contract, which could be based on a template from OpenZeppelin or a launchpad platform like Juicebox, must include a function to check the holding status. A common pattern is a modifier like onlyTokenHolders that queries the balance from the gating token's contract. For an ERC-20, this uses IERC20(gatingToken).balanceOf(msg.sender) > 0. For an ERC-721 NFT, you might check IERC721(gatingToken).ownerOf(tokenId) == msg.sender or use a balance check for a collection.
On the frontend, you must perform a pre-transaction check to prevent users from submitting failing transactions. Using a library like ethers.js or viem, your dApp should call the contract's view function (e.g., checkEligibility(address user)) or directly query the token contract when the user connects their wallet. Display a clear message if the check fails. It's also a best practice to implement client-side signing for gasless verification, where users sign a message proving token ownership, which is then validated by a backend server before revealing the contribution interface, enhancing UX and security.
Consider the user flow and error states. An eligible user should see the contribution form with available tier details. An ineligible user should be directed to a page explaining how to acquire the gating token, with links to a marketplace or staking portal. For transparency, the frontend should also display the gating token's address and the required minimum balance. Always use the readContract method for initial checks and ensure your writeContract calls to the crowdfunding contract will succeed by incorporating the eligibility check within the transaction's logic, catching any revert reasons gracefully.
Security and Testing Considerations
Essential security practices and testing strategies for developers implementing token-gated funding rounds. This guide addresses common pitfalls and ensures your smart contracts are robust against exploits.
This is often due to incorrect interface usage or assumptions about token standards. A common error is assuming balanceOf works identically across all ERC-20 implementations, which is not true for tokens with rebasing or fee-on-transfer mechanics.
Key issues to check:
- Interface Compliance: Use
IERC20from OpenZeppelin for standard functions. - Decimals: Always fetch
decimals()dynamically; don't hardcode 18. - Cross-Chain Verification: For bridged assets (e.g., wETH, USDC.e), verify the canonical bridge contract address, not the native asset's address.
- Testing: Use forked mainnet tests with real token contracts (like Aave's aTokens) to simulate edge cases.
Example check for a gating condition:
solidityIERC20 token = IERC20(tokenAddress); uint256 balance = token.balanceOf(msg.sender); uint256 required = 1000 * 10 ** token.decimals(); // Calculate with correct decimals require(balance >= required, "Insufficient balance");
Development Resources and Tools
Tools and protocols used to implement token-gated crowdfunding rounds, where participation depends on wallet holdings, onchain credentials, or governance rights. Each resource focuses on a specific layer: access control, funding rails, voting, or integration.
Frequently Asked Questions
Common technical questions and solutions for developers implementing token-gated fundraising rounds on EVM-compatible blockchains.
Token-gated crowdfunding restricts participation in a fundraising round to holders of a specific ERC-20 or ERC-721 token. It uses a smart contract as the sale vault, which validates a participant's token balance before accepting their contribution.
Core Mechanism:
- Setup: The project deploys a sale contract (e.g., using OpenZeppelin's
OwnableandReentrancyGuard) and defines the gating token address and minimum required balance. - Validation: When a user calls the
contribute()function, the contract performs abalanceOfcheck on the gating token contract via theIERC20interface. - Execution: If the check passes (
userBalance >= requiredBalance), the contribution in the native token (e.g., ETH) or a specified ERC-20 is accepted and recorded.
This mechanism enables community-focused raises, pre-sales for NFT holders, or tiered access based on governance token ownership.
Conclusion and Next Steps
You have now built a foundational token-gated crowdfunding contract. This guide covered the core mechanics of access control, contribution tracking, and milestone-based fund release.
Your implementation uses a require statement with balanceOf to gate participation, a mapping to track contributions, and a onlyOwner modifier for releasing funds to project milestones. This creates a transparent, on-chain record of backers and fund allocation. For production, you must rigorously test the contract's logic for edge cases like early contributor refunds or failed funding goals. Use frameworks like Foundry or Hardhat to write comprehensive unit and integration tests.
To enhance your dApp, consider integrating additional features. Implementing a timelock on the releaseFunds function can increase trust by giving contributors a window to review payments. Adding a refund function that allows backers to withdraw their funds if a funding goal isn't met by a deadline is a critical safety mechanism. You could also explore using Chainlink Oracles to trigger fund releases based on verifiable off-chain milestones, moving beyond simple owner control.
The next step is to build a frontend interface. Use a framework like Next.js with wagmi and Viem to connect the user's wallet, check their token balance, and interact with your contract's contribute and checkContribution functions. Display real-time data like the total funds raised and the contributor list. For deployment, verify your contract source code on block explorers like Etherscan and consider an audit for significant fundraisers. The complete code and further resources are available in the Chainscore Labs GitHub repository.