Introduction to Delegate Cash

Like the title says in this blog we are going to discover Delegate Cash, what it is and how to use it.

So what is Delegate Cash ?

As it say on the website it's "A decentralized registry to protect your assets." So basically it's a registry that allow you to link your burner wallets to your vault.

The purpose of this is to allow you to mint, claim and prove ownership without having to use your vault.

Here's an exemple :

Imagine you have a wallet with BAYC NFTs. You want to mint an NFT from a new collection that require you to own a BAYC to be able to mint. The first option is to use your vault to mint the NFT but this is risky because you are exposing your vault to the minting contract and you don't know if it's malicious contract or not.

The second option if the contract implement it, is to use Delegate Cash to register a burner wallet as your delegate. This way you can mint the NFT from your burner wallet without exposing your vault and still proving that you own a BAYC.

How to use it

First if you want to register a delegate wallet you can simply do it on the website. You just need to connect your vault and then add as many burner wallets you want to delegate to.

There is 3 types of delegates :

  • Wallet : Entrust a throwaway burner wallet (eg. your hot wallet) to prove ownership on your behalf for any contract.
  • Contract : Entrust an entire contract to a wallet of your choice (eg. an ERC20 token).
  • NFT : Entrust a single NFT to a wallet of your choice.

How to use the Delegate Cash contract

First there's one thing to know, you can delegate your vault to a burner wallet but if the contract you want to interact with doesn't integrate Delegate Cash you can't use it.

So the first thing you can do if you preview to use it is to allow your users to delegate their vault directly from your dapp. You can do this by calling one the 3 functions :

function delegateForAll(address delegate, bool value) external;
function delegateForContract(address delegate, address contract_, bool value) external;
function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external;

You can also display to the user the list of the delegates they have registered on the website by using this functions :

function getDelegatesForAll(address vault) external view returns (address[] memory);
function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory);
function getDelegatesForToken(address vault, address contract_, uint256 tokenId) external view returns (address[] memory);

How to use the Delegate Cash contract inside your contract

Now we are going to see how to use Delegate Cash inside your contract to allow your users to mint NFTs without exposing their vault. We will take a simple ERC721 smart contract as exemple.

For this exemple :

  • Vault address : 0x7E7C254fA2E31B60b1269956847B8A6AC2eCd730
  • Burner wallet address : 0x88A149D1B8AfCA593bbEd2a708D2AEa5a15FdE2c

Here's the code of our contract :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyToken is ERC721, Ownable {
    using Counters for Counters.Counter;
    mapping(address => bool) public whitelist;
    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("MyToken", "MTK") {
        whitelist[0x7E7C254fA2E31B60b1269956847B8A6AC2eCd730] = true;
        whitelist[0x5C5ef860FF70D86aA6c7300e4E21320791135215] = true;
        whitelist[0xE91FC033eDb7D8A87b480b1a7467CcAD0725BdD7] = true;
        whitelist[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] = true;
    }

    function safeMint() public {
        require(whitelist[msg.sender], "You are not whitelisted");
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(msg.sender, tokenId);
    }
}

This is a basic ERC721 contract with auto increment on tokenId. To mint the user need to be whitelisted. Do not use this code for whitelisting users in production, it's just for the exemple and to make it more simple to understand.

Let's add Delegate Cash to our contract.

For this post I used remix to compile and deploy the contract.I also import Delegate Cash contract directly from github repo. To be able to do this you need to update the compiler_config.json file with the following code :

{
  "language": "Solidity",

  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "": ["ast"],
        "*": [
          "abi",
          "metadata",
          "devdoc",
          "userdoc",
          "storageLayout",
          "evm.legacyAssembly",
          "evm.bytecode",
          "evm.deployedBytecode",
          "evm.methodIdentifiers",
          "evm.gasEstimates",
          "evm.assembly"
        ]
      }
    },
    "remappings": [
      "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol=@openzeppelin/contracts/utils/introspection/ERC165.sol",
      "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol=@openzeppelin/contracts/utils/structs/EnumerableSet.sol"
    ]
  }
}

Now we can start to update our contract.

First we need to import the Delegate Cash contract :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "https://github.com/delegatecash/delegation-registry/blob/main/src/IDelegationRegistry.sol";
import "https://github.com/delegatecash/delegation-registry/blob/main/src/DelegationRegistry.sol";

After we declare the DelegationRegistry from Delegate Cash :

contract MyToken is ERC721, Ownable {
    using Counters for Counters.Counter;
    mapping(address => bool) public whitelist;
    Counters.Counter private _tokenIdCounter;
    DelegationRegistry reg;


    constructor() ERC721("MyToken", "MTK") {

        whitelist[0x7E7C254fA2E31B60b1269956847B8A6AC2eCd730] = true;
        whitelist[0x5C5ef860FF70D86aA6c7300e4E21320791135215] = true;
        whitelist[0xE91FC033eDb7D8A87b480b1a7467CcAD0725BdD7] = true;
        whitelist[0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7] = true;

        reg = DelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B);
    }
    ...
}

0x00000000000076A84feF008CDAbe6409d2FE638B is the address of the contract it's the same on all supported network.

And finally we update the mint function :

function safeMint(address _vault) public {
    address requester = msg.sender;

    if (_vault != address(0)) {
        require(whitelist[_vault], "Your vault is not whitelisted");
        bool isDelegateValid = reg.checkDelegateForAll(requester, _vault);
        require(isDelegateValid, "invalid delegate-vault pairing");
        requester = _vault;
    }else{
        require(whitelist[requester], "You are not whitelisted");
    }


    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();
    _safeMint(requester, tokenId);
}

Here's how the mint function work now :

First the mint function need a vault address it can be 0x0000000000000000000000000000000000000000. If the value is 0x00... we just check that the requester wich is msg.sender is whitelisted if yes he can mint. That the same thing that before. So basically if the user decide to mint with his vault wich is whitelisted he can.

Now if the user decide to mint with his burner wallet he need to pass the vault address to the mint function. The function will check that the vault is whitelisted and after that, it will check if the requester is a valid delegate for the vault. If yes the user can mint and the nft will be transfered in the vault.

Here the full code of the contract :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "https://github.com/delegatecash/delegation-registry/blob/main/src/IDelegationRegistry.sol";
import "https://github.com/delegatecash/delegation-registry/blob/main/src/DelegationRegistry.sol";

contract MyToken is ERC721, Ownable {
    using Counters for Counters.Counter;
    mapping(address => bool) public whitelist;
    Counters.Counter private _tokenIdCounter;
    DelegationRegistry reg;


    constructor() ERC721("MyToken", "MTK") {

        whitelist[0x7E7C254fA2E31B60b1269956847B8A6AC2eCd730] = true;
        whitelist[0x5C5ef860FF70D86aA6c7300e4E21320791135215] = true;
        whitelist[0xE91FC033eDb7D8A87b480b1a7467CcAD0725BdD7] = true;
        whitelist[0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7] = true;

        reg = DelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B);
    }

    function safeMint(address _vault) public {
        address requester = msg.sender;

        if (_vault != address(0)) {
            require(whitelist[_vault], "Your vault is not whitelisted");
            bool isDelegateValid = reg.checkDelegateForAll(requester, _vault);
            require(isDelegateValid, "invalid delegate-vault pairing");
            requester = _vault;
        }else{
            require(whitelist[requester], "You are not whitelisted");
        }


        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(requester, tokenId);
    }
}

We can compile the contract and deploy it on the blockchain. Your contract is now ready to be used with Delegate Cash. This is a basic exemple but it let you understand how to use it.

Here a link to the website of Delegate Cash : https://delegate.cash/.