EssentialContext
Inherit EssentialContext in your L2 contract
Last updated
Inherit EssentialContext in your L2 contract
Last updated
EssentialContext
is the contract primitive you must inherit in your Layer 2 contract to enable Gasless Transactions. If you've used meta-transactions in the past you will be familiar with this process. EssentialContext
is compatible with other implementations like OpenZeppelin's Context
, so if you already use that contract you may be able to skip this.
The important thing to know when writing contracts for Gasless Transactions is that you cannot use msg.sender
in your external or public functions. You must replace these calls with _msgSender()
.
EssentialContext
and a version compatible with upgradeable proxy contracts EssentialContextUpgradeable
are available in our contracts SDK. Source code is available on .
You can install from NPM with
or
You can also install the package from Github in a foundry project:
EssentialContext
must be initialized in your constructor with the address of the EssentialForwarder
you're using. If you're using 0xEssential's canonical EssentialForwarder
you can use this address across every network we support: 0x000000000066b3aED7Ae8263588dA67fF381FfCa
.
Specifying the trustedForwarder
restricts Gasless Transactions to forwarding contracts you trust. Without this restriction your contract could be manipulated by malicious forwarders.
EssentialContext
provides a modifier and other convenience functions for restricting calls to your contract. You must restrict calls that depend on NFT authorization to only be called by an EssentialForwarder
otherwise anyone can spoof NFT ownership data and manipulate your contract.
You can add the onlyForwarder
modifier to any function that depends on NFT authorization:
EssentialContext
provides admin functions for managing your trustedForwarder
address should you need to change it.
An external function setTrustedForwarder(address newForwarder)
is restricted to the contract deployer. An internal function is also available if you have different access control needs for administrative functions. We don't anticipate frequent deploys of the canonical EssentialForwarder
contracts, but you may decide to deploy your own for customization purposes.
When writing your contract that integrates NFT Global Entry you will depend on the functions _msgSender()
and _msgNFT()
to get data about the caller (NFT owner) and NFT data.
You should not write functions with arguments like tokenId
or contractAddress
to represent the NFT being "used."
Calls made to your contract's Global Entry functions will include the EssentialForwarder
as the msg.sender
. You must replace msg.sender
with _msgSender()
in order to pull the address of the original submitter from calldata.
This means that _msgSender()
is not necessarily the address that signed or submitted the transaction, but is always an address that owns or has authorization to use that NFT, and has given the transaction submitter authorization as well.
This allows you to build games with great UX without sacrificing your game or users' security. A user can play a game, using an NFT from a hardware wallet, signing Global Entry transactions from a burner wallet, with game reward tokens going to a hot wallet. As long as the user has created a chain of delegations, transactions that pass a delegation check will be valid.
Rather than writing external functions with arguments that represent the NFT being used, your L2 contract will instead use _msgNFT()
to get the verified data about an NFT _msgSender()
has authority to use.
_msgNFT()
returns a struct, IEssentialForwarder.GlobalEntryNFT
:
You can use this data however you need in your function logic to start creating crosschain token-gated applications. Maybe you're writing an onchain raffle with 1 NFT = 1 entry:
NFT Global Entry is only appropriate for applications outside of finance - do not use Global Entry for anything related to loans, staking or anything involving transferring ownership.
When 0xEssential's Ownership Oracle generates a proof of NFT ownership, the proof includes a timestamp. EssentialForwarder
verifies this proof, and requires that the timestamp is no more than 10 minutes old.
It's important to understand how this can be abused, and to recognize that if you require a tighter grace period you may deploy your own EssentialForwarder
and customize that parameter. An NFT holder could in theory generate a proof of their ownership, sell the NFT, and still be able to submit a transaction that passes NFT Global Entry authorization checks for the next 10 minutes.
Ownership proofs also depend on an onchain nonce per proof requester, so ownership proofs become invalid as soon as they are used in an onchain transaction and the requester nonce is incremented.
0xEssential takes QA and testing seriously. We provide utilities to help you write tests in JS/TS with hardhat or in Solidity with foundry.
Once you've inherited EssentialContext
and written some logic that depends on a crosschain NFT, you're ready to start working on your client app. 0xEssential offers a client SDK for React apps, @xessential/react
and an ethers-based package @xessential/signer
for other JS environments.
If you're not using React for your client app, you can use @xessential/signer
and the ethers compatible EssentialSigner
class to handle transaction preparation, proof fetching, and transaction sending, whether you're using Gasless or standard transactions.
_msgSender()
is guaranteed to own or have authorization to use _msgNFT()
any time your function is called. Global Entry supports Account Delegation via , and allows your frontend to specify the address that your contract will receive as _msgSender()
.
If you're using React on your frontend, Global Entry is simple to integrate, particularly if you're already using wagmi
hooks. Head to our to get started.