Only-NFTgate Restricted Smart Contract Functions

There are some circumstances where you will want a smart contract function to be only callable by NFTgate. This is specifically useful when considering off-chain allowlists, or to centralize an NFT drop for more organized queueing of users and prevent gas wars.

We created NFTgateKeyManager to help simplify this process. Here’s a link to the Github Repo for more details.

How it works

Under the hood, NFTgateKeyManager uses the signature pattern similar to EIP-1271 to help ensure that your data has not been spoofed.

Features

  • Makes implementing signature minting for your function trivial.
  • NFTgate automatically rotates and updates the key for you from time to time to following good security hygiene. Set once and forget!

Let’s see how we can use it!

The NFTgateKeyManager Interface

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

/// @title NFTgate Key Manager
/// @author Winston Yeo
/// @notice NFTgateKeyManager makes it easy for developers to restrict certain functions to NFTgate.
/// @dev Developers are in charge of registering the contract with the initial NFTgate key.
///      NFTgate will then help you  automatically rotate and update your key in line with good security hygiene
interface NFTgateKeyManager {
    /// @notice Registers a NFTgate Key to a contract
    /// @dev Registers the @param _paperKey with the caller of the function (your contract)
    /// @param _NFTgateKey The NFTgate key that is associated with the checkout. 
    /// You should be able to find this in the response of the checkout API or on the checkout dashbaord.
    /// @return bool indicating if the @param _NFTgateKey was successfully registered with the calling address
    function register(address _NFTgateKey) external returns (bool);

    /// @notice Verifies if the given @param _data is from NFTgate and have not been used before
    /// @dev Called as the first line in your function or extracted in a modifier. Refer to the Documentation for more usage details.
    /// @param _hash The bytes32 encoding of the data passed into your function.
    /// This is done by calling keccak256(abi.encode(...your params in order))
    /// @param _nonce a random set of bytes NFTgate passes your function which you forward. This helps ensure that the @param _hash has not been used before.
    /// @param _signature used to verify that NFTgate was the one who sent the @param _hash
    /// @return bool indicating if the @param _hash was successfully verified
    function verify(
        bytes32 _hash,
        bytes32 _nonce,
        bytes calldata _signature
    ) external returns (bool);
}

Usage in Your Contract

From above we can see that NFTgateKeyManager is used in two stages, the registration and verification.

Let’s start with the register function:

import "@nftgate/contracts/keyManager/NFTgateKeyManager.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

const YourContract {
    NFTgateKeyManager NFTgateKeyManager;

    // to set the initial NFTgateKey for the contract
    constructor(..., address _NFTgateKeyManagerAddress) {
        NFTgateKeyManager = NFTgateKeyManager(_NFTgateKeyManagerAddress);
    }

    // onlyNFTgate modifier to easily restrict multiple different function
    modifier onlyNFTgate(bytes32 _hash, bytes32 _nonce, bytes calldata _signature) {
        bool success = NFTgateKeyManager.verify(_hash, _nonce, _signature);
        require(success, "Failed to verify signature");
        _;
    }

    function registerNFTgateKey(address _NFTgateKey) external onlyOwner {
        require(paperKeyManager.register(_NFTgateKey), "Error registering key");
    }
  
    // using the modifier
    function yourFunction(... your params, bytes32 _nonce, bytes calldata _signature)
        onlyNFTgate(keccak256(abi.encode(...your params)), _nonce, _signature)
        ...
    {
        // your function
    }
}
  1. You’d want to first store the NFTgateKeyManager contract address as a state variable in your contract. (contract addresses here
  2. Deploy your contract to the chain of interest
  3. Register your contact on the dashboard as a CUSTOM_CONTRACT or through the API
  4. You should now be able to grab the NFTgateKeyManager Token from the dashboard.
  5. Call your register function ( NFTgateKeyManager.register(_NFTgateKey); where _NFTgateKey is the NFTgateKeyManager Token you got from step 4. Note that you can only call register once.
  6. For functions that you want to restrict to only NFTgate, simply make sure that it:
    • Accepts all your parameters and bytes32 _nonce and bytes calldata _signature at the end.
    • Calls NFTgateKeyManager.verify(keccak256(abi.encode(...you params in the order that you received it)), _nonce, _signature);. You can extract this out as a modifier if you plan to use it multiple times.

That’s it! Below is a sample of what this could look like.

  1. Using With Checkout Intents

As mentioned, using NFTgateKeyManager is only supported by CUSTOM_CONTRACT.

To use it, we introduce two more magic variables $NONCE and $SIGNATURE which you can pass in as params for your mintMethod

Below is an example of creating an SDK intent. Creating a link Intent is the same with additional link-related parameters

/**
 * We have two magic variables:
 * * `$WALLET` corresponds to the user's wallet
 * * `$QUANTITY`corresponds to the quantity the user wants to purchase.
 * * `$NONCE` is a unique string to prevent replay attack from attackers trying
 *    to use the same signature more than once  
 * * `$SIGNATURE`corresponds to the signature that the user wants to purchase.
 * It will resolve from {@param recipientWalletAddress} or {@param quantity} as expected.
 * Note that you can pass in the actual values themselves if you so choose.
 *
 */

let headersList = {
 "Authorization": "Bearer YOUR_API_KEY",
 "Content-Type": "application/json"
}

let bodyContent = JSON.stringify({
  "contractId": "YOUR_CONTRACT_ID",
  "mintMethod": {
    "args": {
      // this is just an exmaple call. corresponds to the following stub in solidity
      // function NFTgateMint(uint256 _tokenId, uint256 _quantity, address _to, bytes32 _nonce, bytes _signature)
      "_tokenId": 1,
      "_quantity": "$QUANTITY",
      "_to": "$WALLET"
      "_nonce": "$NONCE",
      "_signature": "$SIGNATURE"
    },
    "name": "NFTgateMint",
    "callOptions": {
      "gasPriority": "medium"
    },
    "payment": {
      "currency": "DERC20",
      "value": "0.001 * $QUANTITY"
    }
  },
  "walletAddress": "RECIPIENT_EMAIL_ADDRESS",
  "email": "RECIPIENT_EMAIL",
  "useNFTgateKey": true,
});

let response = await fetch("https://nftgate.net/api/2022-08-12/checkout-sdk-intent", { 
  method: "POST",
  body: bodyContent,
  headers: headersList
});

const { sdkClientSecret } = await response.json();

With the client secret, you can now use it with the Checkout Elements!

NFTgateKeyManager contract addresses

Reach out to us if the NFTgateKeyManager is not on the chain that you’re on!

Chain Contract Address
Mumbai 0x0c93b186B730985628C04DaDA34B681f38143615
Goerli 0x7924D04fd976B2A3dE32D9CEDCBC90Be4400353e
Polygon 0x43Ca8B5b235A0f607259C8fEACd15f9f06f91878
Mainnet 0x678a3F64A1bF33Ba0746fFD88Ba749B40B565Da5