Gated purchases
Restrict Community Pool purchases with a policy contract, including an NFT ownership gate and the exact Solidity interface.
Both Standard and Buyback Community Pools can restrict new purchases through an owner-selected policy contract. The pool calls that contract before accepting a purchase. For an NFT gate, the policy checks whether the purchaser holds the required NFT at that moment.
Which wallet must qualify?
The gate checks the named purchaser, who receives the NFT and owns any later settlement or refund rights. The payer can be a different wallet or router. An eligible payer cannot buy for an ineligible purchaser; an ineligible payer can fund a purchase for an eligible purchaser. The Community Pools purchase screen names your connected wallet as purchaser.
The check runs once for a single purchase or once for the entire batch. Eligibility is not checked again during randomness fulfillment, settlement, NFT delivery, or refunds. Transferring the qualifying NFT after acceptance does not cancel that purchase, and changing the policy affects future purchases only.
The policy interface
Implement IPurchaseOperator from the contracts repository. The complete interface is:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IPurchaseOperator {
function canPurchase(address wallet)
external view returns (bool allowed);
}Here, wallet is the purchaser supplied to the pool's acquisition call. Inside your policy, msg.sender is the pool contract. Check the supplied wallet's holdings, not those of msg.sender or tx.origin. A shared policy may use msg.sender to select rules for different pools.
- The pool uses a read-only
staticcallwith a 100,000-gas budget, including any NFT balance read. The hook cannot write state, transfer tokens, or increment a purchase counter. - Return a normal ABI-encoded
bool. Onlytrueallows the purchase;falsereverts the acquisition withPurchaseNotAllowed(). - A reverting check, exhausted gas budget, or invalid return data causes
PurchaseOperatorCheckFailed(). The pool requires exactly 32 bytes containing a canonical boolean. No ERC-165 registration or callback success selector is required.
Example: must own this NFT
Require at least one copy of Resonance Labs token #1 on Ethereum mainnet. The collection at 0x20eba1Ac07c022C14D7FE2BE8Ba7C298f83E62eB is an ERC-1155 contract, so ownership means balanceOf(wallet, 1) > 0. Multiple wallets can qualify by holding copies of that token ID.
This standalone example includes both minimal interfaces and needs no library imports. Deploy it on Ethereum mainnet before configuring the pool.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IPurchaseOperator {
function canPurchase(address wallet)
external view returns (bool allowed);
}
interface IERC1155Balance {
function balanceOf(address account, uint256 id)
external view returns (uint256);
}
contract ResonanceLabsPurchaseGate is IPurchaseOperator {
address public constant COLLECTION =
0x20eba1Ac07c022C14D7FE2BE8Ba7C298f83E62eB;
uint256 public constant TOKEN_ID = 1;
function canPurchase(address wallet)
external view override returns (bool allowed)
{
if (wallet == address(0)) return false;
return IERC1155Balance(COLLECTION)
.balanceOf(wallet, TOKEN_ID) > 0;
}
}A holder qualifies; a wallet with zero copies does not. The NFT stays in the purchaser's wallet, with no approval, transfer, or burn. If the balance read reverts, the policy also reverts and the pool reports a failed policy check. This example does not recognize delegated or escrowed ownership, limit repeat purchases, or require one NFT per draw in a batch.
For a different, ERC-721 collection, use ownerOf(tokenId) == wallet for one specific NFT, or balanceOf(wallet) > 0 for any NFT in that collection. Those are different rules from this ERC-1155 example. The membership NFT is separate from pool inventory, which supports ERC-721 listings.
Attach the policy to your pool
- Deploy
ResonanceLabsPurchaseGateon Ethereum mainnet and save its contract address. The example has no constructor arguments. - In Create a Pool, choose Gated access in the Access step and enter the deployed gate address in Access policy contract. Enter the policy's address, not the NFT collection address or a personal wallet address. Setup calls
setPurchaseOperator(gateAddress)on your new pool. - For an existing pool, its owner can call
setPurchaseOperatordirectly on the pool contract to install or replace the policy. ReadpurchaseOperator()to confirm the configured address. - Open purchases after supplying inventory and waiting for the cooldown. Factory purchase controls and VRF availability still apply. Installing a gate does not enable purchases or override a platform pause.
Only the permanent pool owner can change the policy, and a retired pool rejects changes. Passing address(0) clears the wallet gate. A nonzero policy address must contain deployed code, but the setter does not validate the interface or test eligibility.
Check the integration
Test a holder and a non-holder, including purchases funded by a different payer and a multi-draw batch. Simulate the actual acquire or acquireBatch call with its intended purchaser, payment, and current pool parameters. A price quote alone does not check eligibility. If you call canPurchase directly for a policy that depends on the calling pool, set the call's sender to that pool address. A successful simulation is a preview; the transaction checks current holdings again when it executes.
The call behavior above matches the purchaser-aware Standard and Buyback implementations used by the current Community Pools integration. Older pools retain their deployed implementation. See Create & manage a pool for the rest of setup.