Smart Contract Examples

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
 */
function tokengatedMint(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.sender
        if (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
     */
    function mintAllowList(
        address tokenOwner,
        uint256 numberOfTokens,
        uint256 tokenQuota,
        bytes32[] calldata proof
    ) external payable {
        // Set address of the wallet that appears on the allowlist
        address claimer = msg.sender;
        
        // --- DELEGATE INTEGRATION HERE ---

        // Check for delegation
        if (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 quota
        if (getAllowListMinted(claimer) + numberOfTokens > tokenQuota) {
            revert ExceedsAllowListQuota();
        }

        // Check if the claimer is on the allow list
        if (!onAllowList(claimer, tokenQuota, 69000000000000000000, proof)) {
            revert NotOnAllowList();
        }

        if (msg.value != numberOfTokens * pricePerToken) {
            revert WrongETHValueSent();
        }
        
        // 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:

function claim() public returns (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.

address constant public  = 0x0000000000000000000000000000000000000001;


function claim(address cold) public returns (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.sender
  if (_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.

Last updated