Implement the Operator Allowlist
This tutorial will guide you through the steps required to ensure your collection is compliant with Immutable's Operator Allowlist.
Implementation Overview
The Operator Allowlist consists of 2 steps, only 1 of which is mandatory.
1. Asset Compliance (Mandatory)
Applies to: ERC-721 & ERC-1155 collections.
When required: For every NFT/SFT collection deployed on Immutable zkEVM.
Required action: Add interface to collection contract.
2. Utility Contract Privileges (Optional)
Applies to: Smart Contracts.
When required: Contracts necessitating approvals from collections or contracts facilitating transfers of assets between addresses.
Required action: Add smart contract to Immutable's registry via Hub.
Step 1: Asset Compliance (Mandatory)
Every NFT/SFT collection deployed on Immutable must be compliant to our Operator Allowlist specifications. Immutable streamlines this process by offering preset contracts which have the Operator Allowlist interface preconfigured. Should game studios choose to deploy their own custom collections, they must follow the instructions below to ensure their collection respects royalty fees across the platform.
- Immutable Preset (Recommended)
- Custom Collection
All Immutable provided preset contracts for NFT/SFT collections have the Operator Allowlist preconfigured, ensuring that your revenue from trades is protected across our platform.
If you have deployed or extended one of Immutable's preset contracts, either from Immutable Hub or manually, that's everything required to ensure compliance.
In cases where custom functionality is required, game studios can manually comply to the Operator Allowlist requirements by following the instructions below.
All collections on Immutable zkEVM are required to inherit OperatorAllowlistEnforced.sol and add necessary overrides to Approve and Transfer functions, allowing your collection to interact with the OperatorAllowlist
. OperatorAllowlistEnforced
includes the modifier functions to identify compliant smart contracts for approvals and transfers, ensuring the preservation of content creators' royalty fees and protocol fees in third-party marketplace transactions.
Add the Operator Allowlist to your custom collection
Example contracts using OpenZeppelin's ERC-721 and ERC-1155 standards with the OperatorAllowlistEnforced
contract are provided below:
- ERC721
- ERC721 Upgradable
- ERC1155
- ERC1155 Upgradable
Below is an example ClashOfCatsERC721
contract that inherits our OperatorAllowlistEnforced
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {OperatorAllowlistEnforced} from '@imtbl/contracts/contracts/allowlist/OperatorAllowlistEnforced.sol';
contract ClashOfCatsERC721 is ERC721, Ownable, OperatorAllowlistEnforced {
constructor(
string memory name,
string memory symbol,
address operatorAllowlist_
) ERC721(name, symbol) Ownable() {
// OAL address is set in the constructor
_setOperatorAllowlistRegistry(operatorAllowlist_);
}
// Overrides _approve function to include `validateApproval` modifier for OAL
function _approve(
address to,
uint256 tokenId
) internal override(ERC721) validateApproval(to) {
super._approve(to, tokenId);
}
// Overrides setApprovalForAll function to include `validateApproval` modifier for OAL
function setApprovalForAll(
address operator,
bool approved
) public override(ERC721) validateApproval(operator) {
super.setApprovalForAll(operator, approved);
}
// Overrides _transfer function to include `validateTransfer` modifier for OAL
function _transfer(
address from,
address to,
uint256 tokenId
) internal override(ERC721) validateTransfer(from, to) {
super._transfer(from, to, tokenId);
}
}
Below is an example ClashOfCatsERC721Upgradable
contract that inherits our OperatorAllowlistEnforced
:
pragma solidity 0.8.19;
import '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {OperatorAllowlistEnforced} from '@imtbl/contracts/contracts/allowlist/OperatorAllowlistEnforced.sol';
contract ClashOfCatsERC721Upgradable is
ERC721Upgradeable,
OperatorAllowlistEnforced
{
function initialize(
string memory name_,
string memory symbol_,
address operatorAllowlist_
) public initializer {
__ERC721_init(name_, symbol_);
// OAL address is set in the constructor
_setOperatorAllowlistRegistry(operatorAllowlist_);
}
// Overrides approve function to include `validateApproval` modifier for OAL
function approve(
address to,
uint256 tokenId
) public virtual override(ERC721Upgradeable) validateApproval(to) {
super.approve(to, tokenId);
}
// Overrides setApprovalForAll function to include `validateApproval` modifier for OAL
function setApprovalForAll(
address operator,
bool approved
) public virtual override(ERC721Upgradeable) validateApproval(operator) {
super.setApprovalForAll(operator, approved);
}
// Overrides _safeTransfer function to include `validateTransfer` modifier for OAL
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory _data
) internal virtual override(ERC721Upgradeable) validateTransfer(from, to) {
super._safeTransfer(from, to, tokenId, _data);
}
// Overrides transferFrom function to include `validateTransfer` modifier for OAL
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override(ERC721Upgradeable) validateTransfer(from, to) {
super.transferFrom(from, to, tokenId);
}
}
Below is an example ClashOfCatsERC1155
contract that inherits our OperatorAllowlistEnforced
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {OperatorAllowlistEnforced} from '@imtbl/contracts/contracts/allowlist/OperatorAllowlistEnforced.sol';
contract ClashOfCatsERC1155 is ERC1155, Ownable, OperatorAllowlistEnforced {
constructor(
string memory baseTokenURI,
address operatorAllowlist_
) ERC1155(baseTokenURI) Ownable() {
// OAL address is set in the constructor
_setOperatorAllowlistRegistry(operatorAllowlist_);
}
// Overrides _safeTransferFrom function to include `validateTransfer` modifier for OAL
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 value,
bytes memory data
) internal override validateTransfer(from, to) {
super._safeTransferFrom(from, to, id, value, data);
}
// Overrides _safeBatchTransferFrom function to include `validateTransfer` modifier for OAL
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal override validateTransfer(from, to) {
super._safeBatchTransferFrom(from, to, ids, values, data);
}
// Overrides setApprovalForAll function to include `validateApproval` modifier for OAL
function setApprovalForAll(
address operator,
bool approved
) public override(ERC1155) validateApproval(operator) {
super.setApprovalForAll(operator, approved);
}
}
Below is an example ClashOfCatsERC1155Upgradable
contract that inherits our OperatorAllowlistEnforced
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import '@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {OperatorAllowlistEnforced} from './OperatorAllowlistEnforced.sol';
contract ClashOfCatsERC1155Upgradable is
ERC1155Upgradeable,
OperatorAllowlistEnforced
{
function initialize(
string memory uri_,
address operatorAllowlist_
) public initializer {
__ERC1155_init(uri_);
// OAL address is set in the constructor
_setOperatorAllowlistRegistry(operatorAllowlist_);
}
// Overrides _safeTransferFrom function to include `validateTransfer` modifier for OAL
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 value,
bytes memory data
) internal override(ERC1155Upgradeable) validateTransfer(from, to) {
super._safeTransferFrom(from, to, id, value, data);
}
// Overrides _safeBatchTransferFrom function to include `validateTransfer` modifier for OAL
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal override(ERC1155Upgradeable) validateTransfer(from, to) {
super._safeBatchTransferFrom(from, to, ids, values, data);
}
// Overrides setApprovalForAll function to include `validateApproval` modifier for OAL
function setApprovalForAll(
address operator,
bool approved
) public override(ERC1155Upgradeable) validateApproval(operator) {
super.setApprovalForAll(operator, approved);
}
}
Operator Allowlist Checklist
Here is a simple collection operator allowlist checklist you should follow to ensure your game has no delays at launch:
- Have you imported
OperatorAllowlistEnforced.sol
? - Have you implemented the allowlist check on transfers?
- Have you implemented the allowlist check on approvals?
Operator Allowlist values
If you manually deploy one of Immutable's preset contracts or your own custom contract that implements the Operator Allowlist, you must set the operatorAllowlist
parameter in the constructor correctly for the network you're deploying to.
The table below details the values required for Immutable zkEVM Testnet and Mainnet.
Chain Name | Chain ID | Operator Allowlist Address |
---|---|---|
imtbl-zkevm-testnet | eip155:13473 | 0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE |
imtbl-zkevm-mainnet | eip155:13371 | 0x5F5EBa8133f68ea22D712b0926e2803E78D89221 |
Step 2: Utility Contract Privileges (Optional)
Utility smart contracts that perform a subset of actions, that are not settlement contract, will need to be added to Immutable's Operator Allowlist registry to function as intended. This registration process is managed through your Hub account.
This step is not required for all smart contracts so please read this section carefully to understand if it applies to you.
Which smart contracts need elevated privileges?
If your utility smart contract performs the following actions, you will need to request for elevated privileges (i.e. be added to the Operator Allowlist):
- Contracts necessitating approvals from collections.
- Contracts facilitating the asset transfers of ERC721 or ERC1155 assets.
Examples of the above:
- Multi-caller contracts that bundle transactions
- Crafting contracts
- Primary sales contracts
- Reward distribution contracts
- Auction contracts
- Rental contracts
- Lending contracts
Ineligible utility smart contracts
The following smart contracts are ineligible for being added to the Operator Allowlist.
- Settlement contracts (excluding those provided by Immutable)
- Orderbook contracts that could fragment liquidity from Immutable's Global Orderbook
Requirements
Game studios must meet the following criteria to be able to submit a request for elevated privileges:
- Game studios must have a contract with Immutable.
- Contract must be verified in block explorer so Immutable can review your contract's code. This is for both Testnet and Mainnet.
- Contract has previously been approved for elevated privileges (i.e. added to Operator Allowlist registry) on Testnet and tested thoroughly, prior to proceeding with Mainnet.
- Note: All Testnet requests are auto-approved to expedite game integrations.
- Contract does not violate Immutable's ineligibility policy outlined above.
Submitting proxy contracts
Upgradable Contracts
Immutable supports adding proxy contracts to the Operator Allowlist registry. The proxy contract will require being added to the Operator Allowlist. Both proxy and the implementation contract that the proxy references will need to be verified in block explorer to be approved by Immutable.
Smart Contract Wallets
As smart contract wallets are deployed as proxy contracts with a specific implementation contract module, they will need their implementation contract approved as well. Both proxy and the implementation contract that the proxy references will need to be verified in block explorer to be approved by Immutable.
How do I submit a request for elevated smart contract privileges?
- Log into Immutable Hub
- Verify your contract via Immutable's explorer by following our smart contract verification tutorial.
- Link your smart contract by clicking Link Contract on the "Contracts" page in Immutable Hub.
- Go to the contract details page of the contract that you want to be added to OAL. Hit the "Add to OAL" button and follow the application prompts.
Processing time for elevated smart contract privilege requests
Immutable will process these requests as quickly as possible.
- Testnet additions are processed instantly.
- Mainnet additions require at least 48 hours for processing. Timely submission of requests is advised to avoid launch delays for your game.
Interface
Please refer to the OperatorAllowlist.sol
interface for information on specific methods.