Limit Break's official transfer validation contract designed to provide a customizable and secure transfer validation mechanism for Apptokens / Creator tokens (ERC20-C, ERC721-C, ERC1155-C). This contract allows the owner of a C-token to choose a validation ruleset to apply to their collection, global and ruleset-specific options to fine-tune validation behavior, and manage blacklists/whitelists/authorizer/expansion lists and expansion settings for rulesets.
Why is Creator Token Transfer Validator getting updated? Primarily to adapt to the most recent Ethereum network upgrade. The Prague-Electra (Pectra) hardfork contains a EIP-7702 that marks a major change to the behavior of EOA accounts. With EIP-7702, delegations can be signed allowing code can be attached and detached to/from Externally Owned Accounts (EOAs). This means that wallets that previously could not execute trustless code are now able to execute smart contract code natively, and this code can be added to the EOA or removed from the EOA at anytime. What does this mean in the context of Apptokens and ERC20-C/ ERC721-C/ERC1155-C? Before we can answer that question, we need a brief refresher on 721-C security levels.
Since its inception, the Creator Token Transfer Validator has included the concept of Security Levels. 721-C NFT creators could freely increase or decrease the security levels of their collections to defeat various royalty evasion techniques. They could start with a relatively permissive security level and escalate to a higher level should any royalty evasion techniques begin to scale. As a refresher from prior validator versions, common techniques for evading transfer restrictions and/or royalties is shown in the table below with their effectiveness by 721-C security level.
In the following tables below, a checkmark denotes that the security level is secure from the specified evasion tactic.
| Level | Blocked Exchange | Pop-Up Exchange | OTC / Escrow | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA |
|---|---|---|---|---|---|---|
| 0 (Default) | ✔ | ✔ | ||||
| 1 | ||||||
| 2 | ✔ | |||||
| 3 | ✔ | ✔ | ||||
| 4 | ✔ | ✔ | ✔ | |||
| 5 | ✔ | ✔ | Limited | ✔ | ||
| 6 | ✔ | ✔ | ✔ | ✔ | ||
| 7 | ✔ | ✔ | ✔ | Limited | ✔ | Limited |
| 8 | ✔ | ✔ | ✔ | ✔ | ✔ | Limited |
| 9 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
As you can see, OTC, Escrow and Wrapper contracts have always presented some risk of isolated actions to bypass the rules governing transfers and royalties. However, because creators could escalate security level to block transfers to smart contract or to block OTC at any time, these risks were never scaled, and creators were safe to remain at the more permissive security levels. OTC (owner-initiated transfers) has not led to any significantly scalable ways to get around creator royalties.
Back to the impact of EIP-7702...because EIP-7702 now allows EOAs to act with smart contract code attached, it is now possible to attach royalty-free marketplace code to any EOA as a way to trustlessly process an NFT trade that does not honor royalties. Does this defeat 721-C with its existing security leveling? It does not, but it does require creators to escalate from the previous default Security Level 3 where OTC was allowed to either Security Level 4, 7 or 8 which all block OTC transfers unless the owner is whitelisted. Blocking wallet to wallet OTC transfers isn't the end of the world from a developer's perspective, but most developers would agree that it would be nice to continue to allow OTC transfers to the greatest extent possible providing they don't lead to abuse of transfer rules and royalties.
Version 5 of the Transfer Validator is focused on making improvements that keep up with changes on the Ethereum network to preserve the wallet to wallet OTC transfers in most cases and minimize the impacts of EIP-7702. In Version 5, Limit Break also goes beyond the scope of EIP-7702 and has future-proofed the Transfer Validator so that future updates can be made in order to keep up with additional changes to Ethereum that haven't even been considered or proposed yet. The re-design of the Transfer Validator will allow Limit Break to continue to adapt to future EIPs that could impact 20-C/721-C/1155-C without requiring migrations or actions from creators to keep up-to-date. The following subsections detail the key changes made from Version 4 to Version 5.
- Replaced Transfer Security Levels With Modular Rulesets
- Feature: Default List Extension
- Feature: EIP-7702 OTC Option
- Feature: Smart Wallet OTC Option
- Feature: Global and Ruleset Options
- Feature: Whitelist Extension Contracts
- Feature: Expansion Lists
- Feature: Expansion Settings
Prior versions of the transfer validator combined all the logic for security levels into a large, monolithic validation function. Version 5 replaces this monolithic function and transfer security levels with the concept of Rulesets. Rulesets are entirely read-only and unable to change EVM state, as their sole purpose is to determine if a given transfer should be allowed or blocked. Version 5 will initially ship with four rulesets that replicate and improve upon the previous nine transfer security levels (Vanilla, Soulbound, Blacklist, and Whitelist Rulesets).
Over time, Limit Break can register new ruleset modules. These modules could be entirely new types of validation rulesets, or they can be upgrades to existing rulesets such as an augmented Whitelist ruleset that offers new transfer validation options, or patches issues created by new EIPs.
Because ruleset modules must not be able to modify EVM state during execution, the module registration process includes a code purity check. Every opcode in the module is checked against a set of invalid or banned opcodes that have the potential to change state, ensuring that registering a module that modifies the EVM at validation time is impossible.
Upon successful registration, rulesets are bound to Ruleset Ids. Usable Ids range from 0-254.
Ruleset Id 0 is reserved for the default ruleset of the validator. It is currently bound to Ruleset Whitelist, so unless otherwise modified by the creator, the default validation ruleset for a 20-C/721-C/1155-C token is Whitelist with all default global and ruleset-specific option flags set to 0.
Ruleset Id 255 is reserved for creators that wish to opt out of Automatic Updates (more on this in a moment).
With up to 255 ruleset id bindings available, there is a lot of room for future expansion of the Creator Token Transfer Validator capabilities.
The system of registration and binding rulesets creates the opportunity for creators to get passive security upgrades to their creator tokens. In prior versions of the validator, the creator would have to migrate to a new validator. Once migrated to Version 5, creators automatically get access to enhancements without having to be notified to take specific actions!
Note: Creators can opt out of the Automatic Update feature by using Ruleset Id 255 for their token security policies and specifying a specific registered ruleset they want to use. When creators specify the exact ruleset address they wish to use this way, rebindings of rulesets to other ids will not change the validation behavior of their tokens.
In prior versions of Transfer Validator, when creators switched to a custom list id instead of the default, they had to fully manage the custom list. If they still wanted access to default whitelisted protocols, for example, the creator had to add those protocols to their custom list in addition to the extra protocols they put into the list. Updates to the default list were not automatically reflected in the creators' custom lists either.
Version 5 adds an option to extend the default list with a custom list, making the addition of custom protocols much easier. When the creator opts into supplementing their custom list with the default list, they receive automatic updates that follow the custom list, plus they can easily add the additional protocols they want to work with to their custom list and both lists will be checked during transfer validation.
The Whitelist ruleset includes a new option to either allow or block OTC transfers when the OTC transfer is performed by an EOA with an EIP-7702 delegation attached. This option is only evaluated when a collection is not configured to block all OTC. Because of the risk that transfer rules are bypassed by EOAs with 7702 delegates attached, the default behavior is to Block OTC from EOAs in delegation mode. Creators can toggle this setting to opt out of this protection and allow OTC from EOAs in delegation mode. Additionally, when blocking EOAs with delegates creators can whitelist trusted delegate code addresses, codehashes, or even specific delegate code factories that are known to be safe to broaden access to OTC transfers to as many users as possible.
As account abstraction wallets gain popularity, more and more users will hold tokens in smart contract wallets. The Whitelist ruleset includes a new option to either allow or block OTC transfers when the OTC transfer is performed by a smart wallet. This option is only evaluated when a collection is not configured to block all OTC. The default behavior is to Block OTC from Smart Wallets. Creators can toggle this setting to opt out of this protection. Additionally, when blocking OTC from smart wallets, creators can whitelist trusted smart wallet code addresses, codehashes, or even specific smart wallet factories that are known to be safe to broaden access to OTC transfers to as many users as possible.
The Token Security Policy now includes an 8-bit Global Options field and a 16-bit Ruleset-Specific Options field. Global options generally apply to many rulesets (for example: account freezing and authorizer mode flags). Ruleset-specific options generally apply to a single ruleset. For example, the Whitelist ruleset makes use of 5 flags that fine-tune Whitelisting ruleset behavior.
New ruleset implementations can make use of up to 16 ruleset-specific option flags in addition to the standard global options.
Version 5 introduces a new way to make more complex decisions about what accounts should be whitelisted, going beyond account address and codehash. Creators may add external contracts that query wallet factories, for example. This expands the possibilities for easily whitelisting protocols that create many contract instances, such as a wallet factory or AMM with individual pools.
Over time, new rulesets may require the use of new kinds of lists. Prior versions of the transfer validator only included blacklists, whitelists, and authorizer lists. The creation of a system of expansion lists allows new rulesets to incorporate new list types that can be attached to any list id.
For example, the following list types are currently defined:
| List Type | List Type Id | Used In Rulesets |
|---|---|---|
| Blacklist | 0 | Blacklist |
| Whitelist | 1 | Whitelist |
| Authorizers | 2 | Blacklist + Whitelist |
| Whitelist Extension Contracts | 3 | Whitelist |
| EIP-7702 Delegate Whitelist | 4 | Whitelist |
| EIP-7702 Delegate Whitelist Extension Contracts | 5 | Whitelist |
Version 5 of the Transfer Validator supports up to 256 total list types, plenty of room for future expansion.
Over time, new rulesets may require the use of new token-specific settings. Version 5 includes a system for setting arbitrary expansion settings as key-value pairs. Key-value pairs come in two forms: Expansion Words and Expansion Datums. Expansion Words have 32-byte keys and 32-byte values. Expansion Datums have 32-byte keys and variable length bytes values for maximum flexibility and future expansion. At this time, no rulesets currently use expansion settings, and this feature is solely for future-proofing.
The following table displays the feature comparison chart between versions of the transfer validator.
| Feature Comparison Matrix | V1 | V2 | V3 | V4 | V5 |
|---|---|---|---|---|---|
| Lists / List Management | |||||
| List Copy | ✔ | ✔ | ✔ | ✔ | |
| Account Whitelisting | ✔ | ✔ | ✔ | ✔ | ✔ |
| Account Blacklisting | ✔ | ✔ | ✔ | ✔ | |
| Code Hash Whitelisting | ✔ | ✔ | ✔ | ✔ | |
| Code Hash Blacklisting | ✔ | ✔ | ✔ | ✔ | |
| Authorizer Accounts List | ✔ | ✔ | ✔ | ||
| Creator-Defined Frozen Accounts List | ✔ | ✔ | ✔ | ||
| Default List Extension | ✔ | ||||
| Expansion Lists | ✔ | ||||
| Expansion Settings | ✔ | ||||
| EIP-7702 Delegate Whitelisting | ✔ | ||||
| Whitelist Extension Contracts | ✔ | ||||
| Security Escalation and De-Escalation System | |||||
| Transfer Security Levels (1-9) | ✔ | ✔ | ✔ | ✔ | |
| Transfer Ruleset Modules | ✔ | ||||
| Global Options | |||||
| Authorization Mode On/Off | ✔ | ✔ | ✔ | ||
| Authorization Mode Wildcard Operators On/Off | ✔ | ✔ | ✔ | ||
| Account Freezing Mode On/Off | ✔ | ✔ | ✔ | ||
| Extend Default Lists Mode On/Off | ✔ | ||||
| Whitelist Ruleset Options | |||||
| Block All OTC Mode On/Off | ✔ | ✔ | ✔ | ✔ | ✔ |
| Block Smart Wallet Receivers Mode On/Off | ✔ | ✔ | ✔ | ✔ | ✔ |
| Block Unverified EOA Receivers Mode On/Off | ✔ | ✔ | ✔ | ✔ | ✔ |
| Allow OTC For 7702 Delegates Mode On/Off | ✔ | ||||
| Allow OTC For Smart Wallets Mode On/Off | ✔ | ||||
| Software Management / Upgradeability | |||||
| Permissionless Deploy and Config (Any EVM) | ✔ | ✔ | |||
| Automatic Ruleset Updates (Passive for Creators) | ✔ | ||||
| Automatic Update Opt-Out | ✔ | ||||
| Access To Future Validation Rulesets | ✔ | ||||
| Advanced Features | |||||
| Permit-C Permit Processing | ✔ | ✔ | ✔ | ||
| Miscellaneous | |||||
| Authorization Mode With Amounts | ✔ | ||||
| Transfer Validation Simulation Functions | ✔ |
Most creators will find that the default settings meet their needs. Following the default settings on the Version 5 Transfer Validator offers creators the following:
- Default Lists (List Id 0)
- Whitelist Apptoken Protocols
- Payment Processor
- TokenMaster
- Authorizer List
- Seaport Trading with OpenSea Royalty Sign-Off
- Seaport Trading with Reservoir Royalty Sign-Off
- Trusted Smart Wallets and EIP-7702 Delegates List
- Whitelist Apptoken Protocols
- Default Ruleset
- Whitelist with following options
- Authorization Mode: Enabled
- Account Freezing Mode: Disabled
- Default List Extension: N/A
- Allow OTC: Enabled
- Allow EIP-7702 Delegate OTC: Disabled
- Allow Smart Wallet OTC: Disabled
- Block Smart Wallet Receivers: Disabled
- Block Unverified EOA Receivers: Disabled
- Whitelist with following options
- Automatic Updates: Default ruleset may be patched and upgraded by Limit Break as EVM evolves to keep creators protected. Default whitelist and authorizer list kept up to date as new protocols are released.
To configure tokens to use default settings, update to the latest Creator Token Standards library if using Foundry or the NPM Creator Token Standards library if using Hardhat, and deploy a new ERC20-C/ERC721-C/ERC1155-C token.
For collections that are already live, change the validator address to Version 5 Transfer Validator at any time by calling the following function on your collection.
function setTransferValidator(address validator) external;Transfer Validator addresses are documented at apptokens.com, and Limit Break provides Developer Tools to configure your validator here.
The Transfer Validator gives creators complete control over their token ecosystem. This section explores the customization options available to creators.
Transfer Validator Version 5 currently includes four ruleset modules.
| Ruleset | Id Binding | Global Options | Ruleset Options | Default Ruleset | Description |
|---|---|---|---|---|---|
| Vanilla | 1 | None | None | Equivalent to Security Level 1 from prior validators. Allows all transfers. | |
| Soulbound | 2 | None | None | Equivalent to Security Level 9 from prior validators. Blocks all transfers. | |
| Blacklist | 3 | GO0/1/2/3 | None | Equivalent to Security Level 2 from prior validators. Allows all transfers, unless the operator (msg.sender) is explicitly blocked by address or codehash. | |
| Whitelist | 0, 4 | GO0/1/2/3 | WLO0/1/2/3/4/5 | ✔ | Encompasses Security Levels 3 through 8 from prior validators. Blocks all transfers, unless allowed through an authorizer, a whitelist, or other whitelisting option. |
Note: By default, all options bits are set to zero (off).
Global Options Table
| Option Code | Option Name | Bit | Rulesets |
|---|---|---|---|
| GO0 | Global Option Disable Authorization Mode | 0 | Whitelist Blacklist |
| GO1 | Global Option No Authorizer Wildcard Operators Mode | 1 | Whitelist Blacklist |
| GO2 | Global Option Account Freezing Mode | 2 | Whitelist Blacklist |
| GO3 | Global Option Default List Extension Mode | 3 | Whitelist Blacklist |
| GO4 | Reserved | 4 | |
| GO5 | Reserved | 5 | |
| GO6 | Reserved | 6 | |
| GO7 | Reserved | 7 |
Ruleset Whitelist Options Table
| Option Code | Option Name | Bit | Rulesets |
|---|---|---|---|
| WLO0 | Whitelist Option Block All OTC | 0 | Whitelist |
| WLO1 | Whitelist Option Allow OTC For 7702 Delegates | 1 | Whitelist |
| WLO2 | Whitelist Option Allow OTC For Smart Wallets | 2 | Whitelist |
| WLO3 | Whitelist Option Block Smart Wallet Receivers | 3 | Whitelist |
| WLO4 | Whitelist Option Block Unverified EOA Receivers | 4 | Whitelist |
| WLO5 | Reserved | 5 | |
| WLO6 | Reserved | 6 | |
| WLO7 | Reserved | 7 | |
| WLO8 | Reserved | 8 | |
| WLO9 | Reserved | 9 | |
| WLO10 | Reserved | 10 | |
| WLO11 | Reserved | 11 | |
| WLO12 | Reserved | 12 | |
| WLO13 | Reserved | 13 | |
| WLO14 | Reserved | 14 | |
| WLO15 | Reserved | 15 |
Most rulesets use lists of accounts or codehashes to allow or deny transfers. These lists function much like a firewall with an access control list. This on-chain access control list governs what operators are allowed, etc. Lists are identified and accessed by a key derived from two pieces of information. First, the List Id, is a uint48 value ranging from 0 (Default List Id Managed By Limit Break) to 281,474,976,710,655. Second, the List Type, is a uint8 value ranging from 0 to 255 that partitions the List Id into multiple kinds of lists.
List Ids
| List Id | List Manager | Description |
|---|---|---|
| 0 | Limit Break | Default List |
| 1 - 281,474,976,710,655 | Developers / Creators Exchanges DAOs / Enterprises |
Custom / Community Lists |
List Types
| Type Id | List Type | Applicable Ruleset(s) |
|---|---|---|
| 0 | Blacklist | Blacklist |
| 1 | Whitelist | Whitelist |
| 2 | Authorizers | Whitelist Blacklist |
| 3 | Whitelist Extension Contracts | Whitelist |
| 4 | EIP-7702 Delegate Whitelist | Whitelist |
| 5 | EIP-7702 Delegate Whitelist Extension Contracts | Whitelist |
| 6-255 | Reserved for Future Validator Expansion | TBD |
Blacklists, applicable only to the Blacklist ruleset, contain a set of account addresses and codehashes. The blacklist ruleset allows all transfers unless the operator (msg.sender) address is explicitly specified in the blacklist or the codehash of the operator is explicitly specified in the blacklist. Blacklists may extend the default blacklist, in which case the collection owner can specify a custom list id to be combined with the contents of the default blacklist with list id 0.
Whitelists, applicable only to the Whitelist ruleset, contain a set of account addresses and codehashes that are explicitly allowed to be the operator on transfers, or explicit accounts that can perform OTC transfers in all circumstances, or carveouts to smart contract receiver constraints.
A whitelist extension contract is an external contract that can be queried to perform a check more advanced than a simple account or codehash comparison. For example, lets say that a whitelist should include any smart wallet contract that originated from a specific trusted wallet factory. An extension contract that checks to see if an account address was created by the trusted factory could be deployed and added to the whitelist extension contracts list.
The following table shows how various options block transfers and how whitelists can be applied to allow the transfer in a controlled manner.
| Option | Scenario | Whitelisting Checks |
|---|---|---|
| WLO0 | When Block All OTC is enabled, and caller equals from (token owner calls transfer directly)... |
Check if caller (owner/sender) is a whitelisted address. If not, check if codehash of caller is whitelisted. If not, iterate over whitelist extensions and check if caller is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
| WLO0 | When Block All OTC is enabled, and caller does not equal from (operator/protocol-initiated transfer)... |
Check if caller or from (operator/protocol/sender) is a whitelisted address. If not, check if codehash of caller or from is whitelisted. If not, iterate over whitelist extensions and check if caller or from is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
| WLO0 | When Block All OTC is disabled, and caller does not equal from (operator/protocol-initiated transfer)... |
Check if caller (operator/protocol) is a whitelisted address. If not, check if codehash of caller is whitelisted. If not, iterate over whitelist extensions and check if caller is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
| WLO3 | When Block Smart Wallet Receivers is enabled, and the codelength of to is greater than zero... |
Check if to (receiver) is a whitelisted address. If not, check if codehash of to is whitelisted. If not, iterate over whitelist extensions and check if to is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
| WLO4 | When Block Unverified EOA Receivers is enabled, and the to account has not verified a signature with the EOA registry... |
Check if to (receiver) is a whitelisted address. If not, check if codehash of to is whitelisted. If not, iterate over whitelist extensions and check if to is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
| WLO1 | When Block All OTC is disabled and Allow OTC For 7702 Delegates is disabled, and caller equals from (token owner calls transfer directly), and caller has an EIP-7702 delegation attached... |
Get the address of the delegation attached to caller. Check if delegation is a whitelisted address in the delegate whitelist. If not, check if codehash of delegation is whitelisted in the delegate whitelist. If not, iterate over delegate whitelist extensions and check if delegation is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable delegate whitelist checks fail, block the transfer. |
| WLO2 | When Block All OTC is disabled and Allow OTC For Smart Wallets is disabled, and caller equals from (token owner calls transfer directly), and the caller has code, but is not an EIP-7702 delegate... |
Check if caller (owner/sender) is a whitelisted address. If not, check if codehash of caller is whitelisted. If not, iterate over whitelist extensions and check if caller is whitelisted by any extension in the list. When options include default list extension mode, repeat checks for the default list as well. If all applicable whitelist checks fail, block the transfer. |
Authorizers are trusted smart contracts that have special permissions to override blacklist and whitelist restrictions. Special care must be taken to only add well-trusted authorizers. At the current time, the Seaport Royalty Enforcing Zone is the only contract that implements the required interfaces for an authorizer contract. The Seaport Royalty Enforcing Zone relies on an oracle to examine the royalties included in Seaport orders and sign a message authenticating that royalties are properly included in the order. The Royalty Enforcing Zone verifies the oracles signature, and once authenticated the zone uses authorization functions on the Transfer Validator to temporarily override blacklist or whitelist restrictions to allow Seaport trades that properly include full creator royalties. At the current time, OpenSea and Reservoir operate the two default trusted authorizer zones/oracles.
By default, Transfer Validator Version 5 allows Limit Break to upgrade and/or patch rulesets. This allows creators to have improved rulesets automatically applied to their collections without any action on their part. Some creators may prefer not to receive automatic updates. In this case, the creator can specify an exact version of registered ruleset to apply to their collections. If/when Limit Break updates ruleset bindings, collections that have specified an exact version of ruleset will not receive the update without manual action on their part.
Limit Break provides a convenient Developer Tools UI to configure validator settings. However, developers can apply desired settings by interacting directly with the contract on Etherscan, My Ether Wallet, Gnosis Safe, etc by calling the following list and collection management functions.
Collection-Specific Function Calls
/**
* @notice Set the ruleset id, global / ruleset options, fixed / custom ruleset for a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
* @dev Throws when setting a custom ruleset to an unregistered ruleset address.
* @dev Throws when setting a ruleset id that is not bound to a ruleset address.
* @dev Throws when setting a custom ruleset with a managed ruleset id.
*
* @dev <h4>Postconditions:</h4>
* 1. The ruleset of the specified collection is set to the new value.
* 2. Global options and ruleset-specific options of the specified collection are set to the new value.
* 3. A `SetCollectionRuleset` event is emitted.
* 4. A `SetCollectionSecurityPolicyOptions` event is emitted.
*
* @param collection The address of the collection.
* @param rulesetId The new ruleset id to apply.
* @param customRuleset The address of the custom ruleset to apply. Must be address(0) unless ruleset
* id is RULESET_ID_FIXED_OR_CUSTOM (255).
* @param globalOptions The global options to apply.
* @param rulesetOptions The ruleset-specific options to apply.
*/
function setRulesetOfCollection(address collection, uint8 rulesetId, address customRuleset, uint8 globalOptions, uint16 rulesetOptions) external;
/**
* @notice Applies the specified list to a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
* @dev Throws when the specified list id does not exist.
*
* @dev <h4>Postconditions:</h4>
* 1. The list of the specified collection is set to the new value.
* 2. An `AppliedListToCollection` event is emitted.
*
* @param collection The address of the collection.
* @param id The id of the operator whitelist.
*/
function applyListToCollection(address collection, uint48 id) external;Creating and Managing Custom Lists
/**
* @notice Creates a new list id. The list id is a handle to allow editing of blacklisted and whitelisted accounts
* and codehashes.
*
* @dev <h4>Postconditions:</h4>
* 1. A new list with the specified name is created.
* 2. The caller is set as the owner of the new list.
* 3. A `CreatedList` event is emitted.
* 4. A `ReassignedListOwnership` event is emitted.
*
* @param name The name of the new list.
* @return id The id of the new list.
*/
function createList(string calldata name) external returns (uint48 id);
/**
* @notice Creates a new list id, and copies all blacklisted and whitelisted accounts and codehashes from the
* specified source list.
*
* @dev <h4>Postconditions:</h4>
* 1. A new list with the specified name is created.
* 2. The caller is set as the owner of the new list.
* 3. A `CreatedList` event is emitted.
* 4. A `ReassignedListOwnership` event is emitted.
* 5. All blacklisted and whitelisted accounts and codehashes from the specified source list are copied
* to the new list.
* 6. An `AddedAccountToList` event is emitted for each blacklisted and whitelisted account copied.
* 7. An `AddedCodeHashToList` event is emitted for each blacklisted and whitelisted codehash copied.
*
* @param name The name of the new list.
* @param sourceListId The id of the source list to copy from.
* @return id The id of the new list.
*/
function createListCopy(string calldata name, uint48 sourceListId) external returns (uint48 id);
/**
* @notice Creates a new list id, and copies all accounts and codehashes from the
* specified source list for each specified list type.
*
* @dev <h4>Postconditions:</h4>
* 1. A new list with the specified name is created.
* 2. The caller is set as the owner of the new list.
* 3. A `CreatedList` event is emitted.
* 4. A `ReassignedListOwnership` event is emitted.
* 5. All accounts and codehashes from the specified source list / list types are copied
* to the new list.
* 6. An `AddedAccountToList` event is emitted for each account copied.
* 7. An `AddedCodeHashToList` event is emitted for each codehash copied.
*
* @param name The name of the new list.
* @param sourceListId The id of the source list to copy from.
* @param listTypes The list types to copy from the source list.
* @return id The id of the new list.
*/
function createListCopy(string calldata name, uint48 sourceListId, uint8[] calldata listTypes) external returns (uint48 id);
/**
* @notice Transfer ownership of a list to a new owner.
*
* @dev Throws when the new owner is the zero address.
* @dev Throws when the caller does not own the specified list.
*
* @dev <h4>Postconditions:</h4>
* 1. The list ownership is transferred to the new owner.
* 2. A `ReassignedListOwnership` event is emitted.
*
* @param id The id of the list.
* @param newOwner The address of the new owner.
*/
function reassignOwnershipOfList(uint48 id, address newOwner) external;
/**
* @notice Renounce the ownership of a list, rendering the list immutable.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when list id is zero (default list).
*
* @dev <h4>Postconditions:</h4>
* 1. The ownership of the specified list is renounced.
* 2. A `ReassignedListOwnership` event is emitted.
*
* @param id The id of the list.
*/
function renounceOwnershipOfList(uint48 id) external;
/**
* @notice Adds one or more accounts to a list of specified list type.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the accounts array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Accounts not previously in the list are added.
* 2. An `AddedAccountToList` event is emitted for each account that is newly added to the list.
*
* @param id The id of the list.
* @param listType The type of the list.
* @param accounts The addresses of the accounts to add.
*/
function addAccountsToList(uint48 id, uint8 listType, address[] calldata accounts) external;
/**
* @notice Removes one or more accounts from a list of the specified list type.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the accounts array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Accounts previously in the list are removed.
* 2. A `RemovedAccountFromList` event is emitted for each account that is removed from the list.
*
* @param id The id of the list.
* @param listType The type of the list.
* @param accounts The addresses of the accounts to remove.
*/
function removeAccountsFromList(uint48 id, uint8 listType, address[] calldata accounts) external;
/**
* @notice Adds one or more codehashes to a list of specified list type.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the codehashes array is empty.
* @dev Throws when a codehash is zero.
*
* @dev <h4>Postconditions:</h4>
* 1. Codehashes not previously in the list are added.
* 2. An `AddedCodeHashToList` event is emitted for each codehash that is newly added to the list.
*
* @param id The id of the list.
* @param listType The type of the list.
* @param codehashes The codehashes to add.
*/
function addCodeHashesToList(uint48 id, uint8 listType, bytes32[] calldata codehashes) external;
/**
* @notice Removes one or more codehashes from a list of the specified list type.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the codehashes array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Codehashes previously in the list are removed.
* 2. A `RemovedCodeHashFromList` event is emitted for each codehash that is removed from the list.
*
* @param id The id of the list.
* @param listType The type of the list.
* @param codehashes The codehashes to remove.
*/
function removeCodeHashesFromList(uint48 id, uint8 listType, bytes32[] calldata codehashes) external;Checking List and Collection Settings
/**
* @notice Returns the owner of the specified list id.
*/
function listOwners(uint48 id) external view returns (address);
/**
* @notice Get the security policy of the specified collection.
* @param collection The address of the collection.
* @return The security policy of the specified collection, which includes:
* Ruleset id, list id, global options, ruleset-specific options, optional custom ruleset address,
* and token type (if registered).
*/
function getCollectionSecurityPolicy(address collection) external view returns (CollectionSecurityPolicy memory);
/**
* @notice Get accounts by list id and list type.
* @param id The id of the list.
* @param listType The type of the list.
* @return An array of accounts in the list of the specified type.
*/
function getListAccounts(uint48 id, uint8 listType) external view returns (address[] memory);
/**
* @notice Get codehashes by list id and list type.
* @param id The id of the list.
* @param listType The type of the list.
* @return An array of codehashes in the list of the specified type.
*/
function getListCodeHashes(uint48 id, uint8 listType) external view returns (bytes32[] memory);
/**
* @notice Check if an account is found in a specified list id / list type.
* @param id The id of the list.
* @param listType The type of the list.
* @param account The address of the account to check.
* @return True if the account is in the specified list / type, false otherwise.
*/
function isAccountInList(uint48 id, uint8 listType, address account) external view returns (bool);
/**
* @notice Check if a codehash is in a specified list / type.
* @param id The id of the list.
* @param listType The type of the list.
* @param codehash The codehash to check.
* @return True if the codehash is in the specified list / type, false otherwise.
*/
function isCodeHashInList(uint48 id, uint8 listType, bytes32 codehash) external view returns (bool);
/**
* @notice Get accounts in list by collection and list type.
* @param collection The address of the collection.
* @param listType The type of the list.
* @return An array of accounts.
*/
function getListAccountsByCollection(address collection, uint8 listType) external view returns (address[] memory);
/**
* @notice Get codehashes in list by collection and list type.
* @param collection The address of the collection.
* @param listType The type of the list.
* @return An array of codehashes.
*/
function getListCodeHashesByCollection(address collection, uint8 listType) external view returns (bytes32[] memory);
/**
* @notice Check if an account is in the list by a specified collection and list type.
* @param collection The address of the collection.
* @param listType The type of the list.
* @param account The address of the account to check.
* @return True if the account is in the list / list type of the specified collection, false otherwise.
*/
function isAccountInListByCollection(address collection, uint8 listType, address account) external view returns (bool);
/**
* @notice Check if a codehash is in the list by a specified collection / list type.
* @param collection The address of the collection.
* @param listType The type of the list.
* @param codehash The codehash to check.
* @return True if the codehash is in the list / list type of the specified collection, false otherwise.
*/
function isCodeHashInListByCollection(address collection, uint8 listType, bytes32 codehash) external view returns (bool);To deploy the Transfer Validator on any EVM-equivalent chain, use the Limit Break Infrastructure Deployment tool.
As explained previously, different rulesets and ruleset options range from more permissive to less permissive. Depending upon the ruleset and options chosen, there can be workarounds implemented to attempt to bypass intended rules governing transfers. Defaults have been selected that strike the ideal balance of transfer security and ease of user experience. In case any of the rules are abused at scale, creators can update their ruleset and/or options to move to a higher level of security that thwarts more of these evasion techniques.
In the following tables below, a checkmark denotes that the ruleset is secure from techniques the can be used to bypass transfer rules.
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| None | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO1 | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO2 | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO1 / WLO2 | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO0 | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | ✔ | Limited | Limited | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO3 | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO1 / WLO3 | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO2 / WLO3 | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WL01 / WLO2 / WLO3 | ✔ | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO0 / WLO3 | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | ✔ | Limited | Limited | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO4 | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WL01 / WLO4 | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | Limited |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO2 / WLO4 | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO1 / WL02 / WLO4 | ✔ | ✔ | ✔ | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ |
| Option Bits Set | Allows EOA OTC | Allows 7702 Delegate OTC | Allows Smart Wallet OTC | Blocks Receivers With Code | Blocks Unverified EOA Receivers | Blocks Unwhitelist Operators |
|---|---|---|---|---|---|---|
| WLO0 / WLO4 | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | ✔ | Limited | Limited | ✔ | ✔ |
| Blocked Exchange | Pop-Up Exchange | Offline OTC | Escrow Contract | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | EIP-7702 | Batch Tx Stuffing |
|---|---|---|---|---|---|---|---|---|
| ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
As a method of last resort, creators can freeze an account to prevent all transfers to or from the address. This is intended to prevent malicious actors from circumventing any transfer rules the validators have set via unintended routes or exploits. Use of this feature is solely at the discretion of the token creator, and is disabled by default, requiring the token creator to opt in. This operation can be performed on the developer tools user interface, or using the following validator contract interfaces directly.
/**
* @notice Adds accounts to the frozen accounts list of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The accounts are added to the list of frozen accounts for a collection.
* 2. A `AccountFrozenForCollection` event is emitted for each account added to the list.
*
* @param collection The address of the collection.
* @param accountsToFreeze The list of accounts to added to frozen accounts.
*/
function freezeAccountsForCollection(address collection, address[] calldata accountsToFreeze) external;
/**
* @notice Removes accounts to the frozen accounts list of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The accounts are removed from the list of frozen accounts for a collection.
* 2. A `AccountUnfrozenForCollection` event is emitted for each account removed from the list.
*
* @param collection The address of the collection.
* @param accountsToUnfreeze The list of accounts to remove from frozen accounts.
*/
function unfreezeAccountsForCollection(address collection, address[] calldata accountsToUnfreeze) external;
/**
* @notice Get frozen accounts by collection.
* @param collection The address of the collection.
* @return An array of frozen accounts.
*/
function getFrozenAccountsByCollection(address collection) external view returns (address[] memory);
/**
* @notice Check if an account is frozen for a specified collection.
* @param collection The address of the collection.
* @param account The address of the account to check.
* @return True if the account is frozen by the specified collection, false otherwise.
*/
function isAccountFrozenForCollection(address collection, address account) external view returns (bool) {
return validatorStorage().frozenAccounts[collection].nonEnumerableAccounts[account];
}Integrators and other users can monitor updates to validator/collection settings through the following event interfaces.
It is especially important for exchange platform to monitor settings of collections, as changes to ruleset or options can block access to unwanted exchanges or invalidate certain marketplace orders, rendering them unfillable.
/// @dev Emitted when a delegated ruleset is registered.
event RulesetRegistered(address indexed delegatedRuleset);
/// @dev Emitted when a ruleset binding is set.
event RulesetBindingUpdated(uint8 indexed rulesetId, address indexed oldRuleset, address indexed newRuleset);/// @dev Emitted when a new list is created.
event CreatedList(uint256 indexed id, string name);
/// @dev Emitted when the ownership of a list is transferred to a new owner.
event ReassignedListOwnership(uint256 indexed id, address indexed newOwner);
/// @dev Emitted when an address is added to a list.
event AddedAccountToList(uint8 indexed kind, uint48 indexed id, address indexed account);
/// @dev Emitted when a codehash is added to a list.
event AddedCodeHashToList(uint8 indexed kind, uint48 indexed id, bytes32 indexed codehash);
/// @dev Emitted when an address is removed from a list.
event RemovedAccountFromList(uint8 indexed kind, uint48 indexed id, address indexed account);
/// @dev Emitted when a codehash is removed from a list.
event RemovedCodeHashFromList(uint8 indexed kind, uint48 indexed id, bytes32 indexed codehash);/// @dev Emitted when a list is applied to a collection.
event AppliedListToCollection(address indexed collection, uint48 indexed id);
/// @dev Emitted when the validation ruleset id and/or custom ruleset for a collection is updated.
event SetCollectionRuleset(address indexed collection, uint8 indexed rulesetId, address indexed customRuleset);
/// @dev Emitted when a collection's token type is updated.
event SetTokenType(address indexed collection, uint16 tokenType);
/// @dev Emitted when a collection's security policy bit options is updated.
event SetCollectionSecurityPolicyOptions(address indexed collection, uint8 globalOptions, uint16 rulesetOptions);
/// @dev Emitted when a collection's security policy expansion words are updated.
event SetCollectionExpansionWords(address indexed collection, bytes32 indexed key, bytes32 value);
/// @dev Emitted when a collection's security policy expansion datums are updated.
event SetCollectionExpansionDatums(address indexed collection, bytes32 indexed key, bytes value);/// @dev Emitted when an account is added to the list of frozen accounts for a collection.
event AccountFrozenForCollection(address indexed collection, address indexed account);
/// @dev Emitted when an account is removed from the list of frozen accounts for a collection.
event AccountUnfrozenForCollection(address indexed collection, address indexed account);