Specific examples and general principles for integrating DelegateRegistry into your Solidity smart contract
Sample Token-Gated Mint w/ Delegate
Here the constant ORIGINAL_CONTRACT would be the one with the original tokens whose holders are being allowlisted. The constantDELEGATE_REGISTRY address would be 0x00000000000000447e69651d841bD8D104Bed493.
/** * @notice For example, bored ape holders minting their mutant apes * @param originalTokenIds The ids of tokens being used to mint something new */functiontokengatedMint(uint256[] calldata originalTokenIds) external {for (uint256 i =0; i < originalTokenIds.length; ++i) {uint256 tokenId = originalTokenIds[i];address tokenOwner = ORIGINAL_CONTRACT.ownerOf(tokenId);// Mint if tokenOwner is msg.sender or tokenOwner delegated to msg.senderif (msg.sender == tokenOwner ||IDelegateRegistry(DELEGATE_REGISTRY).checkDelegateForERC721( msg.sender, tokenOwner,address(ORIGINAL_CONTRACT), tokenId,"" ) ) {// Can mint to either the vaulted wallet or msg.sender, project's choice// Can also use an `address recipient` function parameter for flexibility_mint(tokenOwner, tokenId); } }}
Sample Merkle Tree Claim w/ Delegate
This is based off what Nakamigos CLOAKS used, where if users held a Nakamigo they were eligible for a free mint of a cloak. To save gas they used a merkle tree claim, this is compatible with Delegate.
/** * @notice Claim tokens from your allowlist quota * @param tokenOwner If using delegate.xyz, the address that features in the * allowlist. Set this to 0x000..000 if not using delegation * @param numberOfTokens The number of tokens to claim * @param tokenQuota The total quota of tokens for the claiming address * @param proof The merkle proof for this claimer */functionmintAllowList(address tokenOwner,uint256 numberOfTokens,uint256 tokenQuota,bytes32[] calldata proof ) externalpayable {// Set address of the wallet that appears on the allowlistaddress claimer = msg.sender;// --- DELEGATE INTEGRATION HERE ---// Check for delegationif (tokenOwner !=address(0) && tokenOwner != msg.sender) {if (IDelegateRegistry(_DELEGATE_REGISTRY).checkDelegateForAll(msg.sender, tokenOwner,"")) { claimer = vault; } }// --- END DELEGATE INTEGRATION ---// Check if the claimer has tokens remaining in their quotaif (getAllowListMinted(claimer) + numberOfTokens > tokenQuota) {revertExceedsAllowListQuota(); }// Check if the claimer is on the allow listif (!onAllowList(claimer, tokenQuota,69000000000000000000, proof)) {revertNotOnAllowList(); }if (msg.value != numberOfTokens * pricePerToken) {revertWrongETHValueSent(); }// Claim tokens_setAllowListMinted(claimer, numberOfTokens);_safeMint(msg.sender, numberOfTokens,""); }
General Principles
Whether you're building a mint or claim function, solidity additions to your contract are an important part of securing who is and isn't allowed to mint on behalf of a wallet.
Typically, a mint or claim function looks like this:
functionclaim() publicreturns (uint256 tokenId) {// 1. Check if `msg.sender` is allowed to claim// Maybe using merkle tree, etc// 2. Check if `msg.sender` has already claimed// 3. Your claim code below using `msg.sender`_claim(msg.sender)}
To integrate delegate.cash into your solidity contract, we will add a small code block to the above step. We will also pass an optional _vault address into the claim function.
addressconstantpublic=0x0000000000000000000000000000000000000001;functionclaim(addresscold) publicreturns (uint256 tokenId) {address requester = msg.sender;// Check if msg.sender is a permitted delegate of the cold storage address// If so, then we'll move ahead and mint on behalf of the cold wallet// Rather than msg.senderif (_vault !=address(0)) { bool isDelegateValid = REGISTRY.checkDelegateForContract(msg.sender, _cold, NFT_CONTRACT,"");require(isDelegateValid,"delegation does not exist"); requester = _cold; }// 1. Check if `requester` is allowed to claim// Maybe using merkle tree, etc// 2. Check if `requester` has already claimed// 3. Your claim code below using `requester`_claim(`requester`)}
Let's break down what we added.
(line 1) Passing an optional _cold address variable into the contraction.
When delegating, a hot wallet (msg.sender) is the one making the request on behalf of a cold wallet (_cold). When there is a _cold variable passed through the contract function, we know the hot wallet is minting on someone else's behalf.
(line 2) Using a new requester variable
In a typical mint contract, msg.sender is used throughout the contract as the user who holds the NFT and processes the transaction. Now, since msg.sender may be minting on behalf of someone else, we use requester to know if that is the case or not. requester will either be msg.sender or the cold wallet.
(line 4-8) Checking the DelegateRegistry for the valid pairings
If the transaction includes a cold wallet, this line of code will make sure that there is a vault<>delegate pairing with the cold and hot wallet; which is the act of someone going to delegate.cash and delegating their hot wallet with their cold wallet.