EssentialERC2771Context 🤝 Your Contract

How to use 0xEssential's context primitive to build your L2 Contract

The purpose of cross-chain token gating is to enable developers to deploy smart contracts on EVM based L2s or sidechains that allow users to take actions with NFTs they own on Ethereum mainnet (or other EVM chains).

This token-gating approach prefers a trust-minimized live lookup of NFT ownership from another chain versus requiring users to bridge their NFT to the chain where your game or other contract is deployed. Cross-chain token gating cannot be used to change token ownership; token ownership data from other chains is read only, so this solution would not support building products like marketplaces.

In order to perform token gating on your Layer 2 or sidechain Implementation Contract, you must restrict those functions to only be callable by a forwarding contract that is performing the token ownership lookup - we provide a modifier for this purpose.

Install & Extend

EssentialERC2771Context is available in the 0xessential/contracts NPM package. Install it in your smart contract project with npm or yarn:

yarn add @0xessential/contracts

// or

npm i @0xessential/contracts

The package is written in Solidity. You can review the source code on Github.

Once installed, import into and extend your Implementation Contract with EssentialERC2771Context. You should also import the IEssentialForwarder interface.

import "@0xessential/contracts/fwd/EssentialERC2771Context.sol";
import "@0xessential/contracts/fwd/IForwardRequest.sol";

contract MyContract is EssentialERC2771Context {/*...*/}

Constructor

EssentialERC2771Context requires configuration via a constructor. For security, your contract should only accept transactions from trusted forwarding contracts - either 0xEssential's deployed instances or your own.

Your contract constructor must call EssentialERC2771Context with the address of a trusted forwarder. We recommend passing this as an argument to your deployment:solid

constructor(address trustedForwarder) EssentialERC2771Context(trustedForwarder) {}

_msgSender() & _msgNFT()

When using meta-transactions we can't depend on msg.sender - all msg.sender calls must be replaced with _msgSender(). This function returns the trusted signer EOA address who submitted the meta-tx request.

To access data about an NFT being used in a meta-transaction request, call _msgNFT() - this returns a struct with the chainId, contractAddress, and tokenId of the NFT. 0xEssential's Forwarder and Ownership API have verified that _msgSender() owns _msgNFT().

mapping(uint256 => mapping(address => mapping(uint256 => address))) internal tokenGatedFuncCaller;

function tokenGatedFunc(string stringArg) external onlyForwarder {
    IForwardRequest.NFT memory nft = _msgNFT();
    address owner = _msgSender();
    //
    require(
        tokenGatedFuncCaller[nft.chainId][nft.contractAddress][nft.tokenId] == address(0),
        "NFT already used"
    );

    tokenGatedFuncCaller[nft.chainId][nft.contractAddress][nft.tokenId] = owner;
}

0xEssential's deployed Forwarder uses a 10 minute slack period for NFT ownership. NFT ownership is looked up live, and we ensure that the lookup is within 10 minutes of the transaction request. If your L2 experience has different requirements you may want to deploy your own EssentialForwarder.

Last updated