Crosschain Token Gated Minting

A common approach to generating attention for your NFT project is to grant holders of other projects some advantage in minting - an exclusive minting period or a cheaper price are some of the perks we often see.

There are a couple of ways to implement this functionality and they all work just fine, most of them even in the crosschain context. Merkle trees and signed allowances are acceptable approaches, where the project owner gathers addresses who are allowlisted. Signed allowances are more flexible but trust laden, allowing the developer to generate or remove grants, while Merkle trees tend to be more of an immutable snapshot for better or worse.

The most onchain and trustless approach is to call an NFT contract of a partner project inside a mint function to determine whether the caller is a holder and should be granted minting privileges. This approach however breaks down when we are minting an NFT on an L2 but wish to gate our benefits based on L1 NFT ownership - L2 contracts cannot directly read L1 contracts.

This post and a demo NFT mint introduce a new approach, one that uses ERC-3668 and NFT Global Entry tooling to get the benefits of composability by placing minimal trust in 0xEssential’s ownership oracle to enable crosschain proof of NFT ownership.

An open-edition free mint NFT is available for minting on the Zora network, and the code for the demo is open source. Holders of any of my previous projects can mint for free. And rather than reaching out to projects and managing some sort of “collab manager” process (get a real job), anyone can pay 0.05 ETH to register a mainnet NFT contract, allowing holders to mint a free Global Zorbal Entry NFT.

Mint this NFT for free if you hold an affiliated mainnet NFT
Mint this NFT for free if you hold an affiliated mainnet NFT

NFT Global Entry

Global Entry is a full stack solution for building L2 contracts that depend on NFT ownership from other EVM chains.

Instead of bridging NFTs to use them in games or other low-value transactions, Global Entry allows for crosschain token gating - keep the NFT on its origin chain, and use a cheaper, faster chain as an execution layer for game, governance or other logic.

If bridging is slow and not very secure (mostly theater), then NFT Global Entry is like skipping the security line at the airport, where a third party is signing off on your ability to board. Its honestly not a great metaphor, but much like my “man in the arena” Zoom background it’s meant to pique the interest of VCs. Plus the logo is pretty sweet in a trash art sort of way.

Global Entry is deployed on a bunch of EVM chains, and supports gating ownership from any of those supported chains - so you can token gate a function on Base for NFTs owned on Zora or a function on Polygon for NFTs owned on mainnet. It implements ERC-3668, developed by Nick Johnson at ENS.

Read that proposal for the technical details, but in simple terms, the Layer 2 contract gates function calls by saying “cool transaction, but go get proof you’re allowed to submit it and try again.” The data included in this OffchainLookup error instructs the client how and where to get that proof, and in our case it gets an Ownership Proof from our ownership oracle API.

Once the transaction is submitted with an ownership proof, our protocol verifies the proof and calls your L2 contract, passing along data about the NFT we verified as owned by the caller. Ownership proofs are short-lived and include a nonce so they cannot be reused.

Similar approaches might use storage proofs to prove the state of L1, but they can be inflexible and scaling is a challenge as they depend on the storage layout of an L1 contract which can have major differences even between ERC-721 (NFT compliant) implementations. That said I’m keen on what Herodotus is working on, so check them out too!

NFT Global Entry transactions can be submitted as native L2 transactions, but the protocol also supports sponsored meta-transactions - in a world with new L2s are launching every week, meta-transactions help us offer a better user experience that doesn’t require users to bridge or acquire gas tokens on every esoteric L2 they want to transact on. It also removes the need for users to switch their wallet network, or even add the L2 to their wallet at all.

Global Entry also supports delegation protocols, so users who hold an NFT in a hardware wallet can still perform gated functions from a hot wallet, as long as the vault has delegated access to the hot wallet. Finally we offer an email / password based burner wallet service that allows meta-transactions to be signed without any wallet popup, ideal for games and other high transaction count / low transaction value apps.

While storage proofs or even bridging might be a better solution for certain cases, NFT Global Entry and the related tooling is geared towards user and developer experience. Users should be able to engage with onchain experiences by simply clicking buttons -without bridging, acquiring esoteric gas tokens or approving every no-risk transaction in a wallet popup, taking them out of flow. And developers should be able to craft these experiences without wholesale changes to their development workflow or tools.

Integration

This won’t be a full dev tutorial (see our docs for that) but we should at least look at a some code to see how simple it is to adopt NFT Global Entry and complementary tooling. The complete code for our demo contract and app is available on Github.

Contract

To use NFT Global Entry to gate a L2 function, developers install our SDK and inherit EssentialERC2771Context in the contract they’ll deploy to an L2.

Then, token gated functions must use our onlyForwarder modifier, and the developer can access the owner at _msgSender() and a struct representing the NFT at _msgNFT().

Developers are responsible for using that data in their business logic - for our demo project, we really only care that the NFT being “used” in the transaction is from a project who we’ve granted partner status. We’re doing this by allowing anyone to sponsor a project, but you might use a Merkle tree or admin managed mapping instead, and maybe you want to allow 1 mint per L1 NFT, so you would want to store that data and add some more guards.

A minimal example might look something like this:

When the mint function is executed, the developer can assume that the _msgSender() owns _msgNFT() - the function will not be called by the forwarder protocol unless this is true. Note that we don’t include function parameters for the NFT here and instead pull it out of calldata with _msgNFT(). Even with native transactions, calls are routed through the EssentialForwarder protocol as middleware, responsible for verifying the ownership proof.

App

Our JS SDK is based on tools fronted developers already use. It should be simple to adopt Global Entry, and to drop it if you decide. Our SDK doesn’t require a whole rewrite of your app or to learn a new API. Just use our imports instead of wagmi ones and add some overloaded arguments to transaction calls.

While we try to make integration as simple as possible, our support for meta-transactions and delegated accounts adds a lot of optional complexity to best suit your needs. You could for instance give users the choice between native and meta transactions, or base the transaction type on the user’s L2 gas token balance or some internal budget of per-user sponsored spend - its all up to you as the developer.

An acceptable UX should provide a button to switch to the Zora chain and a link to the Zora bridge if the user lacks Zora ETH to pay for their transaction fee. We like to push that functionality down into a generic EssentialTransactionButton that handles connected network and balance, responding appropriately to the transaction type specified.

For now, let’s replicate wagmi’s documentation example for writing to a contract using native transactions:

If you use wagmi, this should look familiar! We supply the contract address, abi and chain, plus the function we want to call and any args, just like we would to wagmi’s usePrepareContractWrite - we need to import the hook from @xessential/react instead of wagmi.

NFT Global Entry adds some properties - txMode is used to route the transaction through the user’s wallet as a native transaction (“std”) or as a meta-transaction signature request (“meta”) which is then POSTed to a Relayer API. Meta-transaction SDKs usually require a massive overhaul to how you submit transactions, but we like to take responsibility for that complexity and give frontend devs easy optionality between the two types.

Under overrides.customData is where you see the aspects of NFT Global Entry with account delegation support. When we wish to have a user submit a transaction that “uses” an NFT, we specify the nftChainId, nftContract and nftTokenId.

If the user is connected to an account other than the one that owns this NFT, we can specify an authorizer address - Global Entry uses DelegateCash to verify that the user is authorized by authorizer, and that the authorizer owns the NFT being used. So if an NFT is owned by an account secured by a hardware wallet, a one-time DelegateCash delegation would allow the delegated account to use that NFT in Global Entry transactions - we also provide some React hooks to manage user context that respects delegation.

Developers are responsible for ascertaining the NFTs owned by the address and providing the chain, contract address and token ID, but we also have some open source code to help with that. Note that this data isn’t passed as args to contract functions - instead, the contract reads the owned NFT from _msgNFT().

Demo

We deployed a demo NFT contract to Zora and an app that uses the integrations we walked through in this post. The NFT Global Entry protocol and Zorbal Entry NFT contract can both be found on Zora’s explora, while the app can be accessed at zora.0xessential.com.

The Zorbal Entry NFT is an open edition and free to mint NFT for holders of affiliated projects. If you don’t own an affiliated project NFT, you can simply pay the 0.05 ETH to affiliate any mainnet NFT project, allowing any holders to then mint for free.

Next steps

Part II of this demo project will be adding meta-transaction and delegation support - affiliate funds will be used to sponsor gas-free minting in our next release. We’ll publish a second post and update the open source repo and demo app to show how you can layer on this functionality. With EssentialERC7221Context, any contract can add meta-transaction and account delegation support without any contract upgrades or redeploys.

We’ll also look at some more open source code for things like respecting user context with delegations, integrating our Burner Wallet service and giving users the choice between meta and native transactions.

Subscribe to sammybauch.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.