# Overview Limit Break has dedicated itself to building public goods that are pro-creator and pro-business to help the Web3 ecosystem thrive. To that end, Limit Break is pleased to announce the release of the Creator Advanced Protection Suite (CAPS). CAPS is a set of open-sourced, independent smart contract products that, when used properly, offer safeguards not only to token protocols but numerous other types of non-token protocols. Using CAPS, creators are able to carve out their own private on-chain ecosystem that is deployable on any EVM chain. - **Creator Token Standards** offers creator-defined guardrails to the transfer function of ERC20, ERC721, and ERC1155 tokens. In its simplest form, this allows creators to decide what protocols can be used to transfer NFTs and other coins, whether it be for trading, on-chain games, or other custom use cases involving the transfer of tokens. - **Payment Processor** offers the first NFT trading protocol that puts creators first over exchanges. Not only does the protocol guarantee payment of royalties in a dynamic manner, it puts the creator in control over how their collections trade. It even includes mechanisms that can align incentives between creators and exchanges, making for a more equitable relationship. - **TokenMaster** offers a next-generation fungible token protocol optimized for onchain consumer economies. It includes the ability to deploy, buy, sell and spend ERC20-C apptokens. - **Trusted Forwarder** offers an attribution system for open on-chain protocols with the option to include protection to gate-keep Web2 applications that are permitted to interact with on-chain protocols. Creator Token Contracts, Payment Processor, and Trusted Forwarder are all independent products, but they all work together in a layered approach to bestow creators with security for their on-chain works. The relationship between these products is depicted in the diagram below. To recap, creators can use CAPS to: - Block all protocols from interacting with your works without permission! - Launch coins and tokens with confidence! - Enforce royalty payments and/or coin-related fees! - Add application-level attribution to open, on-chain protocols! Want to learn the specifics? The following guides will walk you through everything you need to know to implement CAPS. - [Creator Token Standards](./creator-token-standards/overview) - [Payment Processor](./payment-processor/overview) - [TokenMaster](./token-master/overview) - [Trusted Forwarder](./trusted-forwarder/overview) ## Current Contract Addresses ### Public Infrastructure Contracts | Contract | Address | | ------------------------------------------------ | -------------------------------------------- | | Wrapped Native | `0x6000030000842044000077551D00cfc6b4005900` | | EOA Registry | `0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C` | | Creator Token Transfer Validator / Permit-C (v4) | `0x721C002B0059009a671D00aD1700c9748146cd1B` | | Trusted Forwarder Factory | `0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE` | | Collection Settings Registry (Payment Processor) | `0x9A1D001A842c5e6C74b33F2aeEdEc07F0Cb20BC4` | | Payment Processor Encoder (v3) | `0x9A1D00C3a699f491037745393a0592AC6b62421D` | | Payment Processor (v3) | `0x9a1D00000000fC540e2000560054812452eB5366` | | TokenMaster Router | `0x0E00009d00d1000069ed00A908e00081F5006008` | ### Internal Infrastructure Contracts | Contract | Address | | ---------------- | -------------------------------------------- | | Multisig Factory | `0x0000001Bd9DA29d107c93C8087Dd0365953DD0eA` | | Role Server | `0x00000000B77AE1Fe200078D60F382e962f472E23` | | Role Set Server | `0x00000000d7b37203F54e165Fb204B57c30d15835` | # Apptokens Cookbook Apptokens unlock an infinite number of new token types by adding programmable features into the token smart contract. With **apptokens**, developers have a complete suite of tools they need to design tokens that are functionally effective at serving all of their needs. This document aims to outline a list of potential new token types, their features, and the business problems they solve. The list is non-exhaustive as developers will continue to invent new designs, features, and primitives using **apptokens**. ## Categories 1. [Client-side Apptokens](#client-side-apptokens) 2. [Trade Apptokens](#trade-apptokens) 3. [Temporal Apptokens](#temporal-apptokens) 4. [Spend-only Apptokens](#spend-only-apptokens) 5. [Compliant Apptokens](#compliant-apptokens) 6. [Backed Apptokens](#backed-apptokens) --- ## Client-side Apptokens Client-side **apptokens** are designed to secure tokens into a **web2 client-side environment**. **Client-side apptokens** are available for trading and spending when users are interacting with them inside of a **developer-specified client environment**. ### What new opportunities do client-side apptokens unlock? 1. [Maximized token-driven user acquisition](#user-acquisition) 2. [Augmented quest/task completion incentives](#quests) 3. [Enhanced token security for developers and users](#security) 4. [Novel offerwall products generating ad revenue](#offerwall) --- # Maximize token-driven user acquisition ### How can client-side apptokens maximize conversion from token-driven user acquisition? Tokens are the most powerful source of traffic for crypto applications. Applications have been able to use tokens to grow rapidly at an extremely low cost relative to their web2 counterparts. **Client-side apptokens** provide an enormous upgrade to token-driven user acquisition by encoding the application sign-up/install requirement into the token contract itself. **Client-side apptokens** synchronize the demand for a token with the sign-ups/installs of an application, and maximize conversion from token demand to 100%. For example: # - There is enormous demand for a game developer's token launch. - The game developer releases their first title, and wishes to drive the demand for their token into their game. - The token is designed as a **client-side apptoken** and is available for purchase/claim inside of the game's application. - The token purchase/claim goes live, and all of the demand for the token is converted into game installs. # Augment quest/task completion incentives ### How can client-side apptokens boost quest/task completion incentives for applications? Token-driven incentives are often used by crypto applications in order to drive specific task completion. Applications will offer users the chance to win free tokens in order to incentivize them through a funnel of tasks. Often these tasks drive critical interactions for the application. **Client-side apptokens** can be used to incentivize these tasks natively in the token contract by requiring task completion in order to interact with a token. Currently, token incentive platforms struggle to enforce conversion of tasks as the task is disconnected from the token itself. For example: # - Game developer notices that once players hit level 5 of their game, their monthly retention rate triples. - Game developer wishes to use demand for an in-game token to drive players to hit the level 5 milestone. - The token is programmed as a **client-side apptoken** which requires players to complete level 5 of the game in order to purchase and trade the token. - All of the demand for the token is now converted into players completing level 5 of the game and the monthly retention rate of the game jumps. # Improved user acquisition with promotional tokens ### How can spend-only apptokens be used to improve user acquisition? **Promotional spend-only apptokens** are a novel and extremely cost-efficient method to execute **web3 native user acquisition**. These tokens are implemented to be used for a specific spend in a digital economy, and are then distributed by a developer as a means of acquiring users for their game and application. By distributing a **promotional spend-only apptoken**, a developer can give away something that new users value but that doesn't necessarily impact their broader asset economy. This is especially useful for developers with complex economies consisting of multiple apptoken types with different features. For example: # - Game developer has noticed that the most popular new user activity in their game is **character appearance customization**. - Developer designs a **promotional spend-only apptoken** which can be used to unlock new character skins. - Developer launches a campaign where existing players can generate referral links, and for every friend they sign up to the game they both receive the **promotional spend-only apptoken**. # Pure resource tokens for games ### How can spend-only apptokens be used to design pure resource tokens for games? **Spend-only apptokens** can be implemented to be linked to a specific spend or set of spends in a video game. This unlocks a new token type for web3 developers which is closer to how many resources in web2 video games operate. In a complex, multi-asset digital economy, not all assets need to be or should be tradable between players. The developer may wish to make certain assets **spend-only** as their data indicates that the earning and spending of that asset improves important metrics such as player retention. For example: # - Game developer notices that players who upgrade their initial gear from leather to wood are 80% more likely to return to play the game the next day. - Developer decides to implement the **$LEATHER** and **$WOOD** tokens as **spend-only resource apptokens** in order to encourage players who wish to upgrade their gear to earn the resources for themselves and spend them on their intended function. --- # Pure utility tokens for applications ### How can spend-only apptokens be used to design pure utility tokens for applications? **Spend-only apptokens** can be implemented to be linked to a specific spend or set of spends in an application. Application developers who wish to tightly bind tokens to utility and spends within their digital experience can now do so with **apptokens**. **Spend-only utility apptokens** are a powerful tool to focus user activity on critical feedback loops in the product. Before **spend-only apptokens** it was extremely difficult to create tight token:utility feedback loops due to the impact of open market trading. Now, the token is implemented with its intended utility natively in the smart contract. For example: # - Messaging app has a crypto economy natively built into the messaging experience. - Developer notices that users are much more likely to continue to use the messaging app if they purchase 5 sticker packs with the apptoken **$STICKER**. - Developer implements **$STICKER** as a **spend-only utility apptoken** which can be purchased from the application store or received as a promotional apptoken and then exclusively spent on sticker packs. --- Compliant Apptokens **Compliant apptokens** are a novel token type which are implemented by a developer with features that reflect a given compliance regime. This is an enormous unlock for developers looking to issue regulated assets on-chain, as a **compliant apptoken** can explicitly define and then comply with any theoretical compliance policy that a given asset type or regulatory regime demands. ### What new opportunities do compliant apptokens unlock? 1. [Comply with KYC requirements for asset ownership](#comply-with-kyc) 2. [Create complex permissions for asset ecosystem interactions](#create-complex-permissions) --- Comply with KYC requirements for asset ownership ### How can compliant apptokens be used to fulfill KYC requirements for asset ownership? Many assets in capital markets - particular real-world assets (RWAs) - have strict **KYC** requirements for ownership and trading. Before apptokens it was exceedingly difficult for blockchain-based digital assets to reflect **KYC** requirements due to limited features in the token smart contract. With apptokens, **KYC requirements** for any given asset in any given regime can be implemented natively into the token smart contract, enabling the compliant issuance and trading of regulated **RWAs** on-chain for the first time. For example: # - An equities broker notices that many of their clients have large crypto portfolios. - Broker decides to offer **tokenized securities** as a purchasable asset to its clients in order to tap their crypto liquidity. - Broker implements **tokenized securities** as **KYC apptokens** with native KYC-checks embedded into the smart contract at point of trade. # Create complex permissions for asset ecosystem interactions ### How can compliant apptokens be used to create complex permissions for asset ecosystem interactions? **Apptokens** can be used to synchronize compliance across any number of regulatory regimes simultaneously. For cases where assets have holders across multiple national/local jurisdictions, which each have unique compliance policies for a given asset type, a robust and extensive permissioning standard is required at the token smart contract level. For example: - An American business issues a **digital security**. - This digital security requires a complex, multi-jurisdiction compliance framework to be traded: - In the USA, only **accredited investors** can own the digital security. - In the EU, the asset must be **registered under MiCA**. - The asset must **not be traded by any individuals** from **OFAC** sanctioned jurisdictions. - etc. - The issuer of the digital security implements the **apptoken** as a **multi-KYC apptoken** to conform to all of these regulatory requirements simultaneously. ## Backed Apptokens **Backed apptokens** are a novel token type enabled by the combined utilization of the **ERC20-C apptokens standard** with the **TokenMaster protocol**. **Backed apptokens** are bought and sold through **TokenMaster pools** which store backed value. **Backed apptokens** can be reliably exchanged for their underlying value via the **TokenMaster pool** that launched the apptoken. ### What new opportunities do backed apptokens unlock? 1. [Launch tokens with price stability](#launch-tokens-with-price-stability) 2. [Monetize spends of backed apptokens](#monetize-spends) --- # Launch tokens with price stability ### How can backed apptokens be used to launch tokens with price stability? Utilizing the **TokenMaster protocol**, developers can launch **backed apptokens** in pools paired with other **ERC20 tokens** or **ERC20-C apptokens**. When the backed apptoken is purchased in exchange for the paired token, the paired token is deposited into the **TokenMaster pool** and the backed apptoken is minted. When the backed apptoken is sold back into the pool, the paired token is withdrawn and the backed apptoken is burnt. This dynamic allows developers to configure tokens with much more price stability than traditional ERC20 tokens. Developers can disable external **AMM** trading of the issued token. Developers can also configure fees that monetize trading activity in and out of **TokenMaster pools**. For example: - Developer wishes to issue an apptoken, **BKD**, with a fundamental value equivalent to **0.01 ETH**. - Developer launches a **TokenMaster pool** where **0.01 ETH** can be exchanged for **1 $BKD**. - Developer disables trading on **external AMMs**. - Developer collects a **1% fee** on every trade through the **TokenMaster pool**. - Now users who purchase **$BKD** receive a **backed apptoken** which can be exchanged for the underlying **ETH** at any time. Monetize spends of backed apptokens ### How can backed apptokens be used to monetize spends? **Backed apptokens** can be implemented with spends that burn the backed apptoken and transfer the backing to the developer. This is a critical piece of apptoken infrastructure for developers looking to generate direct revenue from their apptokens ecosystem. Now, developers can issue true value-backed apptokens and then monetize those tokens by connecting them directly to spends in their digital economies. For example: - **BKD** is a **backed apptoken** that is backed by **0.01 ETH** per **BKD**. - Developer creates an in-game spend where **BKD** can be exchanged for desirable cosmetics. - Users spend their **BKD** on cosmetics, and the underlying **ETH** is transferred to the developer. **Creator Token Standards** offers creator-defined guardrails to the transfer function of ERC20, ERC721, and ERC1155 tokens. In its simplest form, this allows creators to decide what protocols can be used to transfer NFTs and other coins, whether it be for trading, on-chain games, or other custom use cases involving the transfer of tokens. All common token protocols (ERC20, ERC721, and ERC1155) include some form of transfer function. The typical base implementation of each standard includes a before transfer hook and an after transfer hook. Creator tokens share a base contract that ties into these hooks and adds guardrails using an external transfer validation registry to apply a creator-defined set of rules allowing or blocking transfers based upon the caller, from, and to address. This allows creators to dictate what protocols may interact with token transfers for their own projects. Collection creators interact with the transfer validation registry to configure the ruleset for transfers, and they are free to change their ruleset at any time to escalate or de-escalate the security of their collections. Creators apply two settings to their collections: (1) the transfer security level to apply to the collection and (2) the list id to apply to the collection. To deploy a collection without writing code, or to update settings visit [developers.freenft.com](https://developers.freenft.com). # Creator Token Standards v4 # Overview ## 4.0.0 (2024-11-19) ### New Features * Support permissionless deployment of Creator Token Transfer Validator by any wallet on any EVM chain so it is freely deployable by the community. * Change collateral required for pausable Permit-C flags. 0.33 ETH on mainnet, 0.01 native tokens on all other chains. # Transfer Security Creator Token Standard contracts implement transfer security through the use of [Transfer Security Levels](#transfer-security-levels) and [Lists](#lists) giving creators full control over how their creations are distributed and used. ## Transfer Security Levels A collection's `Transfer Security Level` determines what restrictions are applied to transfers made by token owners. There are nine unique security levels with the default `Recommended` level being functionally equivalent to level `3` while optimizing contract initialization gas costs by leaving the storage slot as a zero value. | Level | List Type | OTC | Smart Contract Receivers | |-------------|-----------|---------|------------------------------------------| | Recommended | Whitelist | Allowed | Allowed | | 1 | None | Allowed | Allowed | | 2 | Blacklist | Allowed | Allowed | | 3 | Whitelist | Allowed | Allowed | | 4 | Whitelist | Blocked | Allowed | | 5 | Whitelist | Allowed | Blocked Using Code Length Check | | 6 | Whitelist | Allowed | Blocked Using EOA Signature Verification | | 7 | Whitelist | Blocked | Blocked Using Code Length Check | | 8 | Whitelist | Blocked | Blocked Using EOA Signature Verification | | 9 | None | Blocked | Blocked by restricting transfer | Transfer security settings are applied by a `Transfer Validator` contract. Limit Break provides a default transfer validator for use by collections. Creators *may* implement their own transfer validator however it is highly recommended to use the default for user experience and gas efficiency as it provides a single EOA Registry for all collections using Creator Token Standards and will minimize user friction on collections that apply security levels 6 or 8. Creators can manage their security settings at [https://developers.freenft.com](https://developers.freenft.com), with direct calls to their token contract (see [Token Contract Functions](#token-contract-functions)) or direct calls to the transfer validator (see [Transfer Contract Functions](#transfer-validator-functions)). ## List Type List types may be `None`, `Blacklist` or `Whitelist` and apply to the over-the-counter (OTC) transfer setting. - **None** is only applied to security level `1`. There are no filter lists applied to the transfer. - **Blacklist** is only applied to security level `2`. If the transaction caller's codehash or contract address has been added to the list the transaction will not be allowed. This type of list is easily bypassed by users that deploy the same contracts to new addresses or make small changes to the contract code to get a new codehash without significantly changing the contract logic. - **Whitelist** is applied to all other security levels. If the transaction caller's codehash or contract address has *not* been added to the list the transaction will not be allowed. This type of list allows a creator to specify exactly which contracts are allowed to transact items in the collection. ## Lists Lists contain contract addresses and codehashes curated by a list owner and are stored in the Transfer Validator that is utilized by the collection. Lists *may* be shared between collections. A list will be enforced during a token transfer transaction based on the security level set for the collection. ## OTC OTC may be set to `Allowed` or `Blocked` based on the security level selected. If OTC is `Allowed` then a token owner *may* transfer their tokens to another address without the transfer routing through another smart contract. If OTC is `Blocked` then a token owner's transfer *must* be routed through a smart contract. ## Smart Contract Receivers Smart Contract Receivers may be set to `Allowed` or `Blocked` with the blocked state enforcement set to `Code Length Check` or `EOA Signature Verification`. If smart contract receivers are `Allowed` then a token *may* be transferred to a smart contract. If smart contract receivers are `Blocked` then the transfer validator will check that the receiver is an EOA using contract code length or by requiring the receiver send a signed message to the EOA Registry to prove that their is a private key for the address. Code length checks may be bypassed by executing transactions from a smart contract's constructor code. EOA signature verification requires additional steps and a transaction by the receiver. ## Authorizers The Creator Token Transfer Validator now includes a new validation procedure, utilizing approved authorizers to review and approve transfers. Authorizers will provide a signature enabling transfers if the transaction meets certain criteria such as paying royalties, multi-hop transfers, or utilizing a limited smart contract system for gaming restraints. ## Account Freezing As a method of last resort, creators can freeze an account to prevent all transfers from the address. This is intended to prevent malicious actors from circumventing any transfer rules the validators have set via unintended routes or exploits. ## Mechanisms By Which Royalties Can Be Evaded (By Security Level) | Level | Blocked Exchange | Pop-Up Exchange | OTC / Escrow | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | |-------|------------------|-----------------|--------------|--------------------|---------------------------|----------------------------| | 1 | Yes | Yes | Yes | Yes | Yes | Yes | | 2 | No | Yes | Yes | Yes | Yes | Yes | | 3 | No | No | Yes | Yes | Yes | Yes | | 4 | No | No | No | Yes | Yes | Yes | | 5 | No | No | Yes | Limited | No | Yes | | 6 | No | No | Yes | No | No | Yes | | 7 | No | No | No | Limited | No | Limited | | 8 | No | No | No | No | No | Limited | | 9 | No | No | No | No | No | No | ## Transfer Security Functions Transfer security functions are located in two places for a token contract - the token contract itself and the transfer validator contract that the token contract has set as its transfer validator. ### Token Contract Functions The functions in this section are implemented in the token contract. #### setTransferValidator Callable by the token contract owner or any account that is assigned the default admin role to set the transfer validator to a new address. ```solidity function setTransferValidator(address transferValidator_) public; ``` ### Transfer Validator Functions The functions in this section are implemented in the transfer validator used by the token contract. #### createList Callable by any user to create a new list. ```solidity function createList(string calldata name) public returns (uint120); ``` #### createListCopy Callable by any user to create a new list copied from an existing list. ```solidity function createListCopy(string calldata name, uint120 sourceListId) external returns (uint120); ``` #### reassignOwnershipOfList Callable by the owner of a list to transfer ownership of the list to a new owner. ```solidity function reassignOwnershipOfList(uint120 id, address newOwner) public; ``` #### renounceOwnershipOfList Callable by the owner of a list to renounce ownership of the list and prevent further changes. ```solidity function renounceOwnershipOfList(uint120 id) public; ``` #### addAccountsToBlacklist Callable by the owner of a list to add the specified accounts to the list's blacklist. ```solidity function addAccountsToBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### addAccountsToWhitelist Callable by the owner of a list to add the specified accounts to the list's whitelist. ```solidity function addAccountsToWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### addCodeHashesToBlacklist Callable by the owner of a list to add the specified code hashes to the list's blacklist. ```solidity function addCodeHashesToBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### addCodeHashesToWhitelist Callable by the owner of a list to add the specified code hashes to the list's whitelist. ```solidity function addCodeHashesToWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### addAccountsToAuthorizers Callable by the owner of a list to add the specified authorizers to the list's approved authorizer addresses. ```solidity function addAccountsToAuthorizers( uint120 id, address[] calldata accounts ) external ``` #### removeAccountsFromBlacklist Callable by the owner of a list to remove the specified accounts from the list's blacklist. ```solidity function removeAccountsFromBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### removeAccountsFromWhitelist Callable by the owner of a list to remove the specified accounts from the list's whitelist. ```solidity function removeAccountsFromWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### removeCodeHashesFromBlacklist Callable by the owner of a list to remove the specified code hashes from the list's blacklist. ```solidity function removeCodeHashesFromBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeCodeHashesFromWhitelist Callable by the owner of a list to remove the specified code hashes from the list's whitelist. ```solidity function removeCodeHashesFromWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeAccountsFromAuthorizers Callable by the owner of a list to remove the specified authorizers from the list's approved authorizer addresses. ```solidity function removeAccountsFromAuthorizers( uint120 id, address[] calldata accounts ) external ``` #### setTransferSecurityLevelOfCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the token's transfer security level. The caller can also define authorizers, set if authorizers can enable wildcard allowances and enable freezing of accounts. ```solidity function setTransferSecurityLevelOfCollection( address collection, TransferSecurityLevels level, bool enableAuthorizationMode, bool authorizersCanSetWildcardOperators, bool enableAccountFreezingMode ) external; ``` #### applyListToCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the list to use when applying whitelist/blacklist transfer restrictions. ```solidity function applyListToCollection(address collection, uint120 id) public; ``` #### freezeAccountsForCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to freeze transferability for a specific set of addresses. ```solidity function freezeAccountsForCollection(address collection, address[] calldata accountsToFreeze) public; ``` #### unfreezeAccountsForCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to remove transfer restrictions from a specific set of addresses. ```solidity function unfreezeAccountsForCollection(address collection, address[] calldata accountsToUnfreeze) public; ``` # Example Contracts Tokens implementing the Creator Token Standards will inherit from the Limit Break provided abstract contract implementations in the Creator Token Standards repository found [here](https://github.com/limit-break-inc/creator-token-standards). Creators may add additional logic to their token implementation as they would with other token contracts for token minting, burning, metadata rendering or other necessary functions. Creators should be careful when overriding functions implemented by the Creator Token Standard implementation contracts, such as `_beforeTokenTransfer`, to ensure that the Creator Token Standard logic is also executed by calling `super.[overriden function]([function parameters])`. ## ERC721-C ```solidity import {ERC721C} from '@limitbreak/creator-token-standards/erc721c/ERC721C.sol'; contract MockERC721C is ERC721C { // Your contract logic } ``` ## ERC721A-C ```solidity import {ERC721AC} from '@limitbreak/creator-token-standards/erc721c/ERC721AC.sol'; contract MockERC721AC is ERC721AC { // Your contract logic } ``` ## ERC1155-C ```solidity import {ERC1155C} from '@limitbreak/creator-token-standards/erc1155c/ERC1155C.sol'; contract MockERC1155C is ERC1155C { // Your contract logic } ``` ## ERC20-C ```solidity import {ERC20C} from '@limitbreak/creator-token-standards/erc20c/ERC20C.sol'; contract MockERC20C is ERC20C { // Your contract logic } ``` # Events Creator Token contracts emit several indexible events that should be indexed by off-chain order books. Off-chain order books *SHOULD* index the following events for collections they provide listing services for so that the order book can apply the appropriate transfer validation logic to orders such as obtaining an EOA signature from the recipient and registering it with the transfer validator EOA registry or filtering orders that are unfillable based on security settings. `Token Contract Events` are emitted by the collection's smart contract. `Transfer Validator Events` are emitted by transfer validator contracts. A default transfer validator has been deployed that collections *SHOULD* use, however creators *MAY* deploy alternative transfer validators. ## Token Contract Events ```solidity event TransferValidatorUpdated( address oldValidator, address newValidator); ``` ## Transfer Validator Events ```solidity event SetTransferSecurityLevel( address indexed collection, TransferSecurityLevels level); event CreatedList( uint256 indexed id, string name); event AppliedListToCollection( address indexed collection, uint120 indexed id); event ReassignedListOwnership( uint256 indexed id, address indexed newOwner); event AddedAccountToList( ListTypes indexed kind, uint256 indexed id, address indexed account); event AddedCodeHashToList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event RemovedAccountFromList( ListTypes indexed kind, uint256 indexed id, address indexed account); event RemovedCodeHashFromList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event VerifiedEOASignature( address indexed account); event AppliedListToCollection( address indexed collection, uint120 indexed id); event AccountUnfrozenForCollection( address indexed collection, address indexed account); event SetAuthorizationModeEnabled( address indexed collection, bool enabled, bool authorizersCanSetWildcardOperators); event SetAccountFreezingModeEnabled( address indexed collection, bool enabled); ``` # Transfer Validation Off-chain order books should be aware of the transfer security level setting (see [Transfer Security](../for-creators/transfer-security)) for each collection they provide listing services for and apply filtering logic to hide listings that are unfillable. Situations where an off-chain order book will need to filter listings due to changes in transfer validation settings include: - No EOA Validation to EOA Validation - Security Level At Offer Creation: Recommended, 1, 2, 3, 4, 5, or 7 - New Security Level: 6 or 8 - Affected Orders: Offers by a maker where the beneficiary is not a verified EOA. - Action: Offer should be hidden until the beneficiary performs the [EOA Verification](eoa-verification) process. - Notification: Order book *should* notify the maker that they have orders that are unfillable pending action. - Smart Contract Receivers Allowed to Blocked - Security Level At Offer Creation: Recommended, 1, 2, 3, 4 - New Security Level: 5 or 7 - Affected Orders: Offers by a maker where the beneficiary has contract code. - Action: Offer should be hidden. - Notification: Order book *should* notify the maker that they have orders that are unfillable due to collection security settings. - Marketplace Contract Removed from Whitelist - Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for collections using the whitelist the marketplace contract was removed from. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is removed from a whitelist by the list owner or when a collection changes to a list that does not include the marketplace contract in its whitelist.* - Marketplace Contract Added to Blacklist - Security Level: 2 - Affected Orders: All orders for collections using the blacklist the marketplace contract was added to. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is added to a blacklist by the list owner or when a collection changes to a list that includes the marketplace contract in its blacklist.* - No List or Blacklist to Whitelist, Marketplace Contract Not On Whitelist - Security Level At Order Creation: 1, 2 - New Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - No List or Whitelist to Blacklist, Marketplace Contract On Blacklist - Security Level At Order Creation: Recommended, 1, 3, 4, 5, 6, 7, 8 - New Security Level: 2 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - Token updated to Soul Bound Token - Security Level: 9 - Affected Orders: All orders for the collection. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. # EOA Verification A standalone contract name the `EOA Registry` that allows for user addresses to submit a signed message verifying that there is a private key associated with the account and that it is not an undeployed contract address. Off-chain order books *SHOULD* be aware of collections they are servicing that have set a transfer security level requiring the token recipient to be a verified EOA and follow the steps below to verify the user address with the collection's transfer validator. 1. Determine if the user's EOA has already been verified with the collection's transfer validator. This may be through the off-chain order book's indexed event logs or by calling the `isVerifiedEOA(address)` function. If the EOA has been verified, no further action is required. 2. Request a wallet signature using the `personal_sign` method with the message `EOA`. 3. Call either the `verifySignature(bytes)` or `verifySignatureVRS(uint8, bytes32, bytes32)` function from the user's EOA with the signature data obtained in step 2. This transaction will be recorded on-chain and require the user to pay gas. The `verifySignatureVRS` function is slightly more gas optimized and should be the default method utilized. ## EOA Registry Functions ### verifySignature Function called by an EOA with their signature data, as a bytes array, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignature(bytes calldata signature) external; ``` ### verifySignatureVRS Function called by an EOA with their signature, split into `v`, `r`, and `s` values for gas efficiency, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignatureVRS(uint8 v, bytes32 r, bytes32 s) external; ``` ### isVerifiedEOA Function called to check if a specified `account` has previously verified with the EOA Registry. ```solidity function isVerifiedEOA(address account) public view override returns (bool); ``` # Contract Deployments Creator Token Transfer Validator v4.0.0 can be permissionlessly deployed by any wallet to the same addresses on any EVM compatible chain. #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Creator Token Transfer Validator V4.0.0 | `0x721C002B0059009a671D00aD1700c9748146cd1B` | | EOA Registry | `0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C` | ***Note: If Creator Token Transfer Validator is not yet deployed on your chain, use [the infrastructure deployment tool at developers.apptokens.com/infrastructure.](https://developers.apptokens.com/infrastructure)*** zkSync chains, including Abstract, are not fully EVM compatible to enable permissionless deployments. Please see the tables below for deployment addresses. #### Abstract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Creator Token Transfer Validator V4.0.0 | `0x3203c3f64312AF9344e42EF8Aa45B97C9DFE4594` | | EOA Registry | `0xdB8C854027A93F3f98300DE8ECcf4268De676160` | # Creator Token Standards v3 # Transfer Security Creator Token Standard contracts implement transfer security through the use of [Transfer Security Levels](#transfer-security-levels) and [Lists](#lists) giving creators full control over how their creations are distributed and used. ## Transfer Security Levels A collection's `Transfer Security Level` determines what restrictions are applied to transfers made by token owners. There are nine unique security levels with the default `Recommended` level being functionally equivalent to level `3` while optimizing contract initialization gas costs by leaving the storage slot as a zero value. | Level | List Type | OTC | Smart Contract Receivers | |-------------|-----------|---------|------------------------------------------| | Recommended | Whitelist | Allowed | Allowed | | 1 | None | Allowed | Allowed | | 2 | Blacklist | Allowed | Allowed | | 3 | Whitelist | Allowed | Allowed | | 4 | Whitelist | Blocked | Allowed | | 5 | Whitelist | Allowed | Blocked Using Code Length Check | | 6 | Whitelist | Allowed | Blocked Using EOA Signature Verification | | 7 | Whitelist | Blocked | Blocked Using Code Length Check | | 8 | Whitelist | Blocked | Blocked Using EOA Signature Verification | | 9 | None | Blocked | Blocked by restricting transfer | Transfer security settings are applied by a `Transfer Validator` contract. Limit Break provides a default transfer validator for use by collections. Creators *may* implement their own transfer validator however it is highly recommended to use the default for user experience and gas efficiency as it provides a single EOA Registry for all collections using Creator Token Standards and will minimize user friction on collections that apply security levels 6 or 8. Creators can manage their security settings at [https://developers.freenft.com](https://developers.freenft.com), with direct calls to their token contract (see [Token Contract Functions](#token-contract-functions)) or direct calls to the transfer validator (see [Transfer Contract Functions](#transfer-validator-functions)). ## List Type List types may be `None`, `Blacklist` or `Whitelist` and apply to the over-the-counter (OTC) transfer setting. - **None** is only applied to security level `1`. There are no filter lists applied to the transfer. - **Blacklist** is only applied to security level `2`. If the transaction caller's codehash or contract address has been added to the list the transaction will not be allowed. This type of list is easily bypassed by users that deploy the same contracts to new addresses or make small changes to the contract code to get a new codehash without significantly changing the contract logic. - **Whitelist** is applied to all other security levels. If the transaction caller's codehash or contract address has *not* been added to the list the transaction will not be allowed. This type of list allows a creator to specify exactly which contracts are allowed to transact items in the collection. ## Lists Lists contain contract addresses and codehashes curated by a list owner and are stored in the Transfer Validator that is utilized by the collection. Lists *may* be shared between collections. A list will be enforced during a token transfer transaction based on the security level set for the collection. ## OTC OTC may be set to `Allowed` or `Blocked` based on the security level selected. If OTC is `Allowed` then a token owner *may* transfer their tokens to another address without the transfer routing through another smart contract. If OTC is `Blocked` then a token owner's transfer *must* be routed through a smart contract. ## Smart Contract Receivers Smart Contract Receivers may be set to `Allowed` or `Blocked` with the blocked state enforcement set to `Code Length Check` or `EOA Signature Verification`. If smart contract receivers are `Allowed` then a token *may* be transferred to a smart contract. If smart contract receivers are `Blocked` then the transfer validator will check that the receiver is an EOA using contract code length or by requiring the receiver send a signed message to the EOA Registry to prove that their is a private key for the address. Code length checks may be bypassed by executing transactions from a smart contract's constructor code. EOA signature verification requires additional steps and a transaction by the receiver. ## Authorizers The Creator Token Transfer Validator now includes a new validation procedure, utilizing approved authorizers to review and approve transfers. Authorizers will provide a signature enabling transfers if the transaction meets certain criteria such as paying royalties, multi-hop transfers, or utilizing a limited smart contract system for gaming restraints. ## Account Freezing As a method of last resort, creators can freeze an account to prevent all transfers from the address. This is intended to prevent malicious actors from circumventing any transfer rules the validators have set via unintended routes or exploits. ## Mechanisms By Which Royalties Can Be Evaded (By Security Level) | Level | Blocked Exchange | Pop-Up Exchange | OTC / Escrow | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | |-------|------------------|-----------------|--------------|--------------------|---------------------------|----------------------------| | 1 | Yes | Yes | Yes | Yes | Yes | Yes | | 2 | No | Yes | Yes | Yes | Yes | Yes | | 3 | No | No | Yes | Yes | Yes | Yes | | 4 | No | No | No | Yes | Yes | Yes | | 5 | No | No | Yes | Limited | No | Yes | | 6 | No | No | Yes | No | No | Yes | | 7 | No | No | No | Limited | No | Limited | | 8 | No | No | No | No | No | Limited | | 9 | No | No | No | No | No | No | ## Transfer Security Functions Transfer security functions are located in two places for a token contract - the token contract itself and the transfer validator contract that the token contract has set as its transfer validator. ### Token Contract Functions The functions in this section are implemented in the token contract. #### setTransferValidator Callable by the token contract owner or any account that is assigned the default admin role to set the transfer validator to a new address. ```solidity function setTransferValidator(address transferValidator_) public; ``` ### Transfer Validator Functions The functions in this section are implemented in the transfer validator used by the token contract. #### createList Callable by any user to create a new list. ```solidity function createList(string calldata name) public returns (uint120); ``` #### createListCopy Callable by any user to create a new list copied from an existing list. ```solidity function createListCopy(string calldata name, uint120 sourceListId) external returns (uint120); ``` #### reassignOwnershipOfList Callable by the owner of a list to transfer ownership of the list to a new owner. ```solidity function reassignOwnershipOfList(uint120 id, address newOwner) public; ``` #### renounceOwnershipOfList Callable by the owner of a list to renounce ownership of the list and prevent further changes. ```solidity function renounceOwnershipOfList(uint120 id) public; ``` #### addAccountsToBlacklist Callable by the owner of a list to add the specified accounts to the list's blacklist. ```solidity function addAccountsToBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### addAccountsToWhitelist Callable by the owner of a list to add the specified accounts to the list's whitelist. ```solidity function addAccountsToWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### addCodeHashesToBlacklist Callable by the owner of a list to add the specified code hashes to the list's blacklist. ```solidity function addCodeHashesToBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### addCodeHashesToWhitelist Callable by the owner of a list to add the specified code hashes to the list's whitelist. ```solidity function addCodeHashesToWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### addAccountsToAuthorizers Callable by the owner of a list to add the specified authorizers to the list's approved authorizer addresses. ```solidity function addAccountsToAuthorizers( uint120 id, address[] calldata accounts ) external ``` #### removeAccountsFromBlacklist Callable by the owner of a list to remove the specified accounts from the list's blacklist. ```solidity function removeAccountsFromBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### removeAccountsFromWhitelist Callable by the owner of a list to remove the specified accounts from the list's whitelist. ```solidity function removeAccountsFromWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### removeCodeHashesFromBlacklist Callable by the owner of a list to remove the specified code hashes from the list's blacklist. ```solidity function removeCodeHashesFromBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeCodeHashesFromWhitelist Callable by the owner of a list to remove the specified code hashes from the list's whitelist. ```solidity function removeCodeHashesFromWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeAccountsFromAuthorizers Callable by the owner of a list to remove the specified authorizers from the list's approved authorizer addresses. ```solidity function removeAccountsFromAuthorizers( uint120 id, address[] calldata accounts ) external ``` #### setTransferSecurityLevelOfCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the token's transfer security level. The caller can also define authorizers, set if authorizers can enable wildcard allowances and enable freezing of accounts. ```solidity function setTransferSecurityLevelOfCollection( address collection, TransferSecurityLevels level, bool enableAuthorizationMode, bool authorizersCanSetWildcardOperators, bool enableAccountFreezingMode ) external; ``` #### applyListToCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the list to use when applying whitelist/blacklist transfer restrictions. ```solidity function applyListToCollection(address collection, uint120 id) public; ``` #### freezeAccountsForCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to freeze transferability for a specific set of addresses. ```solidity function freezeAccountsForCollection(address collection, address[] calldata accountsToFreeze) public; ``` #### unfreezeAccountsForCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to remove transfer restrictions from a specific set of addresses. ```solidity function unfreezeAccountsForCollection(address collection, address[] calldata accountsToUnfreeze) public; ``` # Example Contracts Tokens implementing the Creator Token Standards will inherit from the Limit Break provided abstract contract implementations in the Creator Token Standards repository found [here](https://github.com/limit-break-inc/creator-token-standards). Creators may add additional logic to their token implementation as they would with other token contracts for token minting, burning, metadata rendering or other necessary functions. Creators should be careful when overriding functions implemented by the Creator Token Standard implementation contracts, such as `_beforeTokenTransfer`, to ensure that the Creator Token Standard logic is also executed by calling `super.[overriden function]([function parameters])`. ## ERC721-C ```solidity import {ERC721C} from '@limitbreak/creator-token-standards/erc721c/ERC721C.sol'; contract MockERC721C is ERC721C { // Your contract logic } ``` ## ERC721A-C ```solidity import {ERC721AC} from '@limitbreak/creator-token-standards/erc721c/ERC721AC.sol'; contract MockERC721AC is ERC721AC { // Your contract logic } ``` ## ERC1155-C ```solidity import {ERC1155C} from '@limitbreak/creator-token-standards/erc1155c/ERC1155C.sol'; contract MockERC1155C is ERC1155C { // Your contract logic } ``` ## ERC20-C ```solidity import {ERC20C} from '@limitbreak/creator-token-standards/erc20c/ERC20C.sol'; contract MockERC20C is ERC20C { // Your contract logic } ``` # Transfer Validation Off-chain order books should be aware of the transfer security level setting (see [Transfer Security](../for-creators/transfer-security)) for each collection they provide listing services for and apply filtering logic to hide listings that are unfillable. Situations where an off-chain order book will need to filter listings due to changes in transfer validation settings include: - No EOA Validation to EOA Validation - Security Level At Offer Creation: Recommended, 1, 2, 3, 4, 5, or 7 - New Security Level: 6 or 8 - Affected Orders: Offers by a maker where the beneficiary is not a verified EOA. - Action: Offer should be hidden until the beneficiary performs the [EOA Verification](eoa-verification) process. - Notification: Order book *should* notify the maker that they have orders that are unfillable pending action. - Smart Contract Receivers Allowed to Blocked - Security Level At Offer Creation: Recommended, 1, 2, 3, 4 - New Security Level: 5 or 7 - Affected Orders: Offers by a maker where the beneficiary has contract code. - Action: Offer should be hidden. - Notification: Order book *should* notify the maker that they have orders that are unfillable due to collection security settings. - Marketplace Contract Removed from Whitelist - Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for collections using the whitelist the marketplace contract was removed from. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is removed from a whitelist by the list owner or when a collection changes to a list that does not include the marketplace contract in its whitelist.* - Marketplace Contract Added to Blacklist - Security Level: 2 - Affected Orders: All orders for collections using the blacklist the marketplace contract was added to. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is added to a blacklist by the list owner or when a collection changes to a list that includes the marketplace contract in its blacklist.* - No List or Blacklist to Whitelist, Marketplace Contract Not On Whitelist - Security Level At Order Creation: 1, 2 - New Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - No List or Whitelist to Blacklist, Marketplace Contract On Blacklist - Security Level At Order Creation: Recommended, 1, 3, 4, 5, 6, 7, 8 - New Security Level: 2 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - Token updated to Soul Bound Token - Security Level: 9 - Affected Orders: All orders for the collection. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. # Events Creator Token contracts emit several indexible events that should be indexed by off-chain order books. Off-chain order books *SHOULD* index the following events for collections they provide listing services for so that the order book can apply the appropriate transfer validation logic to orders such as obtaining an EOA signature from the recipient and registering it with the transfer validator EOA registry or filtering orders that are unfillable based on security settings. `Token Contract Events` are emitted by the collection's smart contract. `Transfer Validator Events` are emitted by transfer validator contracts. A default transfer validator has been deployed that collections *SHOULD* use, however creators *MAY* deploy alternative transfer validators. ## Token Contract Events ```solidity event TransferValidatorUpdated( address oldValidator, address newValidator); ``` ## Transfer Validator Events ```solidity event SetTransferSecurityLevel( address indexed collection, TransferSecurityLevels level); event CreatedList( uint256 indexed id, string name); event AppliedListToCollection( address indexed collection, uint120 indexed id); event ReassignedListOwnership( uint256 indexed id, address indexed newOwner); event AddedAccountToList( ListTypes indexed kind, uint256 indexed id, address indexed account); event AddedCodeHashToList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event RemovedAccountFromList( ListTypes indexed kind, uint256 indexed id, address indexed account); event RemovedCodeHashFromList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event VerifiedEOASignature( address indexed account); event AppliedListToCollection( address indexed collection, uint120 indexed id); event AccountUnfrozenForCollection( address indexed collection, address indexed account); event SetAuthorizationModeEnabled( address indexed collection, bool enabled, bool authorizersCanSetWildcardOperators); event SetAccountFreezingModeEnabled( address indexed collection, bool enabled); ``` # EOA Verification A standalone contract name the `EOA Registry` that allows for user addresses to submit a signed message verifying that there is a private key associated with the account and that it is not an undeployed contract address. Off-chain order books *SHOULD* be aware of collections they are servicing that have set a transfer security level requiring the token recipient to be a verified EOA and follow the steps below to verify the user address with the collection's transfer validator. 1. Determine if the user's EOA has already been verified with the collection's transfer validator. This may be through the off-chain order book's indexed event logs or by calling the `isVerifiedEOA(address)` function. If the EOA has been verified, no further action is required. 2. Request a wallet signature using the `personal_sign` method with the message `EOA`. 3. Call either the `verifySignature(bytes)` or `verifySignatureVRS(uint8, bytes32, bytes32)` function from the user's EOA with the signature data obtained in step 2. This transaction will be recorded on-chain and require the user to pay gas. The `verifySignatureVRS` function is slightly more gas optimized and should be the default method utilized. ## EOA Registry Functions ### verifySignature Function called by an EOA with their signature data, as a bytes array, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignature(bytes calldata signature) external; ``` ### verifySignatureVRS Function called by an EOA with their signature, split into `v`, `r`, and `s` values for gas efficiency, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignatureVRS(uint8 v, bytes32 r, bytes32 s) external; ``` ### isVerifiedEOA Function called to check if a specified `account` has previously verified with the EOA Registry. ```solidity function isVerifiedEOA(address account) public view override returns (bool); ``` # Contract Deployments #### Deployed Chains: - [Ethereum Mainnet](#ethereum) - [Base Mainnet](#base) - [Optimism Mainnet](#optimism) - [Arbitrum One Mainnet](#arbitrum) - [Polygon Mainnet](#polygon) - [Polygon zkEVM Mainnet](#polygon-zkevm) - [Avalanche C-Chain Mainnet](#avalanche-c-chain) - [Binance Smart Chain (BSC) Mainnet](#binance-smart-chain) - [Ethereum Sepolia Testnet](#ethereum-sepolia) #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Creator Token Transfer Validator V3 | `0x721C0078c2328597Ca70F5451ffF5A7B38D4E947` | | EOA Registry | `0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C` | ## Mainnet Deployments ### Ethereum - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://etherscan.io/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://etherscan.io/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Base - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://basescan.org/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://basescan.org/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Optimism - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://optimistic.etherscan.io/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - (0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C)[https://optimistic.etherscan.io/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C] ### Arbitrum - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://arbiscan.io/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://arbiscan.io/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Polygon - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://polygonscan.com/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://polygonscan.com/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Polygon zkEVM - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://zkevm.polygonscan.com/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://zkevm.polygonscan.com/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Binance Smart Chain - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://bscscan.com/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://bscscan.com/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ### Avalanche C-Chain - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://snowtrace.io/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://snowtrace.io/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) ## Testnet Deployments ### Ethereum Sepolia - Creator Token Transfer Validator V3 - [0x721C0078c2328597Ca70F5451ffF5A7B38D4E947](https://sepolia.etherscan.io/address/0x721C0078c2328597Ca70F5451ffF5A7B38D4E947) - EOA Registry - [0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C](https://sepolia.etherscan.io/address/0xE0A0004Dfa318fc38298aE81a666710eaDCEba5C) # Creator Token Standards v2 # Transfer Security Creator Token Standard contracts implement transfer security through the use of [Transfer Security Levels](#transfer-security-levels) and [Lists](#lists) giving creators full control over how their creations are distributed and used. ## Transfer Security Levels A collection's `Transfer Security Level` determines what restrictions are applied to transfers made by token owners. There are eight unique security levels with the default `Recommended` level being functionally equivalent to level `3` while optimizing contract initialization gas costs by leaving the storage slot as a zero value. | Level | List Type | OTC | Smart Contract Receivers | |-------------|-----------|---------|------------------------------------------| | Recommended | Whitelist | Allowed | Allowed | | 1 | None | Allowed | Allowed | | 2 | Blacklist | Allowed | Allowed | | 3 | Whitelist | Allowed | Allowed | | 4 | Whitelist | Blocked | Allowed | | 5 | Whitelist | Allowed | Blocked Using Code Length Check | | 6 | Whitelist | Allowed | Blocked Using EOA Signature Verification | | 7 | Whitelist | Blocked | Blocked Using Code Length Check | | 8 | Whitelist | Blocked | Blocked Using EOA Signature Verification | Transfer security settings are applied by a `Transfer Validator` contract. Limit Break provides a default transfer validator for use by collections. Creators *may* implement their own transfer validator however it is highly recommended to use the default for user experience and gas efficiency as it provides a single EOA Registry for all collections using Creator Token Standards and will minimize user friction on collections that apply security levels 6 or 8. Creators can manage their security settings at [https://developers.freenft.com](https://developers.freenft.com), with direct calls to their token contract (see [Token Contract Functions](#token-contract-functions)) or direct calls to the transfer validator (see [Transfer Contract Functions](#transfer-validator-functions)). ## List Type List types may be `None`, `Blacklist` or `Whitelist` and apply to the over-the-counter (OTC) transfer setting. - **None** is only applied to security level `1`. There are no filter lists applied to the transfer. - **Blacklist** is only applied to security level `2`. If the transaction caller's codehash or contract address has been added to the list the transaction will not be allowed. This type of list is easily bypassed by users that deploy the same contracts to new addresses or make small changes to the contract code to get a new codehash without significantly changing the contract logic. - **Whitelist** is applied to all other security levels. If the transaction caller's codehash or contract address has *not* been added to the list the transaction will not be allowed. This type of list allows a creator to specify exactly which contracts are allowed to transact items in the collection. ## Lists Lists contain contract addresses and codehashes curated by a list owner and are stored in the Transfer Validator that is utilized by the collection. Lists *may* be shared between collections. A list will be enforced during a token transfer transaction based on the security level set for the collection. ## OTC OTC may be set to `Allowed` or `Blocked` based on the security level selected. If OTC is `Allowed` then a token owner *may* transfer their tokens to another address without the transfer routing through another smart contract. If OTC is `Blocked` then a token owner's transfer *must* be routed through a smart contract. ## Smart Contract Receivers Smart Contract Receivers may be set to `Allowed` or `Blocked` with the blocked state enforcement set to `Code Length Check` or `EOA Signature Verification`. If smart contract receivers are `Allowed` then a token *may* be transferred to a smart contract. If smart contract receivers are `Blocked` then the transfer validator will check that the receiver is an EOA using contract code length or by requiring the receiver send a signed message to the EOA Registry to prove that their is a private key for the address. Code length checks may be bypassed by executing transactions from a smart contract's constructor code. EOA signature verification requires additional steps and a transaction by the receiver. ## Mechanisms By Which Royalties Can Be Evaded (By Security Level) | Level | Blocked Exchange | Pop-Up Exchange | OTC / Escrow | Wrapper Contracts | Trading Multi-Sig Wallets | Centralized Exchange w/EOA | |-------|------------------|-----------------|--------------|--------------------|---------------------------|----------------------------| | 1 | Yes | Yes | Yes | Yes | Yes | Yes | | 2 | No | Yes | Yes | Yes | Yes | Yes | | 3 | No | No | Yes | Yes | Yes | Yes | | 4 | No | No | No | Yes | Yes | Yes | | 5 | No | No | Yes | Limited | No | Yes | | 6 | No | No | Yes | No | No | Yes | | 7 | No | No | No | Limited | No | Limited | | 8 | No | No | No | No | No | Limited | ## Transfer Security Functions Transfer security functions are located in two places for a token contract - the token contract itself and the transfer validator contract that the token contract has set as its transfer validator. ### Token Contract Functions The functions in this section are implemented in the token contract. #### setToDefaultSecurityPolicy Callable by the token contract owner or any account that is assigned the default admin role to reset transfer security settings back to the Creator Token Standard defaults. ```solidity function setToDefaultSecurityPolicy() public; ``` #### setToCustomValidatorAndSecurityPolicy Callable by the token contract owner or any account that is assigned the default admin role to set a new transfer validator, transfer security level and specify a list for blacklist/whitelist restrictions. ```solidity function setToCustomValidatorAndSecurityPolicy( address validator, TransferSecurityLevels level, uint120 listId ) public; ``` #### setToCustomSecurityPolicy Callable by the token contract owner or any account that is assigned the default admin role to set the transfer security level and specify a list for blacklist/whitelist restrictions. ```solidity function setToCustomSecurityPolicy( TransferSecurityLevels level, uint120 listId ) public; ``` #### setTransferValidator Callable by the token contract owner or any account that is assigned the default admin role to set the transfer validator to a new address. ```solidity function setTransferValidator(address transferValidator_) public; ``` ### Transfer Validator Functions The functions in this section are implemented in the transfer validator used by the token contract. #### createList Callable by any user to create a new list. ```solidity function createList(string calldata name) public returns (uint120); ``` #### createListCopy Callable by any user to create a new list copied from an existing list. ```solidity function createListCopy(string calldata name, uint120 sourceListId) external returns (uint120); ``` #### reassignOwnershipOfList Callable by the owner of a list to transfer ownership of the list to a new owner. ```solidity function reassignOwnershipOfList(uint120 id, address newOwner) public; ``` #### renounceOwnershipOfList Callable by the owner of a list to renounce ownership of the list and prevent further changes. ```solidity function renounceOwnershipOfList(uint120 id) public; ``` #### addAccountsToBlacklist Callable by the owner of a list to add the specified accounts to the list's blacklist. ```solidity function addAccountsToBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### addAccountsToWhitelist Callable by the owner of a list to add the specified accounts to the list's whitelist. ```solidity function addAccountsToWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### addCodeHashesToBlacklist Callable by the owner of a list to add the specified code hashes to the list's blacklist. ```solidity function addCodeHashesToBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### addCodeHashesToWhitelist Callable by the owner of a list to add the specified code hashes to the list's whitelist. ```solidity function addCodeHashesToWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeAccountsFromBlacklist Callable by the owner of a list to remove the specified accounts from the list's blacklist. ```solidity function removeAccountsFromBlacklist( uint120 id, address[] calldata accounts ) external; ``` #### removeAccountsFromWhitelist Callable by the owner of a list to remove the specified accounts from the list's whitelist. ```solidity function removeAccountsFromWhitelist( uint120 id, address[] calldata accounts ) external; ``` #### removeCodeHashesFromBlacklist Callable by the owner of a list to remove the specified code hashes from the list's blacklist. ```solidity function removeCodeHashesFromBlacklist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### removeCodeHashesFromWhitelist Callable by the owner of a list to remove the specified code hashes from the list's whitelist. ```solidity function removeCodeHashesFromWhitelist( uint120 id, bytes32[] calldata codehashes ) external; ``` #### setTransferSecurityLevelOfCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the token's transfer security level. ```solidity function setTransferSecurityLevelOfCollection( address collection, TransferSecurityLevels level ) external; ``` #### applyListToCollection Callable by the token contract, owner of the token contract or any account that has been assigned the default admin role for the token contract to set the list to use when applying whitelist/blacklist transfer restrictions. ```solidity function applyListToCollection(address collection, uint120 id) public; ``` # Example Contracts Tokens implementing the Creator Token Standards will inherit from the Limit Break provided abstract contract implementations in the Creator Token Standards repository found [here](https://github.com/limit-break-inc/creator-token-standards). Creators may add additional logic to their token implementation as they would with other token contracts for token minting, burning, metadata rendering or other necessary functions. Creators should be careful when overriding functions implemented by the Creator Token Standard implementation contracts, such as `_beforeTokenTransfer`, to ensure that the Creator Token Standard logic is also executed by calling `super.[overriden function]([function parameters])`. ## ERC721-C ```solidity import {ERC721C} from '@limitbreak/creator-token-standards/erc721c/ERC721C.sol'; contract MockERC721C is ERC721C { // Your contract logic } ``` ## ERC721A-C ```solidity import {ERC721AC} from '@limitbreak/creator-token-standards/erc721c/ERC721AC.sol'; contract MockERC721AC is ERC721AC { // Your contract logic } ``` ## ERC1155-C ```solidity import {ERC1155C} from '@limitbreak/creator-token-standards/erc1155c/ERC1155C.sol'; contract MockERC1155C is ERC1155C { // Your contract logic } ``` ## ERC20-C ```solidity import {ERC20C} from '@limitbreak/creator-token-standards/erc20c/ERC20C.sol'; contract MockERC20C is ERC20C { // Your contract logic } ``` # Transfer Validation Off-chain order books should be aware of the transfer security level setting (see [Transfer Security](../for-creators/transfer-security)) for each collection they provide listing services for and apply filtering logic to hide listings that are unfillable. Situations where an off-chain order book will need to filter listings due to changes in transfer validation settings include: - No EOA Validation to EOA Validation - Security Level At Offer Creation: Recommended, 1, 2, 3, 4, 5, or 7 - New Security Level: 6 or 8 - Affected Orders: Offers by a maker where the beneficiary is not a verified EOA. - Action: Offer should be hidden until the beneficiary performs the [EOA Verification](eoa-verification) process. - Notification: Order book *should* notify the maker that they have orders that are unfillable pending action. - Smart Contract Receivers Allowed to Blocked - Security Level At Offer Creation: Recommended, 1, 2, 3, 4 - New Security Level: 5 or 7 - Affected Orders: Offers by a maker where the beneficiary has contract code. - Action: Offer should be hidden. - Notification: Order book *should* notify the maker that they have orders that are unfillable due to collection security settings. - Marketplace Contract Removed from Whitelist - Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for collections using the whitelist the marketplace contract was removed from. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is removed from a whitelist by the list owner or when a collection changes to a list that does not include the marketplace contract in its whitelist.* - Marketplace Contract Added to Blacklist - Security Level: 2 - Affected Orders: All orders for collections using the blacklist the marketplace contract was added to. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - *Note: This situation may occur when a marketplace contract is added to a blacklist by the list owner or when a collection changes to a list that includes the marketplace contract in its blacklist.* - No List or Blacklist to Whitelist, Marketplace Contract Not On Whitelist - Security Level At Order Creation: 1, 2 - New Security Level: Recommended, 3, 4, 5, 6, 7, 8 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. - No List or Whitelist to Blacklist, Marketplace Contract On Blacklist - Security Level At Order Creation: Recommended, 1, 3, 4, 5, 6, 7, 8 - New Security Level: 2 - Affected Orders: All orders for the collection using the marketplace contract. - Action: Orders should be hidden. - Notification: Order book *should* notify makers that they have orders that are unfillable due to collection security settings. # Events Creator Token contracts emit several indexible events that should be indexed by off-chain order books. Off-chain order books *SHOULD* index the following events for collections they provide listing services for so that the order book can apply the appropriate transfer validation logic to orders such as obtaining an EOA signature from the recipient and registering it with the transfer validator EOA registry or filtering orders that are unfillable based on security settings. `Token Contract Events` are emitted by the collection's smart contract. `Transfer Validator Events` are emitted by transfer validator contracts. A default transfer validator has been deployed that collections *SHOULD* use, however creators *MAY* deploy alternative transfer validators. ## Token Contract Events ```solidity event TransferValidatorUpdated( address oldValidator, address newValidator); ``` ## Transfer Validator Events ```solidity event SetTransferSecurityLevel( address indexed collection, TransferSecurityLevels level); event CreatedList( uint256 indexed id, string name); event AppliedListToCollection( address indexed collection, uint120 indexed id); event ReassignedListOwnership( uint256 indexed id, address indexed newOwner); event AddedAccountToList( ListTypes indexed kind, uint256 indexed id, address indexed account); event AddedCodeHashToList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event RemovedAccountFromList( ListTypes indexed kind, uint256 indexed id, address indexed account); event RemovedCodeHashFromList( ListTypes indexed kind, uint256 indexed id, bytes32 indexed codehash); event VerifiedEOASignature( address indexed account); ``` # EOA Verification Transfer validator contracts include an `EOA Registry` that allows for user addresses to submit a signed message verifying that there is a private key associated with the account and that it is not an undeployed contract address. Off-chain order books *SHOULD* be aware of collections they are servicing that have set a transfer security level requiring the token recipient to be a verified EOA and follow the steps below to verify the user address with the collection's transfer validator. 1. Determine if the user's EOA has already been verified with the collection's transfer validator. This may be through the off-chain order book's indexed event logs or by calling the `isVerifiedEOA(address)` function. If the EOA has been verified, no further action is required. 2. Request a wallet signature using the `personal_sign` method with the message `EOA`. 3. Call either the `verifySignature(bytes)` or `verifySignatureVRS(uint8, bytes32, bytes32)` function from the user's EOA with the signature data obtained in step 2. This transaction will be recorded on-chain and require the user to pay gas. The `verifySignatureVRS` function is slightly more gas optimized and should be the default method utilized. ## EOA Registry Functions ### verifySignature Function called by an EOA with their signature data, as a bytes array, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignature(bytes calldata signature) external; ``` ### verifySignatureVRS Function called by an EOA with their signature, split into `v`, `r`, and `s` values for gas efficiency, to verify their address is an EOA. The EOA Registry checks the signature against a predefined value and if it is valid updates the verified EOA mapping to mark the account as a verified EOA. ```solidity function verifySignatureVRS(uint8 v, bytes32 r, bytes32 s) external; ``` ### isVerifiedEOA Function called to check if a specified `account` has previously verified with the EOA Registry. ```solidity function isVerifiedEOA(address account) public view override returns (bool); ``` # Contract Deployments #### Deployed Chains: - [Ethereum Mainnet](#ethereum) - [Base Mainnet](#base) - [Optimism Mainnet](#optimism) - [Arbitrum One Mainnet](#arbitrum) - [Polygon Mainnet](#polygon) - [Polygon zkEVM Mainnet](#polygon-zkevm) - [Avalanche C-Chain Mainnet](#avalanche-c-chain) - [Binance Smart Chain (BSC) Mainnet](#binance-smart-chain) - [Ethereum Sepolia Testnet](#ethereum-sepolia) #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Creator Token Transfer Validator V2 | `0x721C00182a990771244d7A71B9FA2ea789A3b433` | ## Mainnet Deployments ### Ethereum - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://etherscan.io/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Base - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://basescan.org/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Optimism - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://optimistic.etherscan.io/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Arbitrum - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://arbiscan.io/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Polygon - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://polygonscan.com/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Polygon zkEVM - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://zkevm.polygonscan.com/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Binance Smart Chain - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://bscscan.com/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ### Avalanche C-Chain - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://snowtrace.io/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) ## Testnet Deployments ### Ethereum Sepolia - Creator Token Transfer Validator V2 - [0x721C00182a990771244d7A71B9FA2ea789A3b433](https://sepolia.etherscan.io/address/0x721C00182a990771244d7A71B9FA2ea789A3b433) # Payment Processor # Overview Payment Processor is a peer-to-peer NFT trading protocol. It is designed to serve as the blockchain execution layer for one or more off-chain order books and exchanges. In the simplest terms, an NFT orderbook can be thought of as an off-chain system with a minimum of two components: 1. Data store(s) for NFT order data and order maker signatures. 2. API(s) that provides secure read/write access to NFT order data. An NFT exchange is a user-facing interface where buyers and sellers can conduct NFT trading. An NFT exchange typically integrates with one or more NFT orderbook APIs to allow for order creation and order discoverability. NFT exchanges may be scoped to a single NFT collection, a single community consisting of related NFT collections, or a completely open platform for trading of all NFT collections. NFT exchanges run the gamut between a simple trading interface and a professional, feature-rich trading interface. Together, orderbooks and exchanges allow market makers to create liquidity in the form of listings and offers for NFTs. Market takers, then, may instantly execute and fill trades against order book liquidity in order to buy and sell. ## Limit Break's Position on Royalties Unlike other NFT trading protocols, NFT royalty enforcement is built directly into Payment Processor. Payment Processor will ***NEVER*** withhold creator royalties when they are configured on-chain. There are two ways creators can configure on-chain royalties on their collections that will be honored by Payment Processor. 1. Program royalties into the NFT smart contract using EIP-2981 2. Backfill royalties on Payment Processor for collections that lack EIP-2981 support By building NFTs using Limit Break's novel ERC721-C standard, creators have the power to guarantee that trades execute in the safety of the Payment Processor trading protocol where the creator, not the exchange, sets the rules for trading. *Note: To configure backfill royalties for collections that do not support EIP-2981 the collection must either be Ownable with the call to set royalties made by the owner or implement AccessControl with the call to set royalties made by an account with the default admin role.* *Note: If a collection does not implement EIP-2981, does not have an owner and does not have access control, royalties may still be set through an off-chain agreement with a marketplace with the marketplace using the fallback royalty recipient to deliver royalty payments to the creator.* ## Unparalleled Control For Creators Payment Processor protects the interests of creators, offering them choices no other exchange protocol has ever provided. ### Control Over Payment Methods and Pricing Historically, marketplaces have dictated the accepted payment methods for NFT trades. Payment Processor flips control to creators, putting them in the driver’s seat to set what methods of payment are permitted for trades on their collections. The spectrum of choices are: - **Default Payment Method Whitelist** — Rather than allow any payment method by default, all collections are automatically protected from trading in meme coin or other undesirable coins unless otherwise specified by the collection’s creator. This protects creators from trades where royalties are paid in worthless, undesirable coins. The defaults include: Native Currency (ETH or Equivalent), Wrapped Native Coin, Wrapped ETH (when applicable), USDC (Native), USDC (Bridged). - **Allow Any Payment Method** — For creators that wish to accept trades and royalties in any currency, the option to remove all restrictions is available. - **Custom Payment Method Whitelist** — Creators may define their own shared list of coins that may be used to trade their collections. This works great for Web3 games that have their own in-game currencies, as they can allow trading only in their game’s coin(s). - **Pricing Constraints** — Creators may choose a single payment method and apply a minimum floor or maximum ceiling price at the individual token id or collection level. This is great for Web3 games with special ultra-rare items as it offers some control over the economic balance of a game. ### Royalty Bounties Creators may choose to offer a royalty bounty. This is a percentage of royalties creators may choose to share with marketplaces. Royalty bounties are completely voluntary and set by a collection’s creator if they wish. Consider this simple scenario: An NFT trades for 1E where the creator royalty is 10%, royalty bounty is 20%, and marketplace fee = 1%. The creator receives 0.08E, the marketplace receives 0.03E (0.01E standard fee + 0.02E royalty bounty), and the seller receives 0.89E. Why would a creator volunteer to share royalties with a marketplace? Royalty bounties offer an incentive to align the interests of exchanges and creators. By offering a share of enforceable creator royalties to exchanges, or perhaps an exclusive partner exchange, it offers a new way to incentivize exchanges to cross-promote or boost the visibility of a collection for a period of time. ### Royalty Backfill Many older collections may not implement EIP-2981 royaltyInfo. Some of these collections have exchange blocking features, while others do not. Regardless of whether or not a collection can block exchanges and guarantee trades occur within Payment Processor, if a creator designates a backfilled royalty receiver and percentage for their collections it is guaranteed to be honored and enforced on all trades flowing through the Payment Processor. When combined with the power of ERC721-C NFTs with whitelisted transfers, the on-chain rules selected by creators cannot be circumvented!!! ### Trusted Channels Trusted channels allow exchanges to gain attribution for Payment Processor trades, where every exchange can create their own optional channel. Additionally, creators can choose whether or not to allow all channels or create an allow list of trusted channels. By using an allowlist of trusted channels, creators get access to controls to block undesirable Web2 applications from trading their work, which will be a popular feature for games. ## Continued Reading If you are a creator, configure your collection's Payment Processor trading settings by heading over to [https://developers.apptokens.com](https://developers.apptokens.com)! If you are an exchange, continue on to the [Exchange Integration Guide](./v3.0/for-exchanges/glossary). # Payment Processor V3 # Overview ## 3.0.0 (2024-11-19) ### New Features * Support permissionless deployment of Payment Processor by any wallet on any EVM chain so it is freely deployable by the community. * Add Limit Break's deterministic, permissionlessly deployable Wrapped Native token as a default payment method. * Added Bulk Order Signing - listings and bids may be bulk signed. A bulk listing or bulk bid may include up to 1024 individual orders signed by the maker with a single signature. * Added Permit-C Integration - sellers may optionally list NFTs using Permit-C approvals vs of `approvalForAll`. When accepting bids, the seller also has the option to sign a permit as part of the bid acceptance process or use `approvalForAll`. This can increase asset security for collectors. * Added ability for creators to specify royalty backfill as the primary source of royalties - this saves gas querying collections that do not implement 2981 royalties, and it allows collections that have an unused `royaltyInfo` implementation to collect royalties using the backfill instead. * Added Creator Settings Lazy Loading - from V3.0.0 onwards, creators will no longer have to apply their settings to every new Payment Processor instance, as they will be loaded one-time automatically from a settings registry upon the first trade on each collection. Lists such as custom payment method whitelists, trusted channel lists, and token-level pricing bounds are loaded and cached locally on an as-needed basis. * Add Tstorish re-entrancy guard for gas-cost effective, more comprehensive reentrancy protections while allowing down stream payment composability. This allowed removal of native push payment gas limits and gas limit overrides and also allowed native refunds to be securely issued directly instead of wrapping refunds. * Add mutable protocol fee that can be adjusted up or down by permissioned `FEE_MANAGER` role, based on market conditions or to initiate a migration to future versions of Payment Processor. Protocol fee versioning system allows for fee version grace periods to ensure orders are not instantly invalidated and that exchange integrators have built-in time to integrate new versions. * Unless otherwise adjusted after deployment, the protocol fees are initialized by default to 0.25% minimum protocol fee, 15% tax on exchange fees, and 25% tax on fee on top. When exchange fee tax is insufficient to cover the minimum, the shortfall is deducted from the seller's proceeds. When the exchange fee tax fully covers the minimum protocol fee, the seller's proceeds are unaffected. * Exchanges can stay current on protocol fees by subscribing to the new `ProtocolFeesUpdated` event. ### Interface Changes * Domain Separator has been removed from all trading function calldata. Instead, domain separator is looked up from a cached immutable variable, making trades more gas efficient. Marketplaces/exchanges should use the new v3.0.0 `PaymentProcessorEncoder` contract when generating calldata to ensure compatibility. * Added a collection-level only Pricing Constraints payment type. This saves gas for collections that do not use token-level min/max floors. * Remove banned accounts feature - this is moving to the core Creator Token Standards in order to persist across marketplace versions. * Calldata format has changed for all trading functions - use the new v3.0.0 `PaymentProcessorEncoder` to ensure compatibility. * Order digest for partial fills no longer includes the full EIP-712 type hash and domain separator. Instead, partially fillable ERC1155 orders are now tracked solely through the hash of the order data itself. * Replace `bool isCollectionLevelOrder` field in offers with `uint256 offerType` field, simplifying offer processing logic. * Remove `bytes32 rootHash` field from `TokenSetProof` struct, as it is more gas efficient to simply compute the `rootHash` during proof verification than passing it in via calldata. * Add `uint256 protocolFeeVersion` field to EIP-712 typed data hashes (`SALE_APPROVAL_HASH`, `PERMITTED_TRANSFER_SALE_APPROVAL`, `PERMITTED_ORDER_SALE_APPROVAL`, all `BULK_SALE_APPROVAL` type hashes). * Add `uint256 protocolFeeVersion` field to `Order` struct. * Cap item prices at type(uint240).max. This should not impact operations but allows more gas efficient math when computing payment splits. * ***Note: These breaking interface changes result in new trading function selectors and an update to calldata encoding, so use latest `PaymentProcessorEncoder`.*** ##### ***Payment Processor Encoder - Modified Functions*** * `encodeBuyListingCalldata` * `encodeAcceptOfferCalldata` * `encodeBulkBuyListingsCalldata` * `encodeBulkAcceptOffersCalldata` * `encodeSweepCollectionCalldata` ##### ***Payment Processor Encoder - Added Functions*** * `encodeBuyListingAdvancedCalldata` * `encodeAcceptOfferAdvancedCalldata` * `encodeBulkBuyListingsAdvancedCalldata` * `encodeBulkAcceptOffersAdvancedCalldata` * `encodeSweepCollectionAdvancedCalldata` ##### ***Payment Processor Encoder - Removed Functions*** * `encodeCreatePaymentMethodWhitelistCalldata` * `encodeReassignOwnershipOfPaymentMethodWhitelistCalldata` * `encodeRenounceOwnershipOfPaymentMethodWhitelistCalldata` * `encodeWhitelistPaymentMethodCalldata` * `encodeUnwhitelistPaymentMethodCalldata` * `encodeSetCollectionPaymentSettingsCalldata` * `encodeSetCollectionPricingBoundsCalldata` * `encodeSetTokenPricingBoundsCalldata` * `encodeAddTrustedChannelForCollectionCalldata` * `encodeRemoveTrustedChannelForCollectionCalldata` * `encodeAddBannedAccountForCollectionCalldata` * `encodeRemoveBannedAccountForCollectionCalldata` #### **EIP-712 Typehashes** The following type hashes were modified or added. ##### ***Modified Typehashes*** ```solidity // SALE_APPROVAL_HASH - Added protocol fee version field to be acknowledged by seller // v2.0.1 // 0x938786a8256d04dc45d6d5b997005aa07c0c9e3e4925d0d6c33128d240096ebc SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce) // v3.0.0 // 0x36e5aab0dde52c07206863cf8fe7b4b1a25be63a3ae5286f8fe80d159c154c4f SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion) ``` ##### ***Added Typehashes*** * Added PermitC compliant sale approval typehashes. See docs for details. * Added bulk order typehashes to support signing up to 1024 orders with a single signature. Read docs for details. #### **Events** The following events were modified, added or removed. ##### ***Modified Events*** ```solidity // OrderDigestOpened - Changed `orderStartAmount` from uint248 to uint256 // v2.0.1 event OrderDigestOpened( bytes32 indexed orderDigest, address indexed account, uint248 orderStartAmount); // v3.0.0 event OrderDigestOpened( bytes32 indexed orderDigest, address indexed account, uint256 orderStartAmount); // OrderDigestItemsFilled - Changed `amountFilled` from uint248 to uint256 // v2.0.1 event OrderDigestItemsFilled( bytes32 indexed orderDigest, address indexed account, uint248 amountFilled); // v3.0.0 event OrderDigestItemsFilled( bytes32 indexed orderDigest, address indexed account, uint256 amountFilled); // OrderDigestItemsRestored - Changed `amountRestoredToOrder` from uint248 to uint256 // v2.0.1 event OrderDigestItemsRestored( bytes32 indexed orderDigest, address indexed account, uint248 amountRestoredToOrder); // v3.0.0 event OrderDigestItemsRestored( bytes32 indexed orderDigest, address indexed account, uint256 amountRestoredToOrder); // OrderDigestItemsRestored - Replaced `blockBannedAccounts` with `useRoyaltyBackfillAsRoyaltySource` // v2.0.1 event UpdatedCollectionPaymentSettings( address indexed tokenAddress, PaymentSettings paymentSettings, uint32 indexed paymentMethodWhitelistId, address indexed constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool blockBannedAccounts); // v3.0.0 event UpdatedCollectionPaymentSettings( address indexed tokenAddress, PaymentSettings paymentSettings, uint32 indexed paymentMethodWhitelistId, address indexed constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool useRoyaltyBackfillAsRoyaltySource); ``` ##### ***Added Events*** ```solidity event PermittedOrderNonceInvalidated( uint256 indexed permitNonce, uint256 indexed orderNonce, address indexed account, bool wasCancellation); event ProtocolFeesUpdated( address indexed newProtocolFeeRecipient, uint16 minimumProtocolFeeBps, uint16 marketplaceFeeProtocolTaxBps, uint16 feeOnTopProtocolTaxBps, uint48 gracePeriodExpiration); event TrustedPermitProcessorAdded(address indexed permitProcessor); event TrustedPermitProcessorRemoved(address indexed permitProcessor); ``` ##### ***Removed Events*** ```solidity event BannedAccountAddedForCollection( address indexed tokenAddress, address indexed account); event BannedAccountRemovedForCollection( address indexed tokenAddress, address indexed account); ``` ### **Bug Fixes** * Fix burning of order nonce/partial fill order digest when NFT transfer fails. * Fix edge case caused by fallback functions. ### **Gas Optimizations** * Removed domain separator from calldata and used cached immutable domain separator instead. * Replace OpenZeppelin SafeERC20 library with new Limit Break ERC20 transfer library. * Refactor payments for fee on top to optimize payout flow for protocol fees. * Limit item price to `type(uint240).max` to allow for unchecked math in payment split computation. * Change diamond storage slot from the hash of a string value to `0x9A1D`. * Allow malleable signatures since order replay is prevented by order hash. * Replace standard ABI encoding/hashing with new Limit Break Efficient Hash library. * Remove token set proof root hash from calldata, calculate based on item + proof and validate with offer signature. * Change optimization runs from 30,000 to 40,000. * Other various gas optimizations for trading flows. # Settings Registry Payment Processor v3.0.0 introduces a new way of managing a collection's settings, through the `CollectionSettingsRegistry` contract (Settings Registry). Settings Registry is a central registry contract that will be shared across future versions of Payment Processor v3.X. The simplest way to manage your collection's settings is through [developers.apptokens.com](https://developers.apptokens.com). For creators that would like to build their own integrations for managing collection settings - the sections below describe the functions and parameters that Payment Processor uses to apply rules to collection orders. ### Account Freezing With the update to Payment Processor v3.0.0, the account freezing functions have been moved to the Creator Token Transfer Validator to prevent exploits via contracts or other forms of transfer validator circumvention. To set the frozen account list, see the documentation for [Creator Token Standards v3](../../../creator-token-standards/v3/for-creators/transfer-security#account-freezing). ## Settings Registry Functions ### createPaymentMethodWhitelist Callable by any account to create a new payment method whitelist. The whitelist will be owned by the address that calls the function. ```solidity /** * @notice Allows any user to create a new custom payment method whitelist. * * @dev

Postconditions:

* @dev 1. The payment method whitelist id tracker has been incremented by `1`. * @dev 2. The caller has been assigned as the owner of the payment method whitelist. * @dev 3. A `CollectionSettingsRegistry__CreatedPaymentMethodWhitelist` event has been emitted. * * @param whitelistName The name of the payment method whitelist. * @return paymentMethodWhitelistId The id of the newly created payment method whitelist. */ function createPaymentMethodWhitelist( string calldata whitelistName ) external returns (uint32 paymentMethodWhitelistId); ``` ### reassignOwnershipOfPaymentMethodWhitelist Callable by the owner of the whitelist to assign ownership of the whitelist to a new account. ```solidity /** * @notice Transfer ownership of a payment method whitelist 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

Postconditions:

* 1. The payment method whitelist list ownership is transferred to the new owner. * 2. A `ReassignedPaymentMethodWhitelistOwnership` event is emitted. * * @param id The id of the payment method whitelist. * @param newOwner The address of the new owner. */ function reassignOwnershipOfPaymentMethodWhitelist( uint32 id, address newOwner ) external; ``` ### renounceOwnershipOfPaymentMethodWhitelist Callable by the owner of the whitelist to renounce ownership of the whitelist. Whitelists with the owner renounced are immutable and can not be modified. ```solidity /** * @notice Renounce the ownership of a payment method whitelist, rendering the list immutable. * * @dev Throws when the caller does not own the specified list. * * @dev

Postconditions:

* 1. The ownership of the specified payment method whitelist is renounced. * 2. A `ReassignedPaymentMethodWhitelistOwnership` event is emitted. * * @param id The id of the payment method whitelist. */ function renounceOwnershipOfPaymentMethodWhitelist(uint32 id) external; ``` ### whitelistPaymentMethod Callable by the owner of the whitelist to add a new payment method. ```solidity /** * @notice Allows custom payment method whitelist owners to approve a new coin for use as a payment currency. * * @dev Throws when caller is not the owner of the specified payment method whitelist. * @dev Throws when the specified coin is already whitelisted under the specified whitelist id. * * @dev

Postconditions:

* @dev 1. `paymentMethod` has been approved in `paymentMethodWhitelist` mapping. * @dev 2. A `PaymentMethodAddedToWhitelist` event has been emitted. * * @param paymentMethodWhitelistId The id of the payment method whitelist to update. * @param paymentMethods The address of the payment method to whitelist. */ function whitelistPaymentMethod( uint32 paymentMethodWhitelistId, address[] calldata paymentMethods, address[] calldata paymentProcessorsToSync ) external; ``` ### unwhitelistPaymentMethod Callable by the owner of the whitelist to remove a payment method. ```solidity /** * @notice Allows custom payment method whitelist owners to remove a coin from the list of approved payment * currencies. * * @dev Throws when caller is not the owner of the specified payment method whitelist. * @dev Throws when the specified coin is not currently whitelisted under the specified whitelist id. * * @dev

Postconditions:

* @dev 1. `paymentMethod` has been removed from the `paymentMethodWhitelist` mapping. * @dev 2. A `CollectionSettingsRegistry__PaymentMethodRemovedFromWhitelist` event has been emitted. * * @param paymentMethodWhitelistId The id of the payment method whitelist to update. * @param paymentMethods The address of the payment method to unwhitelist. */ function unwhitelistPaymentMethod( uint32 paymentMethodWhitelistId, address[] calldata paymentMethods, address[] calldata paymentProcessorsToSync ) external; ``` ### syncRemovedPaymentMethodsFromWhitelist Callable by any user. This function will force a sync of the removed payment methods from a whitelist. ```solidity /** * @notice Forces sync of removed payment methods from the whitelist. This is unowned to allow for anyone to trigger updates of removed payment methods. * * @dev Leaving this unowned allows for anyone to trigger the removal of payment methods from the whitelist. * @dev If there is an issue with a payment method, this allows for a quick removal from all payment processors. * @dev Throws when the specified payment method is still whitelisted under the specified whitelist id. * * @dev

Postconditions:

* @dev 1. `paymentMethod` has been removed from each payment processor in the `paymentProcessorsToSync` array. * @dev 2. X * Y `PaymentMethodRemovedFromWhitelist` events have been emitted where X is the number of payment methods and Y is the number of payment processors to sync. * * @param paymentMethodWhitelistId The id of the payment method whitelist to update. * @param paymentMethods An array of payment method addresses to remove. * @param paymentProcessorsToSync An array of the payment processors to sync the removal with. */ function syncRemovedPaymentMethodsFromWhitelist( uint32 paymentMethodWhitelistId, address[] calldata paymentMethods, address[] calldata paymentProcessorsToSync ) external; ``` ### addTrustedPermitProcessors Callable by the owner of the default payment method whitelist to add a new permit processor instance. ```solidity /** * @notice Allows the trusted permit processor list owner to add a new trusted permit processor and sync. * * @dev Throws when caller is not the owner of the trusted permit processor list. * * @dev

Postconditions:

* @dev 1. `trustedPermitProcessor` has been approved in `_trustedPermitProcessors` list. * @dev 2. A `TrustedPermitProcessorAdded` event has been emitted. * * @param permitProcessors The addresses of trusted permit processors to add to the list. * @param paymentProcessorsToSync An array of the payment processors to sync the removal with. */ function addTrustedPermitProcessors( address[] calldata permitProcessors, address[] calldata paymentProcessorsToSync ) external; ``` ### removeTrustedPermitProcessors Callable by the owner of the default payment method whitelist to remove a permit processor instance. ```solidity /** * @notice Allows the trusted permit processor list owner to remove a trusted permit processor and sync. * * @dev Throws when caller is not the owner of the trusted permit processor list. * * @dev

Postconditions:

* @dev 1. `trustedPermitProcessor` has been removed from the `_trustedPermitProcessors` list. * @dev 2. A `TrustedPermitProcessorRemoved` event has been emitted. * * @param permitProcessors The addresses of trusted permit processors to remove from the list. * @param paymentProcessorsToSync An array of the payment processors to sync the removal with. */ function removeTrustedPermitProcessors( address[] calldata permitProcessors, address[] calldata paymentProcessorsToSync ) external; ``` ### setCollectionPaymentSettings Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function will configure the payment settings rules to apply to all orders executed on Payment Processor. ```solidity /** * @notice Allows the smart contract, the contract owner, or the contract admin of any NFT collection to * specify the payment settings for their collections. * * @dev Throws when the specified tokenAddress is address(0). * @dev Throws when the caller is not the contract, the owner or the administrator of the specified tokenAddress. * @dev Throws when the royalty backfill numerator is greater than 10,000. * @dev Throws when the royalty bounty numerator is greater than 10,000. * @dev Throws when the specified payment method whitelist id does not exist. * * @dev

Postconditions:

* @dev 1. The `PaymentSettings` type for the collection has been set. * @dev 2. The `paymentMethodWhitelistId` for the collection has been set, if applicable. * @dev 3. The `constrainedPricingPaymentMethod` for the collection has been set, if applicable. * @dev 4. The `royaltyBackfillNumerator` for the collection has been set. * @dev 5. The `royaltyBackfillReceiver` for the collection has been set. * @dev 6. The `royaltyBountyNumerator` for the collection has been set. * @dev 7. The `exclusiveBountyReceiver` for the collection has been set. * @dev 8. The `blockTradesFromUntrustedChannels` for the collection has been set. * @dev 8. An `UpdatedCollectionPaymentSettings` event has been emitted. * * @param tokenAddress The smart contract address of the NFT collection. * @param params The payment settings parameters for the collection. * @param dataExtensions The data extensions to set. * @param dataSettings The data settings to set. * @param wordExtensions The word extensions to set. * @param wordSettings The word settings to set. * @param paymentProcessorsToSync An array of the payment processors to sync the removal with. */ function setCollectionPaymentSettings( address tokenAddress, CollectionPaymentSettingsParams memory params, bytes32[] memory dataExtensions, bytes[] memory dataSettings, bytes32[] memory wordExtensions, bytes32[] memory wordSettings, address[] calldata paymentProcessorsToSync ) external; ``` ### setTokenPricingBounds Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function sets the floor and ceiling price for specific tokens ```solidity /** * @notice Allows the smart contract, the contract owner, or the contract admin of any NFT collection to * specify their own bounded price at the individual token level. * * @dev Throws when the specified tokenAddress is address(0). * @dev Throws when the caller is not the contract, the owner or the administrator of the specified tokenAddress. * @dev Throws when the lengths of the tokenIds and pricingBounds array don't match. * @dev Throws when the tokenIds or pricingBounds array length is zero. * @dev Throws when the any of the specified floor prices is greater than the ceiling price for that token id. * * @dev

Postconditions:

* @dev 1. The token-level pricing bounds for the specified tokenAddress and token ids has been set. * @dev 2. An `UpdatedTokenLevelPricingBoundaries` event has been emitted. * * @param tokenAddress The smart contract address of the NFT collection. * @param tokenIds An array of token ids for which pricing bounds are being set. * @param pricingBounds An array of pricing bounds used to set the floor and ceiling per token. * @param paymentProcessorsToSync An array of the payment processors to sync the removal with. */ function setTokenPricingBounds( address tokenAddress, uint256[] calldata tokenIds, RegistryPricingBounds[] calldata pricingBounds, address[] calldata paymentProcessorsToSync ) external; ``` ### addTrustedChannelForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function approves a series of channel to be allowed to execute orders for the collection when `blockTradesFromUntrustedChannels` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). See [Trusted Forwarder](../../../trusted-forwarder/overview) for more information on channels. ```solidity /** * @notice Allows trusted channels to be added to a collection. * * @dev Throws when the specified tokenAddress is address(0). * @dev Throws when the caller is not the contract, the owner or the administrator of the specified tokenAddress. * @dev Throws when the specified address is not a trusted forwarder. * * @dev

Postconditions:

* @dev 1. `channel` has been approved for trusted forwarding of trades on a collection. * @dev 2. A `TrustedChannelAddedForCollection` event has been emitted. * * @param tokenAddress The smart contract address of the NFT collection. * @param channels The smart contract address of the trusted channel. * @param paymentProcessorsToSync An array of the payment processors to sync the addition with. */ function addTrustedChannelForCollection( address tokenAddress, address[] calldata channels, address[] calldata paymentProcessorsToSync ) external; ``` ### removeTrustedChannelForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function removes a series of channels from the approval to execute orders for the collection when `blockTradesFromUntrustedChannels` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). See [Trusted Forwarder](../../../trusted-forwarder/overview) for more information on channels. ```solidity /** * @notice Allows trusted channels to be removed from a collection. * * @dev Throws when the specified tokenAddress is address(0). * @dev Throws when the caller is not the contract, the owner or the administrator of the specified tokenAddress. * * @dev

Postconditions:

* @dev 1. `channel` has been dis-approved for trusted forwarding of trades on a collection. * @dev 2. A `TrustedChannelRemovedForCollection` event has been emitted. * * @param tokenAddress The smart contract address of the NFT collection. * @param channels The smart contract address of the trusted channel to remove. * @param paymentProcessorsToSync The payment processors to sync with. */ function removeTrustedChannelForCollection( address tokenAddress, address[] calldata channels, address[] calldata paymentProcessorsToSync ) external; ``` ## Data Structures ### RegistryPricingBounds | isSet | floorPrice | ceilingPrice | |-------|------------|--------------| | bool | uint120 | uint120 | - isSet: Boolean true/false flag to indicate the pricing bounds have been set. Distinguishes set zero-value bounds from unset bounds that have default values of zero in the storage slot. - floorPrice: Minimum value to accept for a sales price without rejecting the sale. - ceilingPrice: Maximum value to accept for a sales price without rejecting the sale. ```solidity struct RegistryPricingBounds { bool isSet; uint120 floorPrice; uint120 ceilingPrice; } ``` ### CollectionPaymentSettingsParams |paymentSettings|paymentMethodWhitelistId|constrainedPricingPaymentMethod|royaltyBackfillNumerator|royaltyBackfillReceiver|royaltyBountyNumerator|exclusiveBountyReceiver|extraData| collectionMinimumFloorPrice|collectionMaximumCeilingPrice| |---------------|------------------------|-------------------------------|------------------------|-----------------------|----------------------|-----------------------|---------|---------------------------|-----------------------------| | uint8 | uint32 | address | uint16 | address | uint16 | address | uint16 | uint120 | uint120 | - `paymentSettings`: The payment settings for the collection. - `paymentMethodWhitelistId`: The ID of the payment method whitelist to use for the collection. - `constrainedPricingPaymentMethod`: The payment method to use for constrained pricing. - `royaltyBackfillNumerator`: The numerator to use for the royalty backfill. - `royaltyBackfillReceiver`: The address to use as the royalty backfill receiver. - `royaltyBountyNumerator`: The numerator to use for the royalty bounty. - `exclusiveBountyReceiver`: The address to use as the exclusive bounty receiver. - `extraData`: Extra data for the payment settings. The first 8 bits of this data contains the SettingsRegistry Flags. - `gasLimitOverride`: The gas limit override for the collection. - `collectionMinimumFloorPrice`: The minimum floor price for the collection. - `collectionMaximumCeilingPrice`: The maximum ceiling price for the collection. ### CollectionRegistryPaymentSettings | initialized | paymentSettingsType | paymentMethodWhitelistId | royaltyBackfillReceiver | royaltyBackfillNumerator | royaltyBountyNumerator | extraData | |-------------|---------------------|--------------------------|-------------------------|--------------------------|------------------------|-----------| | bool | uint8 | uint32 | address | uint16 | uint16 | uint16 | - `initialized`: True if the payment settings have been initialized, false otherwise. - `paymentSettingsType`: The type of payment settings for the collection. - `paymentMethodWhitelistId`: The ID of the payment method whitelist to use for the collection. - `royaltyBackfillReceiver`: The address to use as the royalty backfill receiver. - `royaltyBackfillNumerator`: The numerator to use for the royalty backfill. - `royaltyBountyNumerator`: The numerator to use for the royalty bounty. - `extraData`: Extra data for the payment settings. The first 8 bits of this data contains the SettingsRegistry Flags. ## Settings Registry Flags In Payment Processor V3.0.0, flags are used to efficiently store and retreive boolean values in a single uint8. These flags are stored in the `_collectionPaymentSettings` mapping as `CollectionRegistryPaymentSettings.extraData`. ```solidity uint8 constant FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE = 1 << 0; uint8 constant FLAG_BLOCK_TRADES_FROM_UNTRUSTED_CHANNELS = 1 << 1; uint8 constant FLAG_USE_BACKFILL_AS_ROYALTY_SOURCE = 1 << 2; ``` Flags are stored in a single uint8 which uninitialized looks like 0000000000000000. When utilizing flags, bit shifts are used to mask the value. For example, `FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE == 1 << 0` which would set the uint8 to 0000000000000001. To check the whether a flag is set, we can use bitwise AND (&). For example, if we want to check if `FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE` is set, you would use: `if (flags & FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE != 0)`. ### Exclusive Royalty Bounty Flag The exclusive royalty bounty flag is used to indicate whether an exclusive marketplace has been designated as eligible to receive a royalty bounty. If the flag is set, only the address eligible to receive a royalty bounty is the address set in `collectionExclusiveBountyReceivers[tokenAddress]`. ### Block Trades From Untrusted Channels Flag The block trades from untrested channels flag is used to indicate whether trades originating from non-permissioned channels should be reverted. If the flag is set, all trades will be checked against `collectionTrustedChannels[tokenAddress]`. If the originator is not registered as a trusted channel for the token contract address, the trade will be reverted. ### Use Backfill As Royalty Source Flag The use backfill as royalty source flag is used to indicate whether a royalty source should be backfilled. If a token contract is not ERC2981 compliant, creators can set backfilled royalty to ensure that royalties are honored on any trade flow that occurs through Payment Processor. # Glossary Here are some helpful terms to keep in mind. - **Payment Processor**: An NFT exchange protocol built by creators, for creators. Built for trading of ERC721-C and ERC1155-C tokens, but backwards compatible to support trading of any ERC721 or ERC1155 token as well. Analagous to Blur Marketplace and Seaport exchange protocols, but built entirely around honoring fully on-chain programmable royalties. Also known as `PaymentProcessor`. - **PaymentProcessor**: Shorthand for `Payment Processor`. - **Payment Processor Encoder**: A helper contract deployed alongside Payment Processor that integrated marketplaces use to format Payment Processor function calldata. - **Maker(s)**: Create buying or selling orders that are not carried out immediately. For example, "sell NFT `A` at a price of $100" or "buy NFT `B` for $100". This creates liquidity, meaning that it is easier for others to instantly buy or sell NFTs when the conditions are met. When Bob lists an NFT he owns for sale, Bob is considered maker of the order. Similarly, when Bob offers to buy an NFT, Bob is considered the maker of the order. - **Taker(s)**: The entities that buy or sell instantly are called takers. In other words, the takers fill the orders created by the makers. When Alice executes and order to buy Bob's listing, Alice is considered the taker of the order. Similarly, when Alice accepts an offer from Bob to buy an NFT she owns, Alice is considered the taker of the order. - **Order(s)**: Payment Processor supports two broad categories of orders and four types: - **Standard Order**: Any listing or offer that may be filled directly by a taker. Standard orders, when cancelled, must be cancelled on-chain by the order maker at their own gas expense. Standard orders are not susceptible to censorship provided they are accessible through an orderbook API. - **Cosigned Order**: Any listing or offer that must be cosigned by another party in order to be filled by a taker. Cosigned orders, when cancelled, can be gaslessly cancelled off-chain. Order-book providers that support cosigned orders must properly secure and automate the co-signing process. The order-book provider must guarantee that no cosignature can be generated for a previously cancelled order. Similarly, the order-book provider must guarantee availability of cosignatures to takers such that orders can always be filled uninterrupted. The use of cosigned orders SHOULD be at the order maker's discretion. Pros include cheaper, faster cancellation/replacement of orders without incurring transaction gas costs. Cons include censorship/denial of service risks. - **Listing**: An order (standard or cosigned) to sell an NFT once filled by a taker. Listings are gaslessly signed off-chain by the owner of the NFT. - **Item Offer**: An order (standard or cosigned) to buy a single, specific NFT once filled by a taker. Item offers apply to exactly one specific token id in a collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing the NFT. - **Collection Offer**: An order (standard or cosigned) to buy any NFT belonging to a specific collection once filled by a taker. Collection offers apply to any token id for a specific collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing an NFT. - **Token Set Offer**: An order (standard or cosigned) to buy a single NFT from a subset of token ids in a specific collection once filled by a taker. Token set offers apply to a subset of token ids for a specific collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing an NFT. - **Beneficiary**: The address of the account that receives the NFT/item when an order is filled. The buyer and beneficiary can be, but don't have to be the same account. Beneficiaries are acknowledged and signed in offer orders when an offer is made. Alternately, the beneficiary is designated and signed in the calldata of the buy transaction when the taker buys a listing. - **Marketplace Fee**: A fee paid to the primary marketplace where an order was made. The marketplace fee is acknowledged in signed orders. - **Fee On Top**: A `fee on top` is typically reserved for a marketplace or specialized wallet that finds orders filled by the taker. This taker marketplace fee is an optional fee paid by the taker in excess of the items' prices in one or more orders. - **Royalty Bounty**: A percentage, optionally paid out of a creator's royalty on each trade, to the marketplace where the NFT order was made. Royalty bounties are optional, and creators can configure a percentage from 0-100% to share with marketplaces that provide liquidity. Royalty bounties are a powerful tool to align incentives between creators and marketplaces. - **Bulk Orders**: A signature provided by a maker which includes multiple approvals, confirmed by signing the root of a merkle tree and executed by takers who provide merkle proofs. - **Permit Orders**: An order which utilizes `PermitC` or other similar permit processors to handle approvals for ERC20, ERC721 and ERC1155 as well as validate additional data. - **Minimum Protocol Fee**: The minimum fee rate (BPS) collected by the Payment Processor procotocol. Default is 25 BPS (0.25%) unless otherwise configured/adjusted. - **Protocol Exchange Fee Tax**: The rate at which the marketplace/exchange fee is taxed. This is a tax the marketplace pays for the right to monetize trading using the Payment Processor protocol and is used to fund ongoing protocol development and enhancements. Protocol fees collected this way may fully or partially offset the minimum protocol fee to limit the excess fee applied to the seller's proceeds. - **Protocol Fee On Top Tax**: The rate at which the fee on top is taxed. This is a tax the app injecting a fee on top pays for the right to monetize trading using the Payment Processor protocol and is used to fund ongoing protocol development and enhancements. - **Protocol Fee Version**: If the protocol fee is adjusted by the fee manager, it creates a new version. Traders must acknowledge the protocol fee version in their sale approval signatures or their signed transaction to sell an item. Protocol fee versions have a grace period so that existing orders can be filled before they become invalidated and require new orders to use the latest fee version. # Events ## **Collection Settings Registry Events** The following events are emitted by the Collection Settings Registry used by creators to configure their collection trading settings. ### ***Payment Methods and Settings Events*** ```solidity /// @notice Emitted when a new payment method whitelist is created event CreatedPaymentMethodWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed whitelistOwner, string whitelistName); /// @notice Emitted when a coin is added to the approved coins mapping for a whitelist event PaymentMethodAddedToWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); /// @notice Emitted when a coin is removed from the approved coins mapping for a whitelist event PaymentMethodRemovedFromWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); /// @notice Emitted when a payment method whitelist is reassigned to a new owner event ReassignedPaymentMethodWhitelistOwnership( uint32 indexed id, address indexed newOwner); /// @notice Emitted when payment settings are updated for a collection event UpdatedCollectionPaymentSettings( address indexed tokenAddress, CollectionPaymentSettingsParams params); /// @notice Emitted whenever pricing bounds change at a token level for price-constrained collections. event UpdatedTokenLevelPricingBoundaries( address indexed tokenAddress, uint256 indexed tokenId, uint256 floorPrice, uint256 ceilingPrice); ``` ### ***Trusted Channel Events*** ```solidity /// @notice Emitted when a trusted channel is added for a collection event TrustedChannelAddedForCollection( address indexed tokenAddress, address indexed channel); /// @notice Emitted when a trusted channel is removed for a collection event TrustedChannelRemovedForCollection( address indexed tokenAddress, address indexed channel); ``` ### ***Permit Processor Events*** ```solidity /// @notice Emitted when a permit processor is added to the trusted permit processor list. event TrustedPermitProcessorAdded(address indexed permitProcessor); /// @notice Emitted when a permit processor is removed from the trusted permit processor list. event TrustedPermitProcessorRemoved(address indexed permitProcessor); ``` ## **Payment Processor Events** Payment Processor emits several indexible events that should be indexed by off-chain order books and analytics platforms. ### ***Fill Or Kill Trade Events*** Off-chain order books and analytics platforms can track the following events for trading volume and metrics, as well as trades by user. ```solidity /// @notice Emitted when an ERC721 listing is purchased. event BuyListingERC721( address indexed buyer, address indexed seller, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 salePrice); /// @notice Emitted when an ERC1155 listing is purchased. event BuyListingERC1155( address indexed buyer, address indexed seller, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 amount, uint256 salePrice); /// @notice Emitted when an ERC721 offer is accepted. event AcceptOfferERC721( address indexed seller, address indexed buyer, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 salePrice); /// @notice Emitted when an ERC1155 offer is accepted. event AcceptOfferERC1155( address indexed seller, address indexed buyer, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 amount, uint256 salePrice); ``` ### ***Partial Fill Trade Events*** ```solidity /// @notice Emitted the first time a partially fillable 1155 order has items filled on-chain. event OrderDigestOpened( bytes32 indexed orderDigest, address indexed account, uint256 orderStartAmount); /// @notice Emitted when a user fills items on a partially fillable 1155 listing. event OrderDigestItemsFilled( bytes32 indexed orderDigest, address indexed account, uint256 amountFilled); ``` ### ***Order Cancellation Events*** Off-chain order books SHOULD index the following events to track what orders have been filled or cancelled and which nonces are un-available. ```solidity /// @notice Emitted when a user revokes all of their existing listings or offers that share the master nonce. event MasterNonceInvalidated( uint256 indexed nonce, address indexed account); /// @notice Emitted when a user revokes a single listing or offer nonce for a specific marketplace, or the order /// filled successfully. event NonceInvalidated( uint256 indexed nonce, address indexed account, bool wasCancellation); /// @notice Emitted when a user fills a single listing or offer nonce but item fails to transfer/fill. event NonceRestored( uint256 indexed nonce, address indexed account); /// @notice Emitted when a user revokes a single permitted listing nonce, or the order filled successfully. event PermittedOrderNonceInvalidated( uint256 indexed permitNonce, uint256 indexed orderNonce, address indexed account, bool wasCancellation); /// @notice Emitted when a user revokes a single partially fillable 1155 listing or offer for a specific marketplace, /// or a partially fillable order has been completely filled. event OrderDigestInvalidated( bytes32 indexed orderDigest, address indexed account, bool wasCancellation); /// @notice Emitted when a user fills a partially fillable 1155 listing or offer but item fails to transfer/fill. event OrderDigestItemsRestored( bytes32 indexed orderDigest, address indexed account, uint256 amountRestoredToOrder); ``` ### ***Cosigner Events*** Off-chain order books SHOULD index the following event to track when a cosigner has been destroyed and filter any open orders that require a cosignature from the cosigner. ```solidity /// @notice Emitted when a cosigner destroys itself. event DestroyedCosigner(address indexed cosigner); ``` ### ***Collection Payment Settings Events*** Off-chain order books SHOULD index collection payment settings events to track allowed payment methods, backfilled royalties, and royalty bounties. ```solidity /// @notice Emitted when payment settings for a collection are updated. event UpdatedCollectionPaymentSettings( address indexed tokenAddress, PaymentSettings paymentSettings, uint32 indexed paymentMethodWhitelistId, address indexed constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool useRoyaltyBackfillAsRoyaltySource); ``` ### ***Custom Payment Method Whitelist Events*** Off-chain order books SHOULD index payment method whitelists by id. By doing so, when a collection applies a custom payment method whitelist id, exchanges can be informed about which payment methods are allowed for a give collection. ```solidity /// @notice Emitted when a coin is added to the approved coins mapping for a whitelist event PaymentMethodAddedToWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); /// @notice Emitted when a coin is removed from the approved coins mapping for a whitelist event PaymentMethodRemovedFromWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); ``` ### ***Bounded Pricing Events*** Off-chain order books SHOULD index pricing bounds events to track min and max allowable sale prices for collections that implement pricing constraints. ```solidity /// @notice Emitted whenever pricing bounds change at a collection level for price-constrained collections. event UpdatedCollectionLevelPricingBoundaries( address indexed tokenAddress, uint256 floorPrice, uint256 ceilingPrice); /// @notice Emitted whenever pricing bounds change at a token level for price-constrained collections. event UpdatedTokenLevelPricingBoundaries( address indexed tokenAddress, uint256 indexed tokenId, uint256 floorPrice, uint256 ceilingPrice); ``` ### ***Trusted Channel Events*** Off-chain order books SHOULD index trusted channel events to be used in conjunction with the [`Collection Payment Settings Events`](#collection-payment-settings-events) to determine the allowed channels for a collection if `blockTradesFromUntrustedChannels` is set to `true`. ```solidity /// @notice Emitted when a trusted channel is added for a collection event TrustedChannelAddedForCollection( address indexed tokenAddress, address indexed channel); /// @notice Emitted when a trusted channel is removed for a collection event TrustedChannelRemovedForCollection( address indexed tokenAddress, address indexed channel); ``` ### ***Permit Processor Events*** ```solidity /// @notice Emitted when a permit processor is added to the trusted permit processor list. event TrustedPermitProcessorAdded(address indexed permitProcessor); /// @notice Emitted when a permit processor is removed from the trusted permit processor list. event TrustedPermitProcessorRemoved(address indexed permitProcessor); ``` ### ***Protocol Fee Events*** ```solidity /// @notice Emitted when protocol fees are updated. event ProtocolFeesUpdated( address indexed newProtocolFeeRecipient, uint16 minimumProtocolFeeBps, uint16 marketplaceFeeProtocolTaxBps, uint16 feeOnTopProtocolTaxBps, uint48 gracePeriodExpiration); ``` # Data Structures The following subsections detail the data structures needed for the creation of and filling of orders in Payment Processor. Note that maker signature formats vary by order-type. ## ***SignatureECDSA*** | V | R | S | |---------|---------|---------| | uint256 | bytes32 | bytes32 | - V: The `v` component of an ECDSA signature. - R: The `r` component of an ECDSA signature. - S: The `s` component of an ECDSA signature. *[Note: For a detailed explanation of ECDSA signatures read this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).* ## ***Cosignature*** | Signer | Taker | Expiration | V | R | S | |---------|---------|------------|---------|---------|---------| | address | address | uint256 | uint256 | bytes32 | bytes32 | - Signer: The co-signer designated and acknowledged in the order maker's signature approving the order. MUST be an EOA, or `address(0)` when the co-signature is empty. It is expected that marketplaces implementing co-signing MUST expose an API enabling the taker to retrieve a co-signature approving the fill of co-signed orders. - Taker: The address of the account filling the co-signed order. May be an EOA or Smart Contract account, or `address(0)` when the co-signature is empty. A marketplace's co-signing API service MUST acknowledge the taker address in the co-signature. - Expiration: The unix timestamp (in seconds) when the co-signature expires, or `0` when the co-signature is empty. A marketplace's co-signing API should use the current unix timestamp and add a validity time that is acknowledged in the co-signature. If the validity time is too short, there might not be sufficient time for the fill transaction to be confirmed on-chain, possibly resulting in failed fill transactions. However, the validity time should also not be too long, as a taker could sit on a fill transaction for quite a while. It is left up to the marketplace to determine an appropriate validity time. - V: The `v` component of the co-signer's signature. - R: The `r` component of the co-signer's signature. - S: The `s` component of the co-signer's signature. *Note: The co-signature is generated when the co-signer creates an ECDSA signature of the Cosignature typed data message.* ## ***FeeOnTop*** A `fee on top` is typically reserved for a marketplace or specialized wallet that found the order filled by the taker. This taker marketplace fee is an optional fee paid by the taker in excess of the items' prices in one or more orders. When the maker and taker marketplace is the same, it is strongly encouraged not to apply this fee, as the fee can already be assessed in the maker fee. Note that the `fee on top` is paid in the same currency as the order's payment method. | Recipient | Amount | |-----------|---------| | address | uint256 | - Recipient: The address to which the fee is paid, or `address(0)` when the fee is empty. - Amount: The absolute amount of the fee, in wei, or `0` when the fee is empty. Note that the fee amount is not permitted to exceed the item sale price of any given order. ## ***TokenSetProof*** Token set proofs are required to fill token set offers on a collection. A token set offer is a merkle tree where the leaf data is the keccak256 hash of the collection address and token id. ``` leafHash = keccak256(abi.encode(collectionAddress, tokenId)); ``` | Proof | |-----------| | bytes32[] | - Proof: The merkle proof for the specific collection/token id leaf node being filled, , or `bytes32[](0)` when the order being filled is not a token set offer. ## ***Order*** This data structure is used to fill all single and bulk orders that are not sweep orders. | Protocol | Maker | Beneficiary | Marketplace | Fallback Royalty Recipient | Payment Method | Token Address | Token Id | Amount | Item Price | Nonce | Expiration | Marketplace Fee Numerator | Max Royalty Fee Numerator | Requested Fill Amount | Minimum Fill Amount | Protocol Fee Version | |----------|---------|-------------|-------------|----------------------------|----------------|---------------|----------|---------|------------|---------|------------|---------------------------|---------------------------|-----------------------|---------------------|--------| |uint256 | address | address | address | address | address | address | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | - Protocol: `0` for `ERC721_FILL_OR_KILL`, `1` for `ERC1155_FILL_OR_KILL`, or `2` for `ERC1155_FILL_PARTIAL` collections. `FILL_PARTIAL` means an order is partially fillable across multiple trades, while `FILL_OR_KILL` means the entire amount specified in the order must be filled in a single transaction. - Maker: The address of the account the created the order. May be an EOA or Smart Contract account. When the order was a listing, the maker is the seller of the item. When the order was an offer, the maker is the buyer of the item. - Beneficiary: The address of the account that receives the item when the order is filled. When the order was a listing, the taker (`msg.sender`) can either buy with themselves as the beneficiary, or another account. When the order was an offer, the maker would be the buyer, and the beneficiary address can either be the maker's account or another account. For example, the buyer could be a user's hot wallet, and the beneficiary could be the user's cold storage wallet. - Marketplace: The address to which the primary (maker) marketplace fee should be paid, or `address(0)` if the maker marketplace charges no platform fees. Note that for collections that offer non-exclusive royalty bounties, the maker marketplace also receives a royalty bounty paid out of creator royalties. For collections that offer exclusive royalty bounties, the maker marketplace receives a royalty bounty only if it matches the exclusive royalty bounty recipient designated by the collection creator. - Fallback Royalty Recipient: Used to allow marketplaces to support royalties for collections that do not implement ERC-2981, do not have a contract owner and do not have a user with default admin role. Fallback royalties are calculated using the `Max Royalty Fee Numerator` for the order. - Payment Method: The address of the ERC-20 coin used to fill the trade, or `address(0)` for the native currency of the chain the trade executed on. For example, `address(0)` denotes ETH on ETH Mainnet, and Matic on Polygon Mainnet. - Token Address: The address of the collection. - Token Id: The id of the token (if collection is ERC721), or the token type id of the token (if collection is ERC1155). - Amount: The number of tokens. MUST be `1` if collection is ERC721, and MUST be greater than or equal to `1` if collection is ERC1155. - Item Price: The price of the token(s) in the order. Item price may not exceed type(uint240).max. - Nonce: A unique identifier to the order that prevents replay attacks. Nonce applies only to standard signed orders and may not be re-used. For co-signed order, nonce should be set to `0`. Note: It is easier to generate a random nonce than attempt to keep track of nonces that have been used. However, be aware that a gas optimization is in place such that filled or cancelled nonces are tracked in a bitmap. Nonces 0-255, 256-511, 512-767, etc are stored in the same slot. Each maker address has its own set of nonces generated for gasless listings such that `nonce 1234` is stored separately for makers `address(5678)` and `address(abcd)` but storage of the same nonce for the same maker is common across all marketplaces. It is possible that marketplaces will have no knowledge of nonces used in outstanding order signatures. Marketplaces should employ a nonce sequencing methodology that reduces the probability of overlapping nonce usage with other marketplaces. Examples of this could be a fixed set of upper bits for all orders placed through that marketplace where nonces `0x12340000...0000` to `0x1234FFFF...FFFF` relate to a specific marketplace or the development of a common nonce issuance service that tracks all nonces used by a maker. - Expiration: The unix timestamp (in seconds) when the maker's order signature expires. A marketplace's order making API should use the current unix timestamp and add a user-defined validity time that is acknowledged in the maker's signature. - Marketplace Fee Numerator: Marketplace fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. - Max Royalty Fee Numerator: Maximum approved royalty fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. When requesting the order signature from the order maker, the marketplace MUST first attempt to read the royalties for the individual token using the EIP-2981 `royaltyInfo` function call on the collection. If `royaltyInfo` raises an exception (likely because it is unimplemented), the marketplace MUST attempt to determine if royalties have been backfilled by calling the `collectionRoyaltyBackfillSettings` function on Payment Processor. If no on-chain royalties are present, this may be set to `0`. - Requested/Minimum Fill Amount: When a fill transaction is submitted, this is the number of items the taker wishes to fill. If insufficient items remain from the partially filled order, the remaining number of items is filled instead, provided the number of remaining items its greater than or equal to the Minimum Fill Amount. - Protocol Fee Version: The protocol fee version to use when applying protocol fees. ## ***Sweep Order*** This data structure is used to fill sweep orders, and represents the shared values that apply to all order in the sweep. | Protocol | Token Address | Payment Method | Beneficiary | |----------| --------------|----------------|-------------| | uint256 | address | address | address | - Protocol: `0` for ERC721 or `1` for ERC1155 collections. - Token Address: The address of the collection. - Payment Method: The address of the ERC-20 coin used to fill the trade, or `address(0)` for the native currency of the chain the trade executed on. For example, `address(0)` denotes ETH on ETH Mainnet, and Matic on Polygon Mainnet. - Beneficiary: The address of the account that receives the item when the order is filled. ## ***Sweep Item*** This data structure is used to fill sweep orders, and represents the values that apply to individual orders in the sweep. | Maker | Marketplace | Fallback Royalty Recipient | Token Id | Amount | Item Price | Nonce | Expiration | Marketplace Fee Numerator | Max Royalty Fee Numerator | Protocol Fee Version | |---------|-------------|----------------------------|----------|------------|------------|---------|------------|---------------------------|---------------------------|---------------------------| | address | address | address | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | uint256 | - Maker: The address of the account the created the order. May be an EOA or Smart Contract account. When the order was a listing, the maker is the seller of the item. When the order was an offer, the maker is the buyer of the item. - Marketplace: The address to which the primary (maker) marketplace fee should be paid, or `address(0)` if the maker marketplace charges no platform fees. Note that for collections that offer non-exclusive royalty bounties, the maker marketplace also receives a royalty bounty paid out of creator royalties. For collections that offer exclusive royalty bounties, the maker marketplace receives a royalty bounty only if it matches the exclusive royalty bounty recipient designated by the collection creator. - Fallback Royalty Recipient: Used to allow marketplaces to support royalties for collections that do not implement ERC-2981, do not have a contract owner and do not have a user with default admin role. Fallback royalties are calculated using the `Max Royalty Fee Numerator` for the sweep item. - Token Id: The id of the token (if collection is ERC721), or the token type id of the token (if collection is ERC1155). - Amount: The number of tokens. MUST be `1` if collection is ERC721, and MUST be greater than or equal to `1` if collection is ERC1155. - Item Price: The price of the token(s) in the order. May not exceed type(uint240).max. - Nonce: A unique identifier to the order that prevents replay attacks. Nonce applies only to standard signed orders and may not be re-used. For co-signed order, nonce should be set to `0`. Note: It is easier to generate a random nonce than attempt to keep track of nonces that have been used. However, be aware that a gas optimization is in place such that filled or cancelled nonces are tracked in a bitmap. Nonces 0-255, 256-511, 512-767, etc are stored in the same slot. Each maker address has its own set of nonces generated for gasless listings such that `nonce 1234` is stored separately for makers `address(5678)` and `address(abcd)` but storage of the same nonce for the same maker is common across all marketplaces. It is possible that marketplaces will have no knowledge of nonces used in outstanding order signatures. Marketplaces should employ a nonce sequencing methodology that reduces the probability of overlapping nonce usage with other marketplaces. Examples of this could be a fixed set of upper bits for all orders placed through that marketplace where nonces `0x12340000...0000` to `0x1234FFFF...FFFF` relate to a specific marketplace or the development of a common nonce issuance service that tracks all nonces used by a maker. - Expiration: The unix timestamp (in seconds) when the maker's order signature expires. A marketplace's order making API should use the current unix timestamp and add a user-defined validity time that is acknowledged in the maker's signature. - Marketplace Fee Numerator: Marketplace fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. - Max Royalty Fee Numerator: Maximum approved royalty fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. When requesting the order signature from the order maker, the marketplace MUST first attempt to read the royalties for the individual token using the EIP-2981 `royaltyInfo` function call on the collection. If `royaltyInfo` raises an exception (likely because it is unimplemented), the marketplace MUST attempt to determine if royalties have been backfilled by calling the `collectionRoyaltyBackfillSettings` function on Payment Processor. If no on-chain royalties are present, this may be set to `0`. - Protocol Fee Version: The protocol fee version to use when applying protocol fees. ## ***Bulk Order Proof*** | orderIndex | proof | |------------|-----------| | uint256 | bytes32[] | - Order Index: The index of the order within the structure of the merkle tree. This is used to determine what side of the merkle tree the order falls on and used in conjunction with the provided merkle proof to recreate the root. - Proof: An array of bytes32 hashes which are used to complete the merkle tree and generate the root along with the order digest. ## ***Permit Context*** | permitProcessor | permitNonce | |-----------------|-------------| | address | uint256 | - Permit Processor: The address of the Permit Processor contract. In order to be compatible with the Payment Processor, the provided contract must implement the `_permitTransferFromWithAdditionalData` variants in `PermitC` as well as the `fillPermittedOrderERC1155` function. - Permit Nonce: The nonce of the permit processor to use to ensure that the transaction is only executed once. After a nonce is invalidated, the transaction cannot be replayed. ## ***Advanced Order*** This struct defines the items required to execute an advanced order. | saleDetails | signature | cosignature | permitContext | |-------------|----------------|-------------|---------------| | Order | SignatureECDSA | Cosignature | PermitContext | - saleDetails: The order execution parameters. - signature: The signature of the maker authorizing order execution. - cosignature: The cosignature of the maker authorizing the order execution (when cosigning enabled for an order). - permitContext: Contains the address of the permit processor and the permit nonce to be used. ## ***Advanced Bid Order*** This struct defines the items required to execute an advanced bid order. | offerType | advancedOrder | sellerPermitSignature | |-----------|---------------|-----------------------| | uint256 | AdvancedOrder | SignatureECDSA | - offerType: The type of offer to execute [(0) Offer for any item in a collection. (1) Offer for a specific item in a collection. (2) Offer for a set of tokens in a collection.] - advancedOrder: The order execution parameters. - sellerPermitSignature: The signature of the seller to be used for the permit. ## ***Advanced Sweep*** This struct defines the items required to execute an advanced sweep order. | feeOnTop | sweepOrder | items | |----------|------------|---------------------| | FeeOnTop | SweepOrder | AdvancedSweepItem[] | - feeOnTop: The additional fee to be paid by the taker. - sweepOrder: The order execution parameters. - items: An array of items to be executed as part of the sweep order. ## ***Advanced Sweep Item*** This struct is a wrapper for a sweep order item that includes the permit context, bulk order information, signature and cosignature. | sweepItem | signature | cosignature | permitContext | bulkOrderProof | |-----------|----------------|-------------|---------------|----------------| | SweepItem | SignatureECDSA | Cosignature | PermitContext | BulkOrderProof | - sweepItem: The sweep order item to be executed. - signature: The signature of the maker authorizing the order execution. - cosignature: The cosignature of the maker authorizing the order execution. - permitContext: Contains the address of the permit processor and the permit nonce to be used. - bulkOrderProof: The proof data for the bulk order. # Listings A listing is an order (standard or cosigned) to sell an NFT once filled by a taker. Listings are gaslessly signed off-chain by the owner/seller of the NFT. ## Notional Exchange Listing Flow 1. Maker browses their items and selects an item to list for sale. 2. Maker inputs: Listing Currency/Payment Method, Price, Validity Time. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage and on-chain royalty fee (display sale price, fees, and net seller proceeds) 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 5. Signature Prompt for [SaleApproval](#sale-approval) signature. 6. Order and signature submitted to order book. ## Basic Sale Approval Signatures When listing an individual NFT for sale the seller/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the order is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```solidity SaleApproval( uint8 protocol, address cosigner, address seller, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 maxRoyaltyFeeNumerator, uint256 nonce, uint256 masterNonce, uint256 protocolFeeVersion ) ``` Typed data hash: `0x36e5aab0dde52c07206863cf8fe7b4b1a25be63a3ae5286f8fe80d159c154c4f` ## Advanced Sale Approval Signatures There are two types of advanced sale approvals: permits and bulk orders. ### Permitted Sale Approval Signatures Instead of approving Payment Processor to transfer tokens, users can instead authorize Permit-C to transfer tokens and issue gasless permits allowing Payment Processor to transfer their NFTs instead. A permitted sale approval is a Permit-C transfer permit with the sale approval embedded and signed as a signle signature. The structure of the EIP-712 message for permitted sale approvals is shown below. ```solidity PermitTransferFromWithAdditionalData( uint256 tokenType, address token, uint256 id, uint256 amount, uint256 nonce, address operator, uint256 expiration, uint256 masterNonce, SaleApproval approval ) SaleApproval( uint8 protocol, address cosigner, address seller, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 maxRoyaltyFeeNumerator, uint256 nonce, uint256 masterNonce, uint256 protocolFeeVersion ) ``` Typed data hash: `0xb1cb65a3e643070d9d33047fdfd384badc7cd70b2415e548b37db5a10c708716` ### Bulk Sale Approval Signatures For users seeking to list many items at once, users can sign a bulk sale approval order. Listings signed in bulk can be filled independently of the others. Up to 1024 items may be listed with one signature. Due to how `EIP-712` struct hashing works, a nested array of `[2] * treeHeight` will produce the same hash as the root hash, provided it contains the same data and is padded with empty structs. Trees of depths 1 (`2^1 = 2`) to 10 (`2^10 = 1024`) are supported on Payment Processor. ### Bulk Order Typehashes The Bulk Order Typehash Lookup tables are generated by concatenating the hashes from each height into one bytes array. This is decoded by taking the bytes32 output of the offset of `height - 1 * 32`. ```solidity // keccack256("BulkSaleApproval(SaleApproval[2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_1_HASH = 0x8659f55d4c88f84030fe09241169834754ebf2e61099b616e530c6b65712c644; // keccack256("BulkSaleApproval(SaleApproval[2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_2_HASH = 0x5106f4043cda72e04ca2c760016628ea8d5667309323ba590682ea5254b4e82e; // keccack256("BulkSaleApproval(SaleApproval[2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_3_HASH = 0x71267a8f42e7ccde4ac75848a93b741df7a2f7a58e40274055f2fa51dbaa69ce; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_4_HASH = 0x6e90557aed2a349f8b6efb2b652c4025c56cfa82424c5cd0c6801cc234ebf519; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_5_HASH = 0xcaf42fc49ad57705ea6be2ca9e1835c9ccddf7bc6fdea8f851917329b1d53a7f; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_6_HASH = 0x09b32b6b5ffee898d5fe18398652042cc8c46449329c5a1a79e425f4ede9bc2b; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_7_HASH = 0xc2362dea3338b88607906397acfdcc5651f19b40a038ee7c89dc4ab54c736cac; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_8_HASH = 0xf2bbc4f6d7ccfa2b8aab6505f0c8f2868c5a190c6c4cfce8c0de447804904df9; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_9_HASH = 0x81f99e15003039550d3597e34e6426b1e230992a5a3727cb5e230754f18f3566; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_10_HASH = 0x012178d0a6b8f8748e48411572bdc391435539737aafc91fcba6f2c24ae156cf; ``` For more details on bulk order signing, see the [Bulk Orders section of the docs](./bulk-orders.md). # Offers Offers are gaslessly signed off-chain by one or more parties interested in purchasing the NFT. Payment Processor support three offer types: - **Item Offer**: An order (standard or cosigned) to buy a single, specific NFT once filled by a taker. Item offers apply to exactly one specific token id in a collection. - **Collection Offer**: An order (standard or cosigned) to buy any NFT belonging to a specific collection once filled by a taker. Collection offers apply to any token id for a specific collection. - **Token Set Offer**: An order (standard or cosigned) to buy a single NFT from a subset of token ids in a specific collection once filled by a taker. Token set offers apply to a subset of token ids for a specific collection. ## Notional Exchange Offer Flow ### Item Offer 1. Maker browses a collection and selects a single item to make an offer on. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds) 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). For standard orders, a nonce is generated. 5. Signature Prompt for either [ItemOfferApproval](#item-offer-approval). 6. Order and signature submitted to order book. ### Collection Offer 1. Maker browses a collection and chooses to make a collection offer for any item in the collection. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds). 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 5. Signature Prompt for either [CollectionOfferApproval](#collection-offer-approval). 6. Order and signature submitted to order book. ### Token Set Collection Offer This can take a few forms depending on how the marketplace chooses to implement this. This can take the form of offers on traits, offers on tokens that have not been stolen, etc. 1. Maker browses a collection and chooses some criteria for token ids to make an offer on. The marketplace generates a merkle tree of tokens based on the criteria. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds). 4. Maker prompted to acknowledge the set of tokens/merkle root. 5. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 6. Signature Prompt for either [TokenSetOfferApproval](#token-set-offer-approval). 7. Order and signature submitted to order book. ## Item Offer Approval When making an offer on a specific NFT the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js ItemOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce ) ``` Typed data hash: `0xce2e9706d63e89ddf7ee16ce0508a1c3c9bd1904c582db2e647e6f4690a0bf6b` ## Collection Offer Approval When making an offer on any NFT in a specific collection the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js CollectionOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce ) ``` Typed data hash: `0x8fe9498e93fe26b30ebf76fac07bd4705201c8609227362697082288e3b4af9c` ## Token Set Offer Approval When making an offer on a subset of NFTs in a specific collection the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js TokenSetOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce, bytes32 tokenSetMerkleRoot ) ``` Typed data hash: `0x244905ade6b0e455d12fb539a4b17d7f675db14797d514168d09814a09c70e70` # Filling Orders ## Filling A Single Listing (Buy Now) 1. Taker browses listings and chooses a listed item. 2. Taker selects "Buy Now". 3. Taker selects beneficiary (whether self or other account). 4. Taker prompted to review the details, including review and acknowledgement of fee on top when applicable. 5. If listing order was co-signed, client requests a co-signature for the listing. 6. Marketplace calls the PaymentProcessorEncoder `encodeBuyListingCalldata` function to generate the calldata to fill the order. [Encoding Details](./calldata-encoding#encodebuylistingcalldata) 7. Wallet pops up a transaction confirmation of the `buyListing` call. [Usage Details](#buy-listing) 8. Taker confirms the transaction through their wallet interface. ## Filling A Batch Of Listings (Shopping Cart) 1. Taker browses listings and chooses several listed items (they may be from one or more collections). 2. Taker selects "Add To Shopping Cart" for each item. 3. Taker reviews cart and "Checks Out". 4. Taker selects beneficiary (whether self or other account). 5. Taker prompted to review the details, including review and acknowledgement of all fees on top when applicable. 6. For any co-signed listing orders, client requests co-signatures. 7. Marketplace calls the PaymentProcessorEncoder `encodeBulkBuyListingsCalldata` function to generate the calldata to bulk fill the orders. [Encoding Details](./calldata-encoding#encodebulkbuylistingscalldata) 8. Wallet pops up a transaction confirmation of the `bulkBuyListings` call. [Usage Details](#bulk-buy-listings) 9. Taker confirms the transaction through their wallet interface. ## Filling A Batch of Listings (Collection Sweep) 1. Taker chooses "Sweep Collection" for the desired collection. 2. Taker specifies desired quantity and maximum price per item. 3. Taker selects beneficiary (whether self or other account). 4. Order book supplies listings that are the best matches. 5. Taker prompted to review the details, including review and acknowledgement of the fee on top when applicable. 6. For any co-signed listing orders, client requests co-signatures. 7. Marketplace calls the PaymentProcessorEncoder `encodeSweepCollectionCalldata` function to generate the calldata to fill the sweep. [Encoding Details](./calldata-encoding#encodesweepcollectioncalldata) 8. Wallet pops up a transaction confirmation of the `sweepCollection` call. [Usage Details](#sweep-collection) 9. Taker confirms the transaction through their wallet interface. ## Filling A Single Offer (Accept Offer) 1. Taker browser offers where the offer criteria matches items they own and chooses one. 2. Taker selects "Accept Offer". 3. Taker prompted to review the details, including review and acknowledgement of the current on-chain royalty fee, maker marketplace fee, and fee on top when applicable. 4. If the offer order was co-signed, client requests a co-signature for the offer. 5. Marketplace calls the PaymentProcessorEncoder `encodeAcceptOfferCalldata` function to generate the calldata to fill the order. [Encoding Details](./calldata-encoding#encodeacceptoffercalldata) 6. Wallet pops up a transaction confirmation of the `acceptOffer` call. [Usage Details](#accept-offer) 7. Taker confirms the transaction through their wallet interface. ## Filling A Batch Of Offers (Offer Cart) 1. Taker browser offers where the offer criteria matches items they own and chooses several. 2. Taker selects "Add To Offer Cart" for each item. 3. Taker reviews cart and "Checks Out". 4. Taker prompted to review the details, including review and acknowledgement of the current on-chain royalty fees for each item, the maker marketplace fees for each item, and the fees on top when applicable. 5. For any co-signed offer orders, client requests co-signatures. 6. Marketplace calls the PaymentProcessorEncoder `encodeBulkAcceptOffersCalldata` function to generate the calldata to bulk fill the orders. [Encoding Details](./calldata-encoding#encodebulkacceptofferscalldata) 7. Wallet pops up a transaction confirmation of the `bulkAcceptOffers` call. [Usage Details](#bulk-accept-offers) 8. Taker confirms the transaction through their wallet interface. ***Note: There are other steps marketplaces may need to implement to prompt users in the workflow. For instance: approving Payment Processor to transfer NFTs and ERC-20 payments, prompting to wrap native currency or swap currencies when needed, or prompting buyers to perform a one-time signature to prove they are EOAs [for select ERC721-C security levels only].*** ## Taker Operations / Functions ### Buy Listing Exchanges should call `buyListing` when a taker wants to purchase a single listing using a UX analogous to "Buy Now". There are four order types that may be filled through the `buyListing` function: **Standard Sell Order *Without* Fee On Top** - To be used when the taker fills a standard sell order and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `cosignature` and `feeOnTop` struct parameters passed to PaymentProcessorEncoder would be filled with zero values. **Standard Sell Order *With* Fee On Top** - To be used when the taker fills a standard sell order via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution the `cosignature` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `feeOnTop` struct parameter would contain the `recipient` and `amount` for the fee being added by the taker marketplace. **Cosigned Sell Order *Without* Fee On Top** - To be used when the taker fills a co-signed sell order and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `feeOnTop` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `cosignature` struct parameter would contain the `signer`, `taker`, `expiration` and cosignature `v`, `r` and `s` values for the cosignature. **Cosigned Sell Order *With* Fee On Top** - To be used when the taker fills a co-signed sell order via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution both the `feeOnTop` and `cosignature` struct parameters passed to PaymentProcessorEncoder would contain the values for the additional fee on top and cosignature validation. Use the PaymentProcessorEncoder to encode the calldata(./calldata-encoding#encodebuylistingcalldata). *Note: The taker/buyer (`msg.sender`) pays for the item and any fee on top when applicable. Because the `buyListing` function is payable, both native or ERC-20 payment methods are accepted.* ### Advanced Buy Listing Advanced orders include 2 additional features, Permits and executing listings / offers which were generated via the `Bulk Order` merkle root. These orders can include any combination of the above 4 types, but are separated to prevent extra gas costs for standard orders. **Permit Order** - To be used when the maker generated the signature including the Permit Processor field. This will include the Permit Processor required data as well as advanced data which will need to match the order that is being fulfilled. **Bulk Order** - To be used when the maker generated the signature via signing a merkle tree. The `BulkOrderProof` must include the correct merkle proof and the correct index of the listing in order to process correctly. ### Accept Offer Exchanges should call `acceptOffer` when a taker wants to sell a single item that matches an offer made by a prospective buyer. The kinds of offers are currently supported by Payment Processor: 1. Item Offer - An offer made on a specific collection where only one specific token id can be used to fill the order. 2. Collection Offer - An offer made on a specific collection where any token id can be used to fill the order. 3. Token Set Offer - An offer made on a specific collection where any token id contained in a specified subset of token ids can be used to fill the order. There are four order types that may be filled through the `acceptOffer` function: **Standard Offer *Without* Fee On Top** - To be used when the taker fills a standard buy order (offer) and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `cosignature` and `feeOnTop` struct parameters passed to PaymentProcessorEncoder would be filled with zero values. **Standard Offer *With* Fee On Top** - To be used when the taker fills a standard buy order (offer) via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution the `cosignature` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `feeOnTop` struct parameter would contain the `recipient` and `amount` for the fee being added by the taker marketplace. **Cosigned Offer *Without* Fee On Top** - To be used when the taker fills a co-signed buy order (offer) and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `feeOnTop` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `cosignature` struct parameter would contain the `signer`, `taker`, `expiration` and cosignature `v`, `r` and `s` values for the cosignature. **Cosigned Offer *With* Fee On Top** - To be used when the taker fills a co-signed buy order (offer) via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution both the `feeOnTop` and `cosignature` struct parameters passed to PaymentProcessorEncoder would contain the values for the additional fee on top and cosignature validation. *Note: The taker/seller (`msg.sender`) pays for the fee on top when applicable. The maker/buyer pays the cost of the filled item. Because the `acceptOffer` function is not payable, only ERC-20 payment methods are accepted.* ### Bulk Buy Listings Exchanges should call `bulkBuyListings` when a taker wants to purchase more than one listing using a UX analagous to a "Shopping Cart". This allows a taker to select many NFTs across different collections or with varying payment methods and fill all of the listings at once. Each listing being purchased may be any of the four transaction types supported by the `buyListing` function (see [Buy Listing](#buy-listing) above). When encoding bulk purchases the `Order`, `SignatureECDSA`, `Cosignature` and `FeeOnTop` struct values are passed as arrays of values where the item at the same index in each array corresponds to the listing being purchased. All four arrays **MUST** be of equal length and follow the same structure as a single listing purchase for standard versus cosigned orders and execution with or without a fee on top. ### Bulk Accept Offers Exchanges should call `bulkAcceptOffers` when a taker wants to accept/fill more than one offer at once. This allows a taker to sell multiple items they own across one or more collections in a single transaction. The kinds of offers are currently supported by Payment Processor: 1. Item Offer - An offer made on a specific collection where only one specific token id can be used to fill the order. 2. Collection Offer - An offer made on a specific collection where any token id can be used to fill the order. 3. Token Set Offer - An offer made on a specific collection where any token id contained in a specified subset of token ids can be used to fill the order. Each offer being accepted may be any of the four transaction types supported by the `acceptOffer` function (see [Accept Offer](#accept-offer) above). When encoding bulk accept offers the `offerType` enumerated value and `Order`, `SignatureECDSA`, `TokenSetProof`, `Cosignature` and `FeeOnTop` struct values are passed as arrays of values where the item at the same index in each array corresponds to the offer being accepted. All six arrays **MUST** be of equal length and follow the same structure as a single offer acceptance for standard versus cosigned orders and execution with or without a fee on top. ### Sweep Collection Exchanges should call `sweepCollection` (a more gas efficient form of `bulkBuyListings`) when a taker wants to purchase more than similar listings. A common UX is the "Collection Sweep" UX where the taker specifies a number of items from a collection they want to buy, with some pricing limits. For a sweep to be filled, all items in the sweep order must share the following commonalities: 1. All sell orders fillable in the sweep must be from the same collection. 3. All sell orders fillable in the sweep must use the same method of payment. 5. All sell orders fillable in the sweep must specify the same beneficiary of the NFT. When filling sweep orders, the following fields can be different for each filled item: 1. Maker 2. Marketplace 3. Fallback Royalty Recipient 4. Token Id 5. Amount 6. Item Price 7. Nonce (for standard orders) 8. Expiration 9. Marketplace Fee Numerator 10. Max Royalty Fee Numerator Sweep orders may be executed *with* or *without* a single fee on top for the sweep. To execute *with* a fee on top the `feeOnTop` struct value passed to PaymentProcessorEncoder would contain the `recipient` and `amount` for the fee. To execute *without* a fee on top the `feeOnTop` struct value passed to PaymentProcessorEncoder would contain zero values for `recipient` and `amount`. When encoding sweep orders with PaymentProcessorEncoder the `SweepItem`, `SignatureECDSA` and `Cosignature` structs are passed as arrays of values where the item at the same index in each array corresponds to the listing being purchased. All three arrays **MUST** be of equal length and follow the same structure as a single listing purchase for standard versus cosigned orders. *Note: The taker/buyer (`msg.sender`) pays for the items and any fee on top when applicable. Because the `sweepCollection` function is payable, both native or ERC-20 payment methods are accepted.* *Note: For the most gas-efficient collection sweeps, marketplaces should make a best effort to group orders in the array by marketplace address, seller, and royalty recipient.* ### Cosignature Format All cosigned listings and offers require a secondary signature to be provided at fill-time/execution time. The same cosignature format applies to all cosigned maker signatures, and takes a cosigner expiration and the maker's signature v, r, s components as typed data inputs. Note: for security purposes, co-signatures must always be signed by EOA co-signers. Furthermore, cosignature expiration times should be relatively short. A cosignature expiration time between 5 and 10 minutes is suggested. If the expiration time is too short, transactions may fail because the expiration time elapses before a transaction has been confirmed. However, if the expiration time is too long a leak of the co-signature could be executed well into the future. ```js Cosignature( uint8 v, bytes32 r, bytes32 s, uint256 expiration, address taker ) ``` # Cancelling Orders ## Cancelling A Single Order 1. Display maker's orders (listings and/or offers). 2. Maker selects an order to cancel. 3. If standard order, maker confirms transaction for `revokeSingleNonce` (when order type is fill or kill) or `revokeOrderDigest` (when order type is partially fillable). Revoking a single nonce or order digest requires calldata to be encoded with PaymentProcessorEncoder. 4. If co-signed order flag order as cancelled in DB and do not allow co-signing service to co-sign the order if requested. Order should be hidden from future listing/offer queries. ## Cancelling All Outstanding Standard Orders 1. If the maker chooses to cancel ALL outstanding standard listings and offers they have made, the maker simply needs to confirm a `revokeMasterNonce` transaction. This will cancel/invalidate all prior orders that were signed using their previous master nonce. `revokeMasterNonce` has no calldata requirements and may be called directly by a maker without using PaymentProcessorEncoder. # Cancelling All Outstanding Co-Signed Orders 1. The marketplace should flag all outstanding orders for the user as cancelled in their database. The co-signing service should not return co-signatures for any of the cancelled orders, and the orders should be hidden from all future queries. # Cancelling a Single Permit Order 1. Display maker's permit orders (listings and/or offers). 2. Maker selects an order to cancel. 3. Maker confirms transaction for `invalidateUnorderedNonce` (when the order type is fill or kill) or `closePermittedOrder` (when the order type is partially fillable). ***Note: These operations are executed on Permit-C, not Payment Processor, as that is where permit nonces and permitted partially fillable orders are tracked.*** 4. If co-signed order flag order as cancelled in DB and do not allow co-signing service to co-sign the order if requested. Order should be hidden from future listing/offer queries. # Cancelling ALL Outstanding Permit Orders 1. If the maker chooses to cancel ALL outstanding permit orders, the maker simply needs to confirm a `lockdown` transaction on the permit processor. This will cancel/invalidate all permits, including permits unrelated to Payment Processor. # Cosigner Security Cosignatures on orders allow an order book to offer users the ability to gaslessly cancel their listings and offers. To guard against bad actors order books ***MUST*** keep the private keys of the cosigning accounts secure and ***SHOULD*** use short expirations on cosignatures so that signed but unexecuted cosignatures are unlikely to be used after the maker has cancelled their order off-chain. If the private key to a cosigner account has been exposed the order book ***MUST*** destroy the cosigner through Payment Processor on all chains that it was used as an order cosigner so that it may not be used to fill off-chain cancelled orders. Cosigners cannot be recovered after they have been destroyed on Payment Processor. ## Destroy a Cosigner 1. Use the cosigner private key to sign the message `COSIGNER_SELF_DESTRUCT`. 2. Call `destroyCosigner` on Payment Processor with the cosigner address and message signature. See [encodeDestroyCosigner](./calldata-encoding#encodedestroycosignercalldata). # Encoding Function Call Parameters In order to not overflow smart contract bytecode size limits, Payment Processor is split into smaller library modules. When making calls to Payment Processor, these calls are forwarded to the corresponding module and implementation function using `DELEGATECALL`. For gas efficiency purposes, the function calldata must be passed in as a raw calldata byte array. Payment Processor is deployed with a helper contract called the PaymentProcessorEncoder. It accepts the expected function implementation parameters and encodes them as a single calldata byte array that should be passed into Payment Processor when making calls to fill orders. The subsections below detail each PaymentProcessorEncoder function. ### encodeBuyListingCalldata Used to encode calldata for Payment Processor `buyListing` function. ```solidity function encodeBuyListingCalldata( address paymentProcessorAddress, Order memory saleDetails, SignatureECDSA memory signature, Cosignature memory cosignature, FeeOnTop memory feeOnTop ) external view returns (bytes memory); ``` ### encodeBuyListingAdvancedCalldata Used to encode calldata for PaymentProcessor `buyListingAdvanced` function. ```solidity function encodeBuyListingAdvancedCalldata( address paymentProcessorAddress, AdvancedOrder memory advancedListing, BulkOrderProof memory bulkOrderProof, FeeOnTop memory feeOnTop ) external view returns (bytes memory); ``` ### encodeAcceptOfferCalldata Used to encode calldata for Payment Processor `acceptOffer` function. ```solidity function encodeAcceptOfferCalldata( address paymentProcessorAddress, uint256 offerType, Order memory saleDetails, SignatureECDSA memory signature, TokenSetProof memory tokenSetProof, Cosignature memory cosignature, FeeOnTop memory feeOnTop ) external view returns (bytes memory); ``` *Note: When filling an item offer, `offerType` MUST be set to `1`, and `tokenSetProof` MUST be set to the empty `TokenSetProof({proof: bytes32[](0)})`. *Note: When filling a collection offer, `offerType` MUST be set to `0`, and `tokenSetProof` MUST be set to the empty `TokenSetProof({proof: bytes32[](0)})`. *Note: When filling a token set offer, `offerType` MUST be set to `2`, and `tokenSetProof` MUST contain the valid merkle proof for the collection and token id being filled. ### encodeAcceptOfferAdvancedCalldata Used to encode calldata for the PaymentProcessor `acceptOfferAdvanced` function. ```solidity function encodeAcceptOfferAdvancedCalldata( address paymentProcessorAddress, AdvancedBidOrder memory advancedBid, BulkOrderProof memory bulkOrderProof, FeeOnTop memory feeOnTop, TokenSetProof memory tokenSetProof) external view returns (bytes memory) ``` *Note: If the maker did not use a permit processor, `advancedBid.permitContext` MUST be set to the empty `PermitContext({permitProcessor: address(0), permitNonce: 0})`.* *Note: If the order being filled is not associated with a merkle root, `bulkOrderProof` MUST be set to the empty `BulkOrderProof({orderIndex: 0, proof: []})`.* *Note: If the order being filled does not have fees on top, `feeOnTop` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})`.* ### encodeBulkBuyListingsCalldata Used to encode calldata for Payment Processor `bulkBuyListings` function. ```solidity function encodeBulkBuyListingsCalldata( address paymentProcessorAddress, Order[] calldata saleDetailsArray, SignatureECDSA[] calldata signatures, Cosignature[] calldata cosignatures, FeeOnTop[] calldata feesOnTop ) external view returns (bytes memory); ``` *Note: The length of the `saleDetailsArray`, `signatures`, `cosignatures`, and `feesOnTop` MUST match and MUST be non-zero. `saleDetailsArray[index]` MUST correspond to `signatures[index]`, `cosignatures[index]`, and `feesOnTop[index]`.* *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: As fees on top may not apply to all orders being filled, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})` for any order that is not subject to a fee on top.* ### encodeBulkBuyListingsAdvancedCalldata Used to encode calldata for PaymentProcessor `bulkBuyListingsAdvanced` function. ```solidity function encodeBulkBuyListingsAdvancedCalldata( address paymentProcessorAddress, AdvancedOrder[] memory advancedListingsArray, BulkOrderProof[] memory bulkOrderProofs, FeeOnTop[] memory feesOnTop) external view returns (bytes memory) ``` *Note: The length of the `advancedListingsArray`, `bulkOrderProofs` and `feesOnTop` MUST match and MUST be non-zero. `advancedListingsArray[index]` MUST correspond to `bulkOrderProofs[index]` and `feesOnTop[index]`.* *Note: If the maker did not use a permit processor, `advancedListingsArray[index].permitContext` MUST be set to the empty `PermitContext({permitProcessor: address(0), permitNonce: 0})`.* *Note: If the order being filled is not be associated with a merkle root, `bulkOrderProofs[index]` MUST be set to `BulkOrderProof({orderIndex: 0, proof: []})`.* *Note: If the order being filled does not have fees on top, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})`.* ### encodeBulkAcceptOffersCalldata Used to encode calldata for Payment Processor `bulkAcceptOffers` function. ```solidity function encodeBulkAcceptOffersCalldata( address paymentProcessorAddress, uint256[] memory offerTypeArray, Order[] memory saleDetailsArray, SignatureECDSA[] memory signatures, TokenSetProof[] memory tokenSetProofsArray, Cosignature[] memory cosignaturesArray, FeeOnTop[] memory feesOnTopArray ) external view returns (bytes memory); ``` *Note: The length of the `saleDetailsArray`, `signatures`, `tokenSetProofsArray`, `offerTypeArray`, `cosignatures`, and `feesOnTopArray` MUST match and MUST be non-zero. `saleDetailsArray[index]` MUST correspond to `signatures[index]`, `tokenSetProofsArray[index]`, `offerTypeArray[index]`, `cosignatures[index]`, and `feesOnTopArray[index]`.* *Note: When filling an item offer, `offerTypeArray[index]` MUST be set to `1`, and `tokenSetProofsArray[index]` MUST be set to the empty `TokenSetProof({proof: bytes32[](0)})`. *Note: When filling a collection offer, `offerTypeArray[index]` MUST be set to `0`, and `tokenSetProofsArray[index]` MUST be set to the empty `TokenSetProof({proof: bytes32[](0)})`. *Note: When filling a token set offer, `offerTypeArray[index]` MUST be set to `2`, and `tokenSetProofsArray[index]` MUST contain the valid merkle proof for the collection and token id being filled. *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: As fees on top may not apply to all orders being filled, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})` for any order that is not subject to a fee on top.* ### encodeBulkAcceptOffersAdvancedCalldata Used to encode calldata for PaymentProcessor `bulkAcceptOffersAdvanced` function. ```solidity function encodeBulkAcceptOffersAdvancedCalldata( address paymentProcessorAddress, AdvancedBidOrder[] memory advancedBidsArray, BulkOrderProof[] memory bulkOrderProofs, FeeOnTop[] memory feesOnTop, TokenSetProof[] memory tokenSetProofs) external view returns (bytes memory) ``` *Note: The length of the `advancedBidsArray`, `bulkOrderProofs`, `feesOnTop`, and `tokenSetProofs` MUST match and MUST be non-zero. `advancedBidsArray[index]` MUST correspond to `bulkOrderProofs[index]`, `feesOnTop[index]`, and `tokenSetProofs[index]`.* *Note: If the maker did not use a permit processor, `advancedBidsArray[index].permitContext` MUST be set to the empty `PermitContext({permitProcessor: address(0), permitNonce: 0})`.* *Note: If the order being filled is not be associated with a merkle root, `bulkOrderProofs[index]` MUST be set to the empty `BulkOrderProof({orderIndex: 0, proof: []})`.* *Note: As fees on top may not apply to all orders being filled, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})` for any order that is not subject to a fee on top.* *Note: When filling a token set offer, `tokenSetProofs[index]` MUST be set to the empty `TokenSetProof({proof: bytes32[](0)})`.* ### encodeSweepCollectionCalldata Used to encode calldata for Payment Processor `sweepCollection` function. ```solidity function encodeSweepCollectionCalldata( address paymentProcessorAddress, FeeOnTop memory feeOnTop, SweepOrder memory sweepOrder, SweepItem[] calldata items, SignatureECDSA[] calldata signatures, Cosignature[] calldata cosignatures ) external view returns (bytes memory); ``` *Note: The length of the `items`, `signatures`, and `cosignatures` MUST match and MUST be non-zero. `items[index]` MUST correspond to `signatures[index]` and `cosignatures[index]`.* *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: A single fee on top may be applied to the entire sweep order.* ### encodeSweepCollectionAdvancedCalldata Used to encode calldata for PaymentProcessor `sweepCollectionAdvanced` fucntion. ```solidity function encodeSweepCollectionAdvancedCalldata( address paymentProcessorAddress, AdvancedSweep memory advancedSweep) external view returns (bytes memory) ``` *Note: If the maker did not use a permit processor, `advancedSweep.items[index].permitContext` MUST be set to the empty `PermitContext({permitProcessor: address(0), permitNonce: 0})`.* *Note: If the order being filled is not be associated with a merkle root, `advancedSweep.items[index].bulkOrderProof` MUST be set to the empty `BulkOrderProof({orderIndex: 0, proof: []})`.* *Note: If the order being filled does not have fees on top, `advancedSweep.feeOnTop` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})`.* ### encodeRevokeSingleNonceCalldata Used to encode calldata for Payment Processor `revokeSingleNonce` function (on-chain order cancellation). ```solidity function encodeRevokeSingleNonceCalldata(address paymentProcessorAddress, uint256 nonce) external view returns (bytes memory); ``` ### encodeRevokeOrderDigestCalldata Used to encode calldata for Payment Processor `revokeOrderDigest` function (on-chain order cancellation). ```solidity function encodeRevokeOrderDigestCalldata(address paymentProcessorAddress, bytes32 digest) external view returns (bytes memory); ``` ### encodeDestroyCosignerCalldata Used to encode calldata for Payment Processor `destroyCosignerCalldata` function. ```solidity function encodeDestroyCosignerCalldata( address paymentProcessorAddress, address cosigner, SignatureECDSA memory signature ) external view returns (bytes memory); ``` # Bulk Orders Bulk Order Proofs allow for users to sign for multiple listings or offers in one signature. To make it more transparent, the marketplace should present the signature digest in a human readable format, allowing for users to review the data they sign before completing. Due to how `EIP-712` struct hashing works, a nested array of `[2] * treeHeight` will produce the same hash as the root hash, provided it contains the same data and is padded with empty structs. Trees of depths 1 (`2^1 = 2`) to 10 (`2^10 = 1024`) are supported on Payment Processor. ### Bulk Order Typehashes The Bulk Order Typehash Lookup tables are generated by concatenating the hashes from each height into one bytes array. This is decoded by taking the bytes32 output of the offset of `height - 1 * 32`. ```js // keccack256("BulkSaleApproval(SaleApproval[2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_1_HASH = 0x8659f55d4c88f84030fe09241169834754ebf2e61099b616e530c6b65712c644; // keccack256("BulkSaleApproval(SaleApproval[2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_2_HASH = 0x5106f4043cda72e04ca2c760016628ea8d5667309323ba590682ea5254b4e82e; // keccack256("BulkSaleApproval(SaleApproval[2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_3_HASH = 0x71267a8f42e7ccde4ac75848a93b741df7a2f7a58e40274055f2fa51dbaa69ce; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_4_HASH = 0x6e90557aed2a349f8b6efb2b652c4025c56cfa82424c5cd0c6801cc234ebf519; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_5_HASH = 0xcaf42fc49ad57705ea6be2ca9e1835c9ccddf7bc6fdea8f851917329b1d53a7f; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_6_HASH = 0x09b32b6b5ffee898d5fe18398652042cc8c46449329c5a1a79e425f4ede9bc2b; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_7_HASH = 0xc2362dea3338b88607906397acfdcc5651f19b40a038ee7c89dc4ab54c736cac; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_8_HASH = 0xf2bbc4f6d7ccfa2b8aab6505f0c8f2868c5a190c6c4cfce8c0de447804904df9; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_9_HASH = 0x81f99e15003039550d3597e34e6426b1e230992a5a3727cb5e230754f18f3566; // keccack256("BulkSaleApproval(SaleApproval[2][2][2][2][2][2][2][2][2][2] tree)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)") bytes32 constant BULK_SALE_APPROVAL_HEIGHT_10_HASH = 0x012178d0a6b8f8748e48411572bdc391435539737aafc91fcba6f2c24ae156cf; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_1_HASH = 0xfb1dd652f3a5dcde3cb90b0cbb7ff0d33df15a48f216e6ac0e2306ff3538e336; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_2_HASH = 0x32fc12675167bd2e9c23f1a9b90ce9c0a14daa7e8b3fa5a01105804ad64aa49d; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_3_HASH = 0x22beb101a4a232e5b05a7b5f4727888724dd418194d6c5b1f1ba5ead4cbeed42; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_4_HASH = 0x9ba46e6a390d8f8edfb6f752177c215b1cf8b6dcf1f5da395ead6c3e19755698; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_5_HASH = 0xae93fe4a03206f6bb45035fab04a39964c83e7d265e93fc31521c86dd25a3be4; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_6_HASH = 0xee3cb9e25b214af21b0d321776864470916324653397b355b267b2894a009a98; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_7_HASH = 0x516d5d5306f95a17cb6d0b84e71f78919a98ac5e3c723fc4b9b1a720193d590d; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_8_HASH = 0xf5b1dcad1bd5c8161d6b904513bc658d4c9b8c288b2271e0f27d5938654e4186; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_9_HASH = 0x63c21e3ce7e506548c01ef4e6a27a5805f9328e0da83e9a7c765dc28266a5aa1; // keccack256("BulkCollectionOfferApproval(CollectionOfferApproval[2][2][2][2][2][2][2][2][2][2] tree)CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_COLLECTION_OFFER_APPROVAL_HEIGHT_10_HASH = 0x9802b2e35d72255f2ae69bed829f0104723aa156059e02dfccfe781ffc955356; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_1_HASH = 0x243dc86e63d73b10b2764253a38ab8763ded4ac63a632adef4019e482161964a; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_2_HASH = 0x2ed4880b2bfec74882da06ca9f461158c42fa73fa5974b5abec9fcde7cdd43ee; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_3_HASH = 0x8308607f65c90c023abf48563d2c8bd8c783d1db48ffd02ec324105d9f682f96; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_4_HASH = 0x1f7a30dcec9205ff30a5290b3b756e1fd1a1585539bccbc817ab4e59d69a055e; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_5_HASH = 0xcfa8c0abdff5fb2704d1818f38dc974f5bdde6dfe1a4ddc3e22a1f3e593e98f3; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_6_HASH = 0x24e3124a40922800d1eb1890240fc7d07f257a303bb383e8c0820e9910be1f97; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_7_HASH = 0xc72769ff919066b8d1f586bb8b7e720ad93f735ffa9570f9e9ef0e865d13251d; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_8_HASH = 0x89338818bfd140179260dbdc6d6bf38461d49661c166f2b76d3b56727acab7ad; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_9_HASH = 0xf3e381ed4335a28fa612f4068c58de673cad05c8a7fbfb5c1fd557c2c5a2de9f; // keccack256("BulkItemOfferApproval(ItemOfferApproval[2][2][2][2][2][2][2][2][2][2] tree)ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)") bytes32 constant BULK_ITEM_OFFER_APPROVAL_HEIGHT_10_HASH = 0x84c94a0cfd84379f92ed5827593e2218a1dc1b975626bbe5c6948b9d2232f7d7; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_1_HASH = 0x0d0b16c0b281480a518e0ae1543bf94c612edc289e45edc04e8510dac617afa4; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_2_HASH = 0x337b9c051f65323200a3580d4917087d4d82e350fcc68b3397c654c9fc7872c3; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_3_HASH = 0x8f8f306a409d08a43b82756f57beb839c3bf5bdd388ba0850d9d21f5c1d0c4dc; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_4_HASH = 0x9f510b8ccf7bbda5c9bd4333d7dcfc8e280aac9eaa9427dcd2f2ffca1fa7a833; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_5_HASH = 0x8599a8fc89aae892d7419812435866d931c9da91198b2f48d1d5318acb524461; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_6_HASH = 0xa0f24864e615845dfa14bee5d4fa28950fcdcf9d31893c68c26f4cfa13402a97; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_7_HASH = 0x621dd1d4cc7477f0587a16f5cd40ea8750209653e7275ccaea3b68623860181b; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_8_HASH = 0x51e570aee92287102d49a7493d337e8aa332c1bc639273de546b428973379fa9; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_9_HASH = 0xa9415346b8de2006517a8de68b2b4147a1d6fd91ce600f4040941627c1c8c3df; // keccack256("BulkTokenSetOfferApproval(TokenSetOfferApproval[2][2][2][2][2][2][2][2][2][2] tree)TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)") bytes32 constant BULK_TOKEN_SET_OFFER_APPROVAL_HEIGHT_10_HASH = 0xb75be8e10d720cf92518936f56974bdfa8e4a723902dd12e415ffba6eb6dde12; ``` ### Bulk Approval Signature Creation Creating a Bulk Order Signature requires you to create a merkle tree where the root is the bulk order, and the leaves are the approval types (e.g. `SaleApproval` or `ItemOfferApproval`). The tree must be **exactly** 2^N items, where N is the height of the tree calculated by taking the maximum of `log2(orderCount)`. If the amount of approvals is not exactly equal to the computed amount of required items, you would pad with empty approvals which would be unfillable. An example for an order with 7 items would be: Visual Order ``` log2(7) = 2.8 = 3 |---------------------------------------------------| 1 | [[[sa1,sa2], [sa3,sa4]], [[sa5,sa6], [sa7,pad]]] | |---------------------------------------------------| 2 | [[sa1,sa2], [sa3,sa4]] | [[sa5,sa6], [sa7,pad]] | |---------------------------------------------------| 3 | [sa1, sa2] | [sa3, sa4] | [sa5, sa6] | [sa7, pad] | ``` Example in JavaScript ```javascript // Bulk Order Type export const EIP_712_BULK_ORDER_TYPE = { BulkSaleApproval: [{ name: "tree", type: "SaleApproval[2][2][2]" }], SaleApproval: [ { name: "protocol", type: "uint8" }, { name: "cosigner", type: "address" }, { name: "seller", type: "address" }, { name: "marketplace", type: "address" }, { name: "fallbackRoyaltyRecipient", type: "address" }, { name: "paymentMethod", type: "address" }, { name: "tokenAddress", type: "address" }, { name: "tokenId", type: "uint256" }, { name: "amount", type: "uint256" }, { name: "itemPrice", type: "uint256" }, { name: "expiration", type: "uint256" }, { name: "marketplaceFeeNumerator", type: "uint256" }, { name: "maxRoyaltyFeeNumerator", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "masterNonce", type: "uint256" }, { name: "protocolFeeVersion", type: "uint256" } ] }; const bulkArray: SaleApproval[][][] = []; const exampleSaleApproval = { protocol: 0, cosigner: zeroAddress, seller: '0xabc...', marketplace: '0xbeef...', fallbackRoyaltyRecipient: zeroAddress, paymentMethod: zeroAddress, tokenAddress: '0x123...', tokenId: 1, amount: 1, itemPrice: 1e18, expiration: 9999999999999, marketplaceFeeNumerator: 0, maxRoyaltyFeeNumerator: 0, nonce: 0, masterNonce: 0, protocolFeeVersion: 0 }; const blankApproval = { protocol: 0, cosigner: zeroAddress, seller: zeroAddress, marketplace: zeroAddress, fallbackRoyaltyRecipient: zeroAddress, paymentMethod: zeroAddress, tokenAddress: zeroAddress, tokenId: 0, amount: 0, itemPrice: 0, expiration: 0, marketplaceFeeNumerator: 0, maxRoyaltyFeeNumerator: 0, nonce: 0, masterNonce: 0, protocolFeeVersion: 0 } bulkArray[0][0][0] = exampleSaleApproval; bulkArray[0][0][1] = exampleSaleApproval; bulkArray[0][1][0] = exampleSaleApproval; bulkArray[0][1][1] = exampleSaleApproval; bulkArray[1][0][0] = exampleSaleApproval; bulkArray[1][0][1] = exampleSaleApproval; bulkArray[1][1][0] = exampleSaleApproval; bulkArray[1][1][1] = blankApproval; const dataToSign = { BulkSaleApproval: { tree: bulkArray; } } const typedData = JSON.stringify({ primaryType: 'BulkSaleApproval', domain: domain, message: { tree: dataToSign }, types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' } ], ... types, } }); const params = [signerAddress, typedData]; const method = 'eth_signTypedData_v4'; const sig = await window.ethereum.request({ method, params }); // Generate tree const hashedData = dataToSign.flat(Infinity).map((saleApproval) => { const encodedData = encodeAbiParameters( parseAbiParameters( "bytes32 hash, uint8 protocol, address cosigner, address seller, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 maxRoyaltyFeeNumerator, uint256 nonce, uint256 masterNonce, uint256 protocolFeeVersion"), [ SALE_APPROVAL_TYPE_HASH, saleApproval.protocol, saleApproval.cosigner, saleApproval.seller, saleApproval.marketplace, saleApproval.fallbackRoyaltyRecipient, saleApproval.paymentMethod, saleApproval.tokenAddress, saleApproval.tokenId, saleApproval.amount, saleApproval.itemPrice, saleApproval.expiration, saleApproval.marketplaceFeeNumerator, saleApproval.maxRoyaltyFeeNumerator, saleApproval.nonce, saleApproval.masterNonce, saleApproval.protocolFeeVersion ] ); return keccak256(encodedData); }); const tree: MerkleTree = new MerkleTree(hashedData, keccak256, { complete: true, sort: false, hashLeaves: false, fillDefaultHash: keccak256(encodedNullSaleApproval) }); ``` ### Processing Bulk Orders When submitting an order that is part of a bulk approval, the index of the approval must be provided with the proof. This is used to determine the placing of the approval in order to recreate the tree and generate the correct hash. Using the previous example, orders are indexed as below: ``` bulkArray[0][0][0] == 0; bulkArray[0][0][1] == 1; bulkArray[0][1][0] == 2; bulkArray[0][1][1] == 3; bulkArray[1][0][0] == 4; bulkArray[1][0][1] == 5; bulkArray[1][1][0] == 6; bulkArray[1][1][1] == 7; ``` So to submit an order for `bulkArray[0][1][0]` you would submit as below: ```javascript const index = 2; const hexProof = tree.getHexProot(hashedData[index]); const order: AdvancedOrder = { saleDetails: order, signature: sig, cosignature: nullSig, permitContext: { permitProcessor: zeroAddress, permitNonce: BigInt(0), } } const proof: Proof = { proof: hexProof, orderIndex: index }; const feeOnTop: FeeOnTop = { recipient: zeroAddress, amount: BigInt(0), } const calldata = await publicClient.readContract({ address: PAYMENT_PROCESSOR_ENCODER_ADDRESS, abi: paymentProcessorEncoderAbi.abi, functionName: 'encodebuyListingAdvancedCalldata', args: [PAYMENT_PROCESSOR_ADDRESS, order, proof, feeOnTop] }); const { request } = await publicClient.simulateContract({ account: address, address: PAYMENT_PROCESSOR_ADDRESS, abi: paymentProcessorAbi.abi, functionName: 'buyListingAdvanced', args: [calldata] }); await walletClient.writeContract(request); ``` # Permits Payment Processor has support to use a permit processor like PermitC to handle approvals in a more advanced and secure way. To handle this, the taker must first give standard approval on their token to the permit processor contract before executing the order. A signature for the permit processor would utilize the `PermitTransferFromWithAdditionalData` on PermitC. ### Features - **Time Bound Approvals:** Approvals via PermitC include an expiration timestamp, allowing for an approval to be only set for a specific period of time. If it’s used past the expiration timestamp, it will be invalid. - **One Click Approval Revoke:** To quickly revoke all approvals, you can call a single function lockdown which will invalidate all outstanding approvals. - **Signature Based Approvals:** Signatures can be provided for operators to increase approval on chain for multi-use approvals, or permit transfer for one off signatures. - **Additional Data Validation:** Signatures can use the permitTransferFromWithAdditionalData calls to append additional validation logic, such as approving only a specific order or set of orders without opening approvals to other functions on the operator contract. - **Unordered Execution:** Nonces retain the same functionality as Permit2, meaning they can be approved and executed in any order and prevent replay. - **Order Based Approvals:** Reusable signatures scoped to a specific order ID to allow for partial trades / fills allowing for gasless outstanding orders to be leveraged. Useful in ERC1155 sales and ERC20 limit orders. ### Frontend Integration Frontends can directly integrate by prompting the maker with a signature that includes the `SaleApproval` or `OfferApproval` as additional data to PermitC. This will be provided to the taker to execute the order, and PermitC will be used to validate the signature and move the tokens. A mock JS integration would look like this: ```javascript const PERMIT_C_TYPE = { PermitTransferFromWithAdditionalData: [ { name: "token", type: "address" }, { name: "id", type: "uint256" }, { name: "amount", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "operator", type: "address" }, { name: "expiration", type: "uint256" }, { name: "masterNonce", type: "uint256" }, { name: "approval", type: "SaleApproval" }, ], SaleApproval: [ { name: "protocol", type: "uint8" }, { name: "cosigner", type: "address" }, { name: "seller", type: "address" }, { name: "marketplace", type: "address" }, { name: "fallbackRoyaltyRecipient", type: "address" }, { name: "paymentMethod", type: "address" }, { name: "tokenAddress", type: "address" }, { name: "tokenId", type: "uint256" }, { name: "amount", type: "uint256" }, { name: "itemPrice", type: "uint256" }, { name: "expiration", type: "uint256" }, { name: "marketplaceFeeNumerator", type: "uint256" }, { name: "maxRoyaltyFeeNumerator", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "masterNonce", type: "uint256" }, { name: "protocolFeeVersion", type: "protocolFeeVersion" } ] } const permitNonce = getNextPermitNonce(); const typedData = JSON.stringify({ primaryType: 'PermitTransferFromWithAdditionalData', domain: PERMIT_C_DOMAIN, message: { token: sa1.tokenAddress, id: sa1.tokenId, amount: sa1.amount, nonce: permitNonce, operator: PAYMENT_PROCESSOR_ADDRESS, expiration: sa1.expiration, masterNonce: sa1.masterNonce, approval: sa1 }, types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' } ], ... PERMIT_C_TYPE, } }); const params = [address, typedData]; const method = 'eth_signTypedData_v4'; const sig = await window.ethereum.request({ method, params }); setSignature(sig); const order: AdvancedOrder = { saleDetails: order1, signature: sig, cosignature: nullSig, permitContext: { permitProcessor: PERMIT_C_ADDRESS, permitNonce: BigInt(permitNonce), } } const nullFeeOnTop: FeeOnTop = { recipient: zeroAddress, amount: BigInt(0), }; const nullBulkOrder: Proof = { orderIndex: BigInt(0), proof: [], }; const calldata = await publicClient?.readContract({ address: PAYMENT_PROCESSOR_ENCODER_ADDRESS, abi: paymentProcessorEncoderAbi.abi, functionName: 'encodeBuyListingAdvancedCalldata', args: [PAYMENT_PROCESSOR_ADDRESS, order, nullBulkOrder, nullFeeOnTop] }); const {request} = await publicClient.simulateContract({ account: address, address: PAYMENT_PROCESSOR_ADDRESS, abi: paymentProcessorAbi.abi, functionName: 'buyListingAdvanced', args: [calldata] }); await walletClient.writeContract(request); ``` # Nonces Payment Processor uses two types of nonces to manage orders - master nonces and order nonces. Every order maker on Payment Processor has their own master nonce and set of order nonces. The master nonce defaults to zero for an order maker and allows the maker to cancel all open orders that were signed using that master nonce by calling revokeMasterNonce on Payment Processor. revokeMasterNonce will increment the maker's master nonce by one and emit a MasterNonceInvalidated event with the order maker's address and the master nonce that was revoked, the maker's new master nonce will be the revoked nonce plus one. Master nonce is part of an order signature and will result in all previously signed orders with that nonce to recover as a different signer and revert. An order nonce is a 32 byte integer that is used to prevent the replay of orders on Payment Processor and allow for orders to be explicitly cancelled prior to fulfillment by the order maker. When a nonce is consumed by fulfillment or cancellation it cannot be used again. ## How are nonces stored? Order nonces are stored in a bitmap data structure for each order maker where each storage slot is a "bucket" of 256 nonces where nonces 0-255 are in the first bucket, 256-511 are in the second bucket, and so forth. Nonces are packed this way for efficient use of storage within the EVM and to reduce gas costs on transaction execution. At a high level - consuming the first nonce in a bucket costs 20,000 gas units, each additional nonce consumed from the same bucket in the same transaction costs 100 gas units (a 99.5% savings over one slot per nonce or using nonces from different buckets), and an additional nonce consumed from a bucket that previously had a nonce consumed but in a different transaction costs 5,000 gas units (75% saving over one slot per nonce or using nonces from different buckets). Payment Processor does not account for the order book/marketplace in nonce validation - order books must generate nonces for users that will not overlap with nonces created by another order book to prevent fulfilled orders on one order book from interfering with open orders on the other. ## What is an ideal nonce? An ideal nonce for a Payment Processor order will include as many zero bytes in the 32 byte nonce as possible to save calldata gas cost (a zero byte in calldata costs 4 gas units while a non-zero byte costs 16 gas units), pack as many fillable order nonces into a 256 nonce "bucket" as possible, and not overlap with nonces used by other order books. To meet these objectives of an ideal nonce we recommend using the upper 4 bytes of the 32 byte nonce value as a marketplace differentiator and incrementing the nonce used for order signing by one for each marketplace as a user signs an order as a simple method for generating an order nonce. ### Example ```js maker = "0x1234...FFFF" orderbookDifferentiator = keccak256("YOUR_ORDER_BOOK") << 224 // 0x3cd2fd820000...0000 orderMakerNextOrderbookNonce = getNextNonce(orderbookDifferentiator, maker) // query database for next nonce, for example assume this value is 123 orderNonce = orderbookDifferentiator | orderMakerNextOrderbookNonce // 0x3cd2fd820000..007B ``` A more advanced solution could track prior nonces used for order signing where another factor on the order, such as expiration time or master nonce revocation, has made a prior order unfillable and re-use those nonces to better pack fulfilled nonces. # Contract Deployments Payment Processor v3.0.0 can be permissionlessly deployed by any wallet to the same addresses on any EVM compatible chain. #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Payment Processor V3.0.0 | `0x9a1D00000000fC540e2000560054812452eB5366` | | Payment Processor V3.0.0 Encoder | `0x9A1D00C3a699f491037745393a0592AC6b62421D` | | Payment Processor V3.0.0 Configuration | `0x9A1D00773287950891B8A48Be6f21e951EFF91b3` | | Payment Processor Module On Chain Cancellation | `0x9A1D005Da1E3daBCE14bc9734DEe692A8978c71C` | | Payment Processor Module Accept Offers | `0x9A1D00E769A108df1cbC3bFfcF867B64ba2E9eFf` | | Payment Processor Module Buy Listings | `0x9A1D00A68523b8268414E4406268c32EC83323A9` | | Payment Processor Module Sweeps | `0x9A1D008994E8f69C66d99B743BDFc6990a7801aB` | | Collection Settings Registry | `0x9A1D001A842c5e6C74b33F2aeEdEc07F0Cb20BC4` | ***Note: If Payment Processor is not yet deployed on your chain, use [the infrastructure deployment tool at developers.apptokens.com/infrastructure.](https://developers.apptokens.com/infrastructure)*** # Payment Processor V2 # Known Limitations Payment Processor was designed to maximize safety and efficency while enforcing creator-defined royalties. As a result, there are certain intentional limitations documented below that creators should be aware of. ## Push Payment Splitters Are Not Supported Push Payment Splitters are smart contracts that receive native token funds, split and redistribute them to one or more addresses within the same transaction they are received. Payment Processor limits the amount of gas that a payment recipient can utilize to 8,000 gas units which is not enough to operate a Push Payment Splitter. **Justification:** 1. ***Security*** - Forwarding of all available gas allows for recipients of transfers to execute additional, potentially malicious, logic. 2. ***Push Payments Are An Anti-pattern*** - The forwarding of native tokens to many destinations in a single transaction has long been considered an anti-pattern. Any one of the recipients could be a re-entrant attacker, or a destination could intentionally revert to grief all the other destinations and DoS a transaction. 3. ***Fairness*** - Without a gas limit, a push payment splitter could be used to do an arbitrary number of splits. We strongly believe it is unfair to the order takers for them to pay an additional ~10K gas units per split destination with an unbounded limit on the number of destinations. If all transaction gas is forwarded with a payment to a push payment splitter with ten recipients the average transaction cost would double. This is not good for traders executing orders and not efficient for the blockchain. **Recommendations for creators:** Use an OpenZeppelin pull payment splitter as your royalty recipient. It is audited and easy to deploy: [OpenZeppelin PaymentSplitter](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.5/contracts/finance/PaymentSplitter.sol) ## Batch Listings And Transfers Are Not Supported Batch listings and transfers have been used to steal millions of dollars worth of NFTs in single transactions from one errant click by a token owner. Payment Processor was intentionally designed to require a signature on each item being listed to provide more secure trading infrastructure. Order takers may still execute orders in bulk. # Payment Settings The simplest way to manage your collection's payment settings is through [developers.freenft.com](https://developers.freenft.com). For creators that would like to build their own integrations for managing collection payment settings - the sections below describe the functions and parameters that Payment Processor uses to apply rules to collection orders. ## Payment Settings Functions ### createPaymentMethodWhitelist Callable by any account to create a new payment method whitelist. The whitelist will be owned by the address that calls the function. **Note:** Due to contract size limitations and for gas efficiency the `createPaymentMethodWhitelist` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodecreatepaymentmethodwhitelistcalldata) for more information. ```solidity function createPaymentMethodWhitelist( string calldata whitelistName ) external returns (uint32 paymentMethodWhitelistId); ``` ### reassignOwnershipOfPaymentMethodWhitelist Callable by the owner of the whitelist to assign ownership of the whitelist to a new account. **Note:** Due to contract size limitations and for gas efficiency the `reassignOwnershipOfPaymentMethodWhitelist` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodereassignownershipofpaymentmethodwhitelistcalldata) for more information. ```solidity function reassignOwnershipOfPaymentMethodWhitelist( uint32 id, address newOwner ) external; ``` ### renounceOwnershipOfPaymentMethodWhitelist Callable by the owner of the whitelist to renounce ownership of the whitelist. Whitelists with the owner renounced can not be modified. **Note:** Due to contract size limitations and for gas efficiency the `renounceOwnershipOfPaymentMethodWhitelist` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encoderenounceownershipofpaymentmethodwhitelistcalldata) for more information. ```solidity function renounceOwnershipOfPaymentMethodWhitelist(uint32 id) external; ``` ### whitelistPaymentMethod Callable by the owner of the whitelist to add a new payment method. **Note:** Due to contract size limitations and for gas efficiency the `whitelistPaymentMethod` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodewhitelistpaymentmethodcalldata) for more information. ```solidity function whitelistPaymentMethod( uint32 paymentMethodWhitelistId, address paymentMethod ) external; ``` ### unwhitelistPaymentMethod Callable by the owner of the whitelist to remove a payment method. **Note:** Due to contract size limitations and for gas efficiency the `unwhitelistPaymentMethod` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodeunwhitelistpaymentmethodcalldata) for more information. ```solidity function unwhitelistPaymentMethod( uint32 paymentMethodWhitelistId, address paymentMethod ) external; ``` ### setCollectionPaymentSettings Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function will configure the payment settings rules to apply to all orders executed on Payment Processor. - `tokenAddress`: Contract address the payment settings are being set for. - `paymentSettings`: Value from the [PaymentSettings](#paymentsettings) enum that defines the type of rules to apply to orders. Creators may pause trading on their collection by setting this value to `Paused`. - `paymentMethodWhitelistId`: Id for a whitelist that was created with [createPaymentMethodWhitelist](#createpaymentmethodwhitelist). This is only used when `paymentSettings` is set to `CustomPaymentMethodWhitelist`. - `constrainedPricingPaymentMethod`: ERC20 token address for the payment method to use when `paymentSettings` is set to `PricingConstraints`. - `royaltyBackfillNumerator`: Royalty percentage to apply to a sale for a collection that does not utilize ERC2981 for on-chain royalties. Denominated in BPS (ex. value of 250 is 2.5% royalty, value of 1,000 is 10% royalty) with a maximum allowed value of 10,000. - `royaltyBackfillReceiver`: Recipient of royalty payments for a collection that does not utilize ERC2981 for on-chain royalties. - `royaltyBountyNumerator`: Percentage of the royalty amount to pay to the marketplace as a reward. Denominated in BPS (ex. value of 2,500 is 25% of the royalty amount, value of 5,000 is 50% of the royalty amount) with a maximum allowed value of 10,000. - `exclusiveBountyReceiver`: Restricts royalty bounty payments to a single marketplace set by this parameter. If exclusiveBountyReceiver is set to `address(0)` any marketplace can receive royalty bounties for the collection. - `blockTradesFromUntrustedChannels`: Boolean flag that when set to true will require order taker transactions to route through a [Trusted Forwarder](../../../trusted-forwarder/overview) that has been approved for the collection using the [addTrustedChannelForCollection](#addtrustedchannelforcollection) function. - `blockBannedAccounts`: Boolean flag that when set to true will block accounts that are banned by the collection owner from being the maker or taker on an order. **Note:** Due to contract size limitations and for gas efficiency the `setCollectionPaymentSettings` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodesetcollectionpaymentsettingscalldata) for more information. ```solidity function setCollectionPaymentSettings( address tokenAddress, PaymentSettings paymentSettings, uint32 paymentMethodWhitelistId, address constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool blockBannedAccounts ) external; ``` ### setCollectionPricingBounds Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function sets the floor and ceiling price for a collection that is applied when the collection's PaymentSettings is set to `PricingConstraints`. Token pricing bounds set in [setTokenPricingBounds](#settokenpricingbounds) take precedence over collection bounds. See [PricingBounds](#pricingbounds) for struct details. **Note:** Due to contract size limitations and for gas efficiency the `setCollectionPricingBounds` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodesetcollectionpricingboundscalldata) for more information. ```solidity function setCollectionPricingBounds( address tokenAddress, PricingBounds calldata pricingBounds ) external; ``` ### setTokenPricingBounds Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function sets the floor and ceiling price for specific tokens in the collection that are applied when the collection's PaymentSettings is set to `PricingConstraints`. Token pricing bounds take precedence over collection bounds that are set in [setCollectionPricingBounds](#setcollectionpricingbounds). See [PricingBounds](#pricingbounds) for struct details. **Note:** Due to contract size limitations and for gas efficiency the `setTokenPricingBounds` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodesettokenpricingboundscalldata) for more information. ```solidity function setTokenPricingBounds( address tokenAddress, uint256[] calldata tokenIds, PricingBounds[] calldata pricingBounds ) external; ``` ### addTrustedChannelForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function approves a channel to be allowed to execute orders for the collection when `blockTradesFromUntrustedChannels` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). See [Trusted Forwarder](../../../trusted-forwarder/overview) for more information on channels. **Note:** Due to contract size limitations and for gas efficiency the `addTrustedChannelForCollection` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodeaddtrustedchannelforcollectioncalldata) for more information. ```solidity function addTrustedChannelForCollection( address tokenAddress, address channel ) external; ``` ### removeTrustedChannelForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function removes a channel's approval to execute orders for the collection when `blockTradesFromUntrustedChannels` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). See [Trusted Forwarder](../../../trusted-forwarder/overview) for more information on channels. **Note:** Due to contract size limitations and for gas efficiency the `removeTrustedChannelForCollection` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encoderemovetrustedchannelforcollectioncalldata) for more information. ```solidity function removeTrustedChannelForCollection( address tokenAddress, address channel ) external; ``` ### addBannedAccountForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function bans an account from being the maker or taker for an order for the collection when `blockBannedAccounts` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). **Note:** Due to contract size limitations and for gas efficiency the `addBannedAccountForCollection` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encodeaddbannedaccountforcollectioncalldata) for more information. ```solidity function addBannedAccountForCollection( address tokenAddress, address account ) external; ``` ### removeBannedAccountForCollection Callable by the token contract, the owner of the token contract or any account assigned the default admin role for the token contract. This function lifts a ban for an account allowing the account to be the maker or taker for an order for the collection when `blockBannedAccounts` is set to true in [setCollectionPaymentSettings](#setcollectionpaymentsettings). **Note:** Due to contract size limitations and for gas efficiency the `removeBannedAccountForCollection` function on Payment Processor must be called with a single calldata byte array that contains what would be the `msg.data` for the below function. See [Encoding Details](./calldata-encoding#encoderemovebannedaccountforcollectioncalldata) for more information. ```solidity function removeBannedAccountForCollection( address tokenAddress, address account ) external; ``` ## Data Structures ### PricingBounds | isSet | floorPrice | ceilingPrice | |-------|------------|--------------| | bool | uint120 | uint120 | - isSet: Boolean true/false flag to indicate the pricing bounds have been set. Distinguishes set zero-value bounds from unset bounds that have default values of zero in the storage slot. - floorPrice: Minimum value to accept for a sales price without rejecting the sale. - ceilingPrice: Maximum value to accept for a sales price without rejecting the sale. ```solidity struct PricingBounds { bool isSet; uint120 floorPrice; uint120 ceilingPrice; } ``` ## Enums ### PaymentSettings - `DefaultPaymentMethodWhitelist` utilizes Payment Processor's default list which includes native token (ex ETH on Ethereum, MATIC on Polygon), wrapped native token, wrapped ETH (if native token is not ETH), and USDC. - `AllowAnyPaymentMethod` allows any payment method (native token or ERC20) for orders. - `CustomPaymentMethodWhitelist` requires the payment method to be one on the whitelist specified in the `paymentMethodWhitelistId` parameter - `PricingConstraints` restricts payments to a single ERC20 specified in the `constrainedPricingPaymentMethod` parameter and enforces sales price is within the pricing bounds specified for a token or the collection (if bounds are not defined for the specific token). Bounds are set with the [setCollectionPricingBounds](#setcollectionpricingbounds) and [setTokenPricingBounds](#settokenpricingbounds) functions. - `Paused` disables all trading on the collection. f ```solidity enum PaymentSettings { DefaultPaymentMethodWhitelist, AllowAnyPaymentMethod, CustomPaymentMethodWhitelist, PricingConstraints, Paused } ``` # Encoding Function Call Parameters In order to not overflow smart contract bytecode size limits, Payment Processor is split into smaller library modules. When making calls to Payment Processor, these calls are forwarded to the corresponding module and implementation function using `DELEGATECALL`. For gas efficiency purposes, the function calldata must be passed in as a raw calldata byte array. Payment Processor is deployed with a helper contract called the PaymentProcessorEncoder. It accepts the expected function implementation parameters and encodes them as a single calldata byte array that should be passed into Payment Processor when making calls to fill orders. The subsections below detail each PaymentProcessorEncoder function. ### encodeCreatePaymentMethodWhitelistCalldata Used to encode calldata for Payment Processor `createPaymentMethodWhitelist` function. ```solidity function encodeCreatePaymentMethodWhitelistCalldata( address paymentProcessorAddress, string calldata whitelistName ) external view returns (bytes memory); ``` ### encodeReassignOwnershipOfPaymentMethodWhitelistCalldata Used to encode calldata for Payment Processor `reassignOwnershipOfPaymentMethodWhitelist` function. ```solidity function encodeReassignOwnershipOfPaymentMethodWhitelistCalldata( address paymentProcessorAddress, uint32 id, address newOwner ) external view returns (bytes memory) ``` ### encodeRenounceOwnershipOfPaymentMethodWhitelistCalldata Used to encode calldata for Payment Processor `renounceOwnershipOfPaymentMethodWhitelist` function. ```solidity function encodeRenounceOwnershipOfPaymentMethodWhitelistCalldata( address paymentProcessorAddress, uint32 id ) external view returns (bytes memory) ``` ### encodeWhitelistPaymentMethodCalldata Used to encode calldata for Payment Processor `whitelistPaymentMethod` function. ```solidity function encodeWhitelistPaymentMethodCalldata( address paymentProcessorAddress, uint32 paymentMethodWhitelistId, address paymentMethod ) external view returns (bytes memory); ``` ### encodeUnwhitelistPaymentMethodCalldata Used to encode calldata for Payment Processor `unwhitelistPaymentMethod` function. ```solidity function encodeUnwhitelistPaymentMethodCalldata( address paymentProcessorAddress, uint32 paymentMethodWhitelistId, address paymentMethod ) external view returns (bytes memory); ``` ### encodeSetCollectionPaymentSettingsCalldata Used to encode calldata for Payment Processor `setCollectionPaymentSettings` function. ```solidity function encodeSetCollectionPaymentSettingsCalldata( address paymentProcessorAddress, address tokenAddress, PaymentSettings paymentSettings, uint32 paymentMethodWhitelistId, address constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool blockBannedAccounts ); ``` ### encodeSetCollectionPricingBoundsCalldata Used to encode calldata for Payment Processor `setCollectionPricingBounds` function. ```solidity function encodeSetCollectionPricingBoundsCalldata( address paymentProcessorAddress, address tokenAddress, PricingBounds calldata pricingBounds ); ``` ### encodeSetTokenPricingBoundsCalldata Used to encode calldata for Payment Processor `setTokenPricingBounds` function. ```solidity function encodeSetTokenPricingBoundsCalldata( address paymentProcessorAddress, address tokenAddress, uint256[] calldata tokenIds, PricingBounds[] calldata pricingBounds ); ``` ### encodeAddTrustedChannelForCollectionCalldata Used to encode calldata for Payment Processor `addTrustedChannelForCollection` function. ```solidity function encodeAddTrustedChannelForCollectionCalldata( address paymentProcessorAddress, address tokenAddress, address channel ) external view returns (bytes memory); ``` ### encodeRemoveTrustedChannelForCollectionCalldata Used to encode calldata for Payment Processor `removeTrustedChannelForCollection` function. ```solidity function encodeRemoveTrustedChannelForCollectionCalldata( address paymentProcessorAddress, address tokenAddress, address channel ) external view returns (bytes memory); ``` ### encodeAddBannedAccountForCollectionCalldata Used to encode calldata for Payment Processor `addBannedAccountForCollection` function. ```solidity function encodeAddBannedAccountForCollectionCalldata( address paymentProcessorAddress, address tokenAddress, address account ) external view returns (bytes memory); ``` ### encodeRemoveBannedAccountForCollectionCalldata Used to encode calldata for Payment Processor `removeBannedAccountForCollection` function. ```solidity function encodeRemoveBannedAccountForCollectionCalldata( address paymentProcessorAddress, address tokenAddress, address account ) external view returns (bytes memory); ``` # Glossary Here are some helpful terms to keep in mind. - **Payment Processor**: An NFT exchange protocol built by creators, for creators. Built for trading of ERC721-C and ERC1155-C tokens, but backwards compatible to support trading of any ERC721 or ERC1155 token as well. Analagous to Blur Marketplace and Seaport exchange protocols, but built entirely around honoring fully on-chain programmable royalties. Also known as `PaymentProcessor`. - **PaymentProcessor**: Shorthand for `Payment Processor`. - **Payment Processor Encoder**: A helper contract deployed alongside Payment Processor that integrated marketplaces use to format Payment Processor function calldata. - **Maker(s)**: Create buying or selling orders that are not carried out immediately. For example, "sell NFT `A` at a price of $100" or "buy NFT `B` for $100". This creates liquidity, meaning that it is easier for others to instantly buy or sell NFTs when the conditions are met. When Bob lists an NFT he owns for sale, Bob is considered maker of the order. Similarly, when Bob offers to buy an NFT, Bob is considered the maker of the order. - **Taker(s)**: The entities that buy or sell instantly are called takers. In other words, the takers fill the orders created by the makers. When Alice executes and order to buy Bob's listing, Alice is considered the taker of the order. Similarly, when Alice accepts an offer from Bob to buy an NFT she owns, Alice is considered the taker of the order. - **Order(s)**: Payment Processor supports two broad categories of orders and four types: - **Standard Order**: Any listing or offer that may be filled directly by a taker. Standard orders, when cancelled, must be cancelled on-chain by the order maker at their own gas expense. Standard orders are not susceptible to censorship provided they are accessible through an orderbook API. - **Cosigned Order**: Any listing or offer that must be cosigned by another party in order to be filled by a taker. Cosigned orders, when cancelled, can be gaslessly cancelled off-chain. Order-book providers that support cosigned orders must properly secure and automate the co-signing process. The order-book provider must guarantee that no cosignature can be generated for a previously cancelled order. Similarly, the order-book provider must guarantee availability of cosignatures to takers such that orders can always be filled uninterrupted. The use of cosigned orders SHOULD be at the order maker's discretion. Pros include cheaper, faster cancellation/replacement of orders without incurring transaction gas costs. Cons include censorship/denial of service risks. - **Listing**: An order (standard or cosigned) to sell an NFT once filled by a taker. Listings are gaslessly signed off-chain by the owner of the NFT. - **Item Offer**: An order (standard or cosigned) to buy a single, specific NFT once filled by a taker. Item offers apply to exactly one specific token id in a collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing the NFT. - **Collection Offer**: An order (standard or cosigned) to buy any NFT belonging to a specific collection once filled by a taker. Collection offers apply to any token id for a specific collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing an NFT. - **Token Set Offer**: An order (standard or cosigned) to buy a single NFT from a subset of token ids in a specific collection once filled by a taker. Token set offers apply to a subset of token ids for a specific collection. Offers are gaslessly signed off-chain by one or more parties interested in purchasing an NFT. - **Beneficiary**: The address of the account that receives the NFT/item when an order is filled. The buyer and beneficiary can be, but don't have to be the same account. Beneficiaries are acknowledged and signed in offer orders when an offer is made. Alternately, the beneficiary is designated and signed in the calldata of the buy transaction when the taker buys a listing. - **Marketplace Fee**: A fee paid to the primary marketplace where an order was made. The marketplace fee is acknowledged in signed orders. - **Fee On Top**: A `fee on top` is typically reserved for a marketplace or specialized wallet that finds orders filled by the taker. This taker marketplace fee is an optional fee paid by the taker in excess of the items' prices in one or more orders. - **Royalty Bounty**: A percentage, optionally paid out of a creator's royalty on each trade, to the marketplace where the NFT order was made. Royalty bounties are optional, and creators can configure a percentage from 0-100% to share with marketplaces that provide liquidity. Royalty bounties are a powerful tool to align incentives between creators and marketplaces. # Events Payment Processor emits several indexible events that should be indexed by off-chain order books and analytics platforms. ## Order Fill Events Off-chain order books and analytics platforms can track the following events for trading volume and metrics, as well as trades by user. ```solidity event BuyListingERC721( address indexed buyer, address indexed seller, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 salePrice); event BuyListingERC1155( address indexed buyer, address indexed seller, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 amount, uint256 salePrice); event AcceptOfferERC721( address indexed seller, address indexed buyer, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 salePrice); event AcceptOfferERC1155( address indexed seller, address indexed buyer, address indexed tokenAddress, address beneficiary, address paymentCoin, uint256 tokenId, uint256 amount, uint256 salePrice); ``` ## Order Cancellation Events Off-chain order books SHOULD index the following events to track what orders have been filled or cancelled and which nonces are un-available. ```solidity event MasterNonceInvalidated( uint256 indexed nonce, address indexed account); event NonceInvalidated( uint256 indexed nonce, address indexed account, bool wasCancellation); event OrderDigestInvalidated( bytes32 indexed orderDigest, address indexed account, bool wasCancellation); ``` ## Cosigner Events Off-chain order books SHOULD index the following event to track when a cosigner has been destroyed and filter any open orders that require a cosignature from the cosigner. ```solidity event DestroyedCosigner(address indexed cosigner); ``` ## Collection Payment Settings Events Off-chain order books SHOULD index collection payment settings events to track allowed payment methods, backfilled royalties, and royalty bounties. ```solidity event UpdatedCollectionPaymentSettings( address indexed tokenAddress, PaymentSettings paymentSettings, uint32 indexed paymentMethodWhitelistId, address indexed constrainedPricingPaymentMethod, uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver, uint16 royaltyBountyNumerator, address exclusiveBountyReceiver, bool blockTradesFromUntrustedChannels, bool blockBannedAccounts); ``` ## Custom Payment Method Whitelist Events Off-chain order books SHOULD index payment method whitelists by id. By doing so, when a collection applies a custom payment method whitelist id, exchanges can be informed about which payment methods are allowed for a give collection. ```solidity event CreatedPaymentMethodWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed whitelistOwner, string whitelistName); event PaymentMethodAddedToWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); event PaymentMethodRemovedFromWhitelist( uint32 indexed paymentMethodWhitelistId, address indexed paymentMethod); event ReassignedPaymentMethodWhitelistOwnership( uint32 indexed id, address indexed newOwner); ``` ## Bounded Pricing Events Off-chain order books SHOULD index pricing bounds events to track min and max allowable sale prices for collections that implement pricing constraints. ```solidity event UpdatedCollectionLevelPricingBoundaries( address indexed tokenAddress, uint256 floorPrice, uint256 ceilingPrice); event UpdatedTokenLevelPricingBoundaries( address indexed tokenAddress, uint256 indexed tokenId, uint256 floorPrice, uint256 ceilingPrice); ``` ## Trusted Channel Events Off-chain order books SHOULD index trusted channel events to be used in conjunction with the [`Collection Payment Settings Events`](#collection-payment-settings-events) to determine the allowed channels for a collection if `blockTradesFromUntrustedChannels` is set to `true`. ```solidity event TrustedChannelAddedForCollection( address indexed tokenAddress, address indexed channel); event TrustedChannelRemovedForCollection( address indexed tokenAddress, address indexed channel); ``` ## Banned Account Events Off-chain order books SHOULD index banned account events to be used in conjunction with the [`Collection Payment Settings Events`](#collection-payment-settings-events) to determine if a maker or taker is allowed to execute an order when `blockBannedAccounts` is set to `true`. ```solidity event BannedAccountAddedForCollection( address indexed tokenAddress, address indexed account); event BannedAccountRemovedForCollection( address indexed tokenAddress, address indexed account); ``` # Data Structures The following subsections detail the data structures needed for the creation of and filling of orders in Payment Processor. Note that [maker signature formats vary by order-type](#maker-signature-formats). ### SignatureECDSA | V | R | S | |-------|---------|---------| | uint8 | bytes32 | bytes32 | - V: The `v` component of an ECDSA signature. - R: The `r` component of an ECDSA signature. - S: The `s` component of an ECDSA signature. *[Note: For a detailed explanation of ECDSA signatures read this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).* ### Cosignature | Signer | Taker | Expiration | V | R | S | |---------|---------|------------|-------|---------|---------| | address | address | uint256 | uint8 | bytes32 | bytes32 | - Signer: The co-signer designated and acknowledged in the order maker's signature approving the order. MUST be an EOA, or `address(0)` when the co-signature is empty. It is expected that marketplaces implementing co-signing MUST expose an API enabling the taker to retrieve a co-signature approving the fill of co-signed orders. - Taker: The address of the account filling the co-signed order. May be an EOA or Smart Contract account, or `address(0)` when the co-signature is empty. A marketplace's co-signing API service MUST acknowledge the taker address in the co-signature. - Expiration: The unix timestamp (in seconds) when the co-signature expires, or `0` when the co-signature is empty. A marketplace's co-signing API should use the current unix timestamp and add a validity time that is acknowledged in the co-signature. If the validity time is too short, there might not be sufficient time for the fill transaction to be confirmed on-chain, possibly resulting in failed fill transactions. However, the validity time should also not be too long, as a taker could sit on a fill transaction for quite a while. It is left up to the marketplace to determine an appropriate validity time. - V: The `v` component of the co-signer's signature. - R: The `r` component of the co-signer's signature. - S: The `s` component of the co-signer's signature. *Note: The co-signature is generated when the co-signer creates an ECDSA signature of the [Cosignature](#cosignature-format) typed data message.* ### FeeOnTop A `fee on top` is typically reserved for a marketplace or specialized wallet that found the order filled by the taker. This taker marketplace fee is an optional fee paid by the taker in excess of the items' prices in one or more orders. When the maker and taker marketplace is the same, it is strongly encouraged not to apply this fee, as the fee can already be assessed in the maker fee. Note that the `fee on top` is paid in the same currency as the order's payment method. | Recipient | Amount | |-----------|---------| | address | uint256 | - Recipient: The address to which the fee is paid, or `address(0)` when the fee is empty. - Amount: The absolute amount of the fee, in wei, or `0` when the fee is empty. Note that the fee amount is not permitted to exceed the item sale price of any given order. ### TokenSetProof Token set proofs are required to fill token set offers on a collection. A token set offer is a merkle tree where the leaf data is the keccak256 hash of the collection address and token id. ``` leafHash = keccak256(abi.encode(collectionAddress, tokenId)); ``` | Root Hash | Proof | |-----------|-----------| | bytes32 | bytes32[] | - Root Hash: The root hash of the merkle tree containing 2 or more collection/token id leaf nodes, or `bytes32(0)` when the order being filled is not a token set offer. - Proof: The merkle proof for the specific collection/token id leaf node being filled, , or `bytes32[](0)` when the order being filled is not a token set offer. ### Order This data structure is used to fill all single and bulk orders that are not sweep orders. | Protocol | Maker | Beneficiary | Marketplace | Fallback Royalty Recipient | Payment Method | Token Address | Token Id | Amount | Item Price | Nonce | Expiration | Marketplace Fee Numerator | Max Royalty Fee Numerator | Requested Fill Amount | Minimum Fill Amount | |----------|---------|-------------|-------------|----------------------------|----------------|---------------|----------|---------|------------|---------|------------|---------------------------|---------------------------|-----------------------|---------------------| |uint8 | address | address | address | address | address | address | uint256 | uint248 | uint256 | uint256 | uint256 | uint256 | uint256 | uint248 | uint248 | - Protocol: `0` for `ERC721_FILL_OR_KILL`, `1` for `ERC1155_FILL_OR_KILL`, or `2` for `ERC1155_FILL_PARTIAL` collections. `FILL_PARTIAL` means an order is partially fillable across multiple trades, while `FILL_OR_KILL` means the entire amount specified in the order must be filled in a single transaction. - Maker: The address of the account the created the order. May be an EOA or Smart Contract account. When the order was a listing, the maker is the seller of the item. When the order was an offer, the maker is the buyer of the item. - Beneficiary: The address of the account that receives the item when the order is filled. When the order was a listing, the taker (`msg.sender`) can either buy with themselves as the beneficiary, or another account. When the order was an offer, the maker would be the buyer, and the beneficiary address can either be the maker's account or another account. For example, the buyer could be a user's hot wallet, and the beneficiary could be the user's cold storage wallet. - Marketplace: The address to which the primary (maker) marketplace fee should be paid, or `address(0)` if the maker marketplace charges no platform fees. Note that for collections that offer non-exclusive royalty bounties, the maker marketplace also receives a royalty bounty paid out of creator royalties. For collections that offer exclusive royalty bounties, the maker marketplace receives a royalty bounty only if it matches the exclusive royalty bounty recipient designated by the collection creator. - Fallback Royalty Recipient: Used to allow marketplaces to support royalties for collections that do not implement ERC-2981, do not have a contract owner and do not have a user with default admin role. Fallback royalties are calculated using the `Max Royalty Fee Numerator` for the order. - Payment Method: The address of the ERC-20 coin used to fill the trade, or `address(0)` for the native currency of the chain the trade executed on. For example, `address(0)` denotes ETH on ETH Mainnet, and Matic on Polygon Mainnet. - Token Address: The address of the collection. - Token Id: The id of the token (if collection is ERC721), or the token type id of the token (if collection is ERC1155). - Amount: The number of tokens. MUST be `1` if collection is ERC721, and MUST be greater than or equal to `1` if collection is ERC1155. - Item Price: The price of the token(s) in the order. - Nonce: A unique identifier to the order that prevents replay attacks. Nonce applies only to standard signed orders and may not be re-used. For co-signed order, nonce should be set to `0`. Note: It is easier to generate a random nonce than attempt to keep track of nonces that have been used. However, be aware that a gas optimization is in place such that filled or cancelled nonces are tracked in a bitmap. Nonces 0-255, 256-511, 512-767, etc are stored in the same slot. Each maker address has its own set of nonces generated for gasless listings such that `nonce 1234` is stored separately for makers `address(5678)` and `address(abcd)` but storage of the same nonce for the same maker is common across all marketplaces. It is possible that marketplaces will have no knowledge of nonces used in outstanding order signatures. Marketplaces should employ a nonce sequencing methodology that reduces the probability of overlapping nonce usage with other marketplaces. Examples of this could be a fixed set of upper bits for all orders placed through that marketplace where nonces `0x12340000...0000` to `0x1234FFFF...FFFF` relate to a specific marketplace or the development of a common nonce issuance service that tracks all nonces used by a maker. - Expiration: The unix timestamp (in seconds) when the maker's order signature expires. A marketplace's order making API should use the current unix timestamp and add a user-defined validity time that is acknowledged in the maker's signature. - Marketplace Fee Numerator: Marketplace fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. - Max Royalty Fee Numerator: Maximum approved royalty fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. When requesting the order signature from the order maker, the marketplace MUST first attempt to read the royalties for the individual token using the EIP-2981 `royaltyInfo` function call on the collection. If `royaltyInfo` raises an exception (likely because it is unimplemented), the marketplace MUST attempt to determine if royalties have been backfilled by calling the `collectionRoyaltyBackfillSettings` function on Payment Processor. If no on-chain royalties are present, this may be set to `0`. - Requested/Minimum Fill Amount: When a fill transaction is submitted, this is the number of items the taker wishes to fill. If insufficient items remain from the partially filled order, the remaining number of items is filled instead, provided the number of remaining items its greater than or equal to the Minimum Fill Amount. ### Sweep Order This data structure is used to fill sweep orders, and represents the shared values that apply to all order in the sweep. | Protocol | Token Address | Payment Method | Beneficiary | |----------| --------------|----------------|-------------| | uint8 | address | address | address | - Protocol: `0` for ERC721 or `1` for ERC1155 collections. - Token Address: The address of the collection. - Payment Method: The address of the ERC-20 coin used to fill the trade, or `address(0)` for the native currency of the chain the trade executed on. For example, `address(0)` denotes ETH on ETH Mainnet, and Matic on Polygon Mainnet. - Beneficiary: The address of the account that receives the item when the order is filled. ### Sweep Item This data structure is used to fill sweep orders, and represents the values that apply to individual orders in the sweep. | Maker | Marketplace | Fallback Royalty Recipient | Token Id | Amount | Item Price | Nonce | Expiration | Marketplace Fee Numerator | Max Royalty Fee Numerator | |---------|-------------|----------------------------|----------|------------|------------|---------|------------|---------------------------|---------------------------| | address | address | address | uint256 | uint248 | uint256 | uint256 | uint256 | uint256 | uint256 | - Maker: The address of the account the created the order. May be an EOA or Smart Contract account. When the order was a listing, the maker is the seller of the item. When the order was an offer, the maker is the buyer of the item. - Marketplace: The address to which the primary (maker) marketplace fee should be paid, or `address(0)` if the maker marketplace charges no platform fees. Note that for collections that offer non-exclusive royalty bounties, the maker marketplace also receives a royalty bounty paid out of creator royalties. For collections that offer exclusive royalty bounties, the maker marketplace receives a royalty bounty only if it matches the exclusive royalty bounty recipient designated by the collection creator. - Fallback Royalty Recipient: Used to allow marketplaces to support royalties for collections that do not implement ERC-2981, do not have a contract owner and do not have a user with default admin role. Fallback royalties are calculated using the `Max Royalty Fee Numerator` for the sweep item. - Token Id: The id of the token (if collection is ERC721), or the token type id of the token (if collection is ERC1155). - Amount: The number of tokens. MUST be `1` if collection is ERC721, and MUST be greater than or equal to `1` if collection is ERC1155. - Item Price: The price of the token(s) in the order. - Nonce: A unique identifier to the order that prevents replay attacks. Nonce applies only to standard signed orders and may not be re-used. For co-signed order, nonce should be set to `0`. Note: It is easier to generate a random nonce than attempt to keep track of nonces that have been used. However, be aware that a gas optimization is in place such that filled or cancelled nonces are tracked in a bitmap. Nonces 0-255, 256-511, 512-767, etc are stored in the same slot. Each maker address has its own set of nonces generated for gasless listings such that `nonce 1234` is stored separately for makers `address(5678)` and `address(abcd)` but storage of the same nonce for the same maker is common across all marketplaces. It is possible that marketplaces will have no knowledge of nonces used in outstanding order signatures. Marketplaces should employ a nonce sequencing methodology that reduces the probability of overlapping nonce usage with other marketplaces. Examples of this could be a fixed set of upper bits for all orders placed through that marketplace where nonces `0x12340000...0000` to `0x1234FFFF...FFFF` relate to a specific marketplace or the development of a common nonce issuance service that tracks all nonces used by a maker. - Expiration: The unix timestamp (in seconds) when the maker's order signature expires. A marketplace's order making API should use the current unix timestamp and add a user-defined validity time that is acknowledged in the maker's signature. - Marketplace Fee Numerator: Marketplace fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. - Max Royalty Fee Numerator: Maximum approved royalty fee percentage in bips. Should be in range 0-10,000, as denominator is 10,000. 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on. When requesting the order signature from the order maker, the marketplace MUST first attempt to read the royalties for the individual token using the EIP-2981 `royaltyInfo` function call on the collection. If `royaltyInfo` raises an exception (likely because it is unimplemented), the marketplace MUST attempt to determine if royalties have been backfilled by calling the `collectionRoyaltyBackfillSettings` function on Payment Processor. If no on-chain royalties are present, this may be set to `0`. # Listings A listing is an order (standard or cosigned) to sell an NFT once filled by a taker. Listings are gaslessly signed off-chain by the owner/seller of the NFT. ## Notional Exchange Listing Flow 1. Maker browses their items and selects an item to list for sale. 2. Maker inputs: Listing Currency/Payment Method, Price, Validity Time. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage and on-chain royalty fee (display sale price, fees, and net seller proceeds) 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 5. Signature Prompt for [SaleApproval](#sale-approval) signature. 6. Order and signature submitted to order book. ## Sale Approval When listing an individual NFT for sale the seller/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the order is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js SaleApproval( uint8 protocol, address cosigner, address seller, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 maxRoyaltyFeeNumerator, uint256 nonce, uint256 masterNonce ) ``` # Offers Offers are gaslessly signed off-chain by one or more parties interested in purchasing the NFT. Payment Processor support three offer types: - **Item Offer**: An order (standard or cosigned) to buy a single, specific NFT once filled by a taker. Item offers apply to exactly one specific token id in a collection. - **Collection Offer**: An order (standard or cosigned) to buy any NFT belonging to a specific collection once filled by a taker. Collection offers apply to any token id for a specific collection. - **Token Set Offer**: An order (standard or cosigned) to buy a single NFT from a subset of token ids in a specific collection once filled by a taker. Token set offers apply to a subset of token ids for a specific collection. ## Notional Exchange Offer Flow ### Item Offer 1. Maker browses a collection and selects a single item to make an offer on. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds) 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). For standard orders, a nonce is generated. 5. Signature Prompt for either [ItemOfferApproval](#item-offer-approval). 6. Order and signature submitted to order book. ### Collection Offer 1. Maker browses a collection and chooses to make a collection offer for any item in the collection. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds). 4. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 5. Signature Prompt for either [CollectionOfferApproval](#collection-offer-approval). 6. Order and signature submitted to order book. ### Token Set Collection Offer This can take a few forms depending on how the marketplace chooses to implement this. This can take the form of offers on traits, offers on tokens that have not been stolen, etc. 1. Maker browses a collection and chooses some criteria for token ids to make an offer on. The marketplace generates a merkle tree of tokens based on the criteria. 2. Maker inputs: Offer Currency/Payment Method, Price, Validity Time, Beneficiary. 3. Maker prompted to acknowledge/accept maker marketplace fee percentage (display sale price, fees, and net seller proceeds). 4. Maker prompted to acknowledge the set of tokens/merkle root. 5. Maker prompted to decide on standard order (on-chain cancellations) or co-signed order (gasless cancellation). 6. Signature Prompt for either [TokenSetOfferApproval](#token-set-offer-approval). 7. Order and signature submitted to order book. ## Item Offer Approval When making an offer on a specific NFT the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js ItemOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 tokenId, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce ) ``` Typed data hash: `0xce2e9706d63e89ddf7ee16ce0508a1c3c9bd1904c582db2e647e6f4690a0bf6b` ## Collection Offer Approval When making an offer on any NFT in a specific collection the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js CollectionOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce ) ``` Typed data hash: `0x8fe9498e93fe26b30ebf76fac07bd4705201c8609227362697082288e3b4af9c` ## Token Set Offer Approval When making an offer on a subset of NFTs in a specific collection the buyer/maker MUST be prompted by the exchange to sign an EIP-712 sale approval in the following format. If the offer is a standard order `cosigner` MUST be set to `address(0)`, for cosigned orders `cosigner` is set to the address that will cosign the order at time of fulfillment. The exchange MUST submit the signature and order data to an integrated order book via its API(s). ```js TokenSetOfferApproval( uint8 protocol, address cosigner, address buyer, address beneficiary, address marketplace, address fallbackRoyaltyRecipient, address paymentMethod, address tokenAddress, uint256 amount, uint256 itemPrice, uint256 expiration, uint256 marketplaceFeeNumerator, uint256 nonce, uint256 masterNonce, bytes32 tokenSetMerkleRoot ) ``` Typed data hash: `0x244905ade6b0e455d12fb539a4b17d7f675db14797d514168d09814a09c70e70` # Filling Orders ## Filling A Single Listing (Buy Now) 1. Taker browses listings and chooses a listed item. 2. Taker selects "Buy Now". 3. Taker selects beneficiary (whether self or other account). 4. Taker prompted to review the details, including review and acknowledgement of fee on top when applicable. 5. If listing order was co-signed, client requests a co-signature for the listing. 6. Marketplace calls the PaymentProcessorEncoder `encodeBuyListingCalldata` function to generate the calldata to fill the order. [Encoding Details](./calldata-encoding#encodebuylistingcalldata) 7. Wallet pops up a transaction confirmation of the `buyListing` call. [Usage Details](#buy-listing) 8. Taker confirms the transaction through their wallet interface. ## Filling A Batch Of Listings (Shopping Cart) 1. Taker browses listings and chooses several listed items (they may be from one or more collections). 2. Taker selects "Add To Shopping Cart" for each item. 3. Taker reviews cart and "Checks Out". 4. Taker selects beneficiary (whether self or other account). 5. Taker prompted to review the details, including review and acknowledgement of all fees on top when applicable. 6. For any co-signed listing orders, client requests co-signatures. 7. Marketplace calls the PaymentProcessorEncoder `encodeBulkBuyListingsCalldata` function to generate the calldata to bulk fill the orders. [Encoding Details](./calldata-encoding#encodebulkbuylistingscalldata) 8. Wallet pops up a transaction confirmation of the `bulkBuyListings` call. [Usage Details](#bulk-buy-listings) 9. Taker confirms the transaction through their wallet interface. ## Filling A Batch of Listings (Collection Sweep) 1. Taker chooses "Sweep Collection" for the desired collection. 2. Taker specifies desired quantity and maximum price per item. 3. Taker selects beneficiary (whether self or other account). 4. Order book supplies listings that are the best matches. 5. Taker prompted to review the details, including review and acknowledgement of the fee on top when applicable. 6. For any co-signed listing orders, client requests co-signatures. 7. Marketplace calls the PaymentProcessorEncoder `encodeSweepCollectionCalldata` function to generate the calldata to fill the sweep. [Encoding Details](./calldata-encoding#encodesweepcollectioncalldata) 8. Wallet pops up a transaction confirmation of the `sweepCollection` call. [Usage Details](#sweep-collection) 9. Taker confirms the transaction through their wallet interface. ## Filling A Single Offer (Accept Offer) 1. Taker browser offers where the offer criteria matches items they own and chooses one. 2. Taker selects "Accept Offer". 3. Taker prompted to review the details, including review and acknowledgement of the current on-chain royalty fee, maker marketplace fee, and fee on top when applicable. 4. If the offer order was co-signed, client requests a co-signature for the offer. 5. Marketplace calls the PaymentProcessorEncoder `encodeAcceptOfferCalldata` function to generate the calldata to fill the order. [Encoding Details](./calldata-encoding#encodeacceptoffercalldata) 6. Wallet pops up a transaction confirmation of the `acceptOffer` call. [Usage Details](#accept-offer) 7. Taker confirms the transaction through their wallet interface. ## Filling A Batch Of Offers (Offer Cart) 1. Taker browser offers where the offer criteria matches items they own and chooses several. 2. Taker selects "Add To Offer Cart" for each item. 3. Taker reviews cart and "Checks Out". 4. Taker prompted to review the details, including review and acknowledgement of the current on-chain royalty fees for each item, the maker marketplace fees for each item, and the fees on top when applicable. 5. For any co-signed offer orders, client requests co-signatures. 6. Marketplace calls the PaymentProcessorEncoder `encodeBulkAcceptOffersCalldata` function to generate the calldata to bulk fill the orders. [Encoding Details](./calldata-encoding#encodebulkacceptofferscalldata) 7. Wallet pops up a transaction confirmation of the `bulkAcceptOffers` call. [Usage Details](#bulk-accept-offers) 8. Taker confirms the transaction through their wallet interface. ***Note: There are other steps marketplaces may need to implement to prompt users in the workflow. For instance: approving Payment Processor to transfer NFTs and ERC-20 payments, prompting to wrap native currency or swap currencies when needed, or prompting buyers to perform a one-time signature to prove they are EOAs [for select ERC721-C security levels only].*** ## Taker Operations / Functions ### Buy Listing Exchanges should call `buyListing` when a taker wants to purchase a single listing using a UX analogous to "Buy Now". There are four order types that may be filled through the `buyListing` function: **Standard Sell Order *Without* Fee On Top** - To be used when the taker fills a standard sell order and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `cosignature` and `feeOnTop` struct parameters passed to PaymentProcessorEncoder would be filled with zero values. **Standard Sell Order *With* Fee On Top** - To be used when the taker fills a standard sell order via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution the `cosignature` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `feeOnTop` struct parameter would contain the `recipient` and `amount` for the fee being added by the taker marketplace. **Cosigned Sell Order *Without* Fee On Top** - To be used when the taker fills a co-signed sell order and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `feeOnTop` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `cosignature` struct parameter would contain the `signer`, `taker`, `expiration` and cosignature `v`, `r` and `s` values for the cosignature. **Cosigned Sell Order *With* Fee On Top** - To be used when the taker fills a co-signed sell order via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution both the `feeOnTop` and `cosignature` struct parameters passed to PaymentProcessorEncoder would contain the values for the additional fee on top and cosignature validation. Use the PaymentProcessorEncoder to encode the calldata(./calldata-encoding#encodebuylistingcalldata). *Note: The taker/buyer (`msg.sender`) pays for the item and any fee on top when applicable. Because the `buyListing` function is payable, both native or ERC-20 payment methods are accepted.* ### Accept Offer Exchanges should call `acceptOffer` when a taker wants to sell a single item that matches an offer made by a prospective buyer. The kinds of offers are currently supported by Payment Processor: 1. Item Offer - An offer made on a specific collection where only one specific token id can be used to fill the order. 2. Collection Offer - An offer made on a specific collection where any token id can be used to fill the order. 3. Token Set Offer - An offer made on a specific collection where any token id contained in a specified subset of token ids can be used to fill the order. There are four order types that may be filled through the `acceptOffer` function: **Standard Offer *Without* Fee On Top** - To be used when the taker fills a standard buy order (offer) and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `cosignature` and `feeOnTop` struct parameters passed to PaymentProcessorEncoder would be filled with zero values. **Standard Offer *With* Fee On Top** - To be used when the taker fills a standard buy order (offer) via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution the `cosignature` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `feeOnTop` struct parameter would contain the `recipient` and `amount` for the fee being added by the taker marketplace. **Cosigned Offer *Without* Fee On Top** - To be used when the taker fills a co-signed buy order (offer) and the maker and taker marketplace is the same, or when the taker marketplace does not include a fee on top. For this type of order execution the `feeOnTop` struct parameter passed to PaymentProcessorEncoder would be filled with zero values and the `cosignature` struct parameter would contain the `signer`, `taker`, `expiration` and cosignature `v`, `r` and `s` values for the cosignature. **Cosigned Offer *With* Fee On Top** - To be used when the taker fills a co-signed buy order (offer) via a secondary marketplace. While the maker marketplace can assess a primary fee that is deducted from the seller's proceeds, the taker marketplace may assess an extra `fee on top` paid by the taker in excess of the item purchase price. For this type of order execution both the `feeOnTop` and `cosignature` struct parameters passed to PaymentProcessorEncoder would contain the values for the additional fee on top and cosignature validation. *Note: The taker/seller (`msg.sender`) pays for the fee on top when applicable. The maker/buyer pays the cost of the filled item. Because the `acceptOffer` function is not payable, only ERC-20 payment methods are accepted.* ### Bulk Buy Listings Exchanges should call `bulkBuyListings` when a taker wants to purchase more than one listing using a UX analagous to a "Shopping Cart". This allows a taker to select many NFTs across different collections or with varying payment methods and fill all of the listings at once. Each listing being purchased may be any of the four transaction types supported by the `buyListing` function (see [Buy Listing](#buy-listing) above). When encoding bulk purchases the `Order`, `SignatureECDSA`, `Cosignature` and `FeeOnTop` struct values are passed as arrays of values where the item at the same index in each array corresponds to the listing being purchased. All four arrays **MUST** be of equal length and follow the same structure as a single listing purchase for standard versus cosigned orders and execution with or without a fee on top. ### Bulk Accept Offers Exchanges should call `bulkAcceptOffers` when a taker wants to accept/fill more than one offer at once. This allows a taker to sell multiple items they own across one or more collections in a single transaction. The kinds of offers are currently supported by Payment Processor: 1. Item Offer - An offer made on a specific collection where only one specific token id can be used to fill the order. 2. Collection Offer - An offer made on a specific collection where any token id can be used to fill the order. 3. Token Set Offer - An offer made on a specific collection where any token id contained in a specified subset of token ids can be used to fill the order. Each offer being accepted may be any of the four transaction types supported by the `acceptOffer` function (see [Accept Offer](#accept-offer) above). When encoding bulk accept offers the `isCollectionLevelOffer` boolean value and `Order`, `SignatureECDSA`, `TokenSetProof`, `Cosignature` and `FeeOnTop` struct values are passed as arrays of values where the item at the same index in each array corresponds to the offer being accepted. All six arrays **MUST** be of equal length and follow the same structure as a single offer acceptance for standard versus cosigned orders and execution with or without a fee on top. ### Sweep Collection Exchanges should call `sweepCollection` (a more gas efficient form of `bulkBuyListings`) when a taker wants to purchase more than similar listings. A common UX is the "Collection Sweep" UX where the taker specifies a number of items from a collection they want to buy, with some pricing limits. For a sweep to be filled, all items in the sweep order must share the following commonalities: 1. All sell orders fillable in the sweep must be from the same collection. 3. All sell orders fillable in the sweep must use the same method of payment. 5. All sell orders fillable in the sweep must specify the same beneficiary of the NFT. When filling sweep orders, the following fields can be different for each filled item: 1. Maker 2. Marketplace 3. Fallback Royalty Recipient 4. Token Id 5. Amount 6. Item Price 7. Nonce (for standard orders) 8. Expiration 9. Marketplace Fee Numerator 10. Max Royalty Fee Numerator Sweep orders may be executed *with* or *without* a single fee on top for the sweep. To execute *with* a fee on top the `feeOnTop` struct value passed to PaymentProcessorEncoder would contain the `recipient` and `amount` for the fee. To execute *without* a fee on top the `feeOnTop` struct value passed to PaymentProcessorEncoder would contain zero values for `recipient` and `amount`. When encoding sweep orders with PaymentProcessorEncoder the `SweepItem`, `SignatureECDSA` and `Cosignature` structs are passed as arrays of values where the item at the same index in each array corresponds to the listing being purchased. All three arrays **MUST** be of equal length and follow the same structure as a single listing purchase for standard versus cosigned orders. *Note: The taker/buyer (`msg.sender`) pays for the items and any fee on top when applicable. Because the `sweepCollection` function is payable, both native or ERC-20 payment methods are accepted.* *Note: For the most gas-efficient collection sweeps, marketplaces should make a best effort to group orders in the array by marketplace address, seller, and royalty recipient.* ### Cosignature Format All cosigned listings and offers require a secondary signature to be provided at fill-time/execution time. The same cosignature format applies to all cosigned maker signatures, and takes a cosigner expiration and the maker's signature v, r, s components as typed data inputs. Note: for security purposes, co-signatures must always be signed by EOA co-signers. Furthermore, cosignature expiration times should be relatively short. A cosignature expiration time between 5 and 10 minutes is suggested. If the expiration time is too short, transactions may fail because the expiration time elapses before a transaction has been confirmed. However, if the expiration time is too long a leak of the co-signature could be executed well into the future. ```js Cosignature( uint8 v, bytes32 r, bytes32 s, uint256 expiration, address taker ) ``` # Cancelling Orders ## Cancelling A Single Order 1. Display maker's orders (listings and/or offers). 2. Maker selects an order to cancel. 3. If standard order, maker confirms transaction for `revokeSingleNonce` (when order type is fill or kill) or `revokeOrderDigest` (when order type is partially fillable). Revoking a single nonce or order digest requires calldata to be encoded with PaymentProcessorEncoder. See [encodeRevokeSingleNonceCalldata](./calldata-encoding#encoderevokesinglenoncecalldata) for `revokeSingleNonce` or [encodeRevokeOrderDigestCalldata](./calldata-encoding#encoderevokeorderdigestcalldata) for `revokeOrderDigest`. 4. If co-signed order flag order as cancelled in DB and do not allow co-signing service to co-sign the order if requested. Order should be hidden from future listing/offer queries. ## Cancelling All Outstanding Standard Orders 1. If the maker chooses to cancel ALL outstanding standard listings and offers they have made, the maker simply needs to confirm a `revokeMasterNonce` transaction. This will cancel/invalidate all prior orders that were signed using their previous master nonce. `revokeMasterNonce` has no calldata requirements and may be called directly by a maker without using PaymentProcessorEncoder. # Cancelling All Outstanding Co-Signed Orders 1. The marketplace should flag all outstanding orders for the user as cancelled in their database. The co-signing service should not return co-signatures for any of the cancelled orders, and the orders should be hidden from all future queries. # Cosigner Security Cosignatures on orders allow an order book to offer users the ability to gaslessly cancel their listings and offers. To guard against bad actors order books ***MUST*** keep the private keys of the cosigning accounts secure and ***SHOULD*** use short expirations on cosignatures so that signed but unexecuted cosignatures are unlikely to be used after the maker has cancelled their order off-chain. If the private key to a cosigner account has been exposed the order book ***MUST*** destroy the cosigner through Payment Processor on all chains that it was used as an order cosigner so that it may not be used to fill off-chain cancelled orders. Cosigners cannot be recovered after they have been destroyed on Payment Processor. ## Destroy a Cosigner 1. Use the cosigner private key to sign the message `COSIGNER_SELF_DESTRUCT`. 2. Call `destroyCosigner` on Payment Processor with the cosigner address and message signature. See [encodeDestroyCosigner](./calldata-encoding#encodedestroycosignercalldata). # Encoding Function Call Parameters In order to not overflow smart contract bytecode size limits, Payment Processor is split into smaller library modules. When making calls to Payment Processor, these calls are forwarded to the corresponding module and implementation function using `DELEGATECALL`. For gas efficiency purposes, the function calldata must be passed in as a raw calldata byte array. Payment Processor is deployed with a helper contract called the PaymentProcessorEncoder. It accepts the expected function implementation parameters and encodes them as a single calldata byte array that should be passed into Payment Processor when making calls to fill orders. The subsections below detail each PaymentProcessorEncoder function. ### encodeBuyListingCalldata Used to encode calldata for Payment Processor `buyListing` function. ```solidity function encodeBuyListingCalldata( address paymentProcessorAddress, Order memory saleDetails, SignatureECDSA memory signature, Cosignature memory cosignature, FeeOnTop memory feeOnTop ) external view returns (bytes memory); ``` ### encodeAcceptOfferCalldata Used to encode calldata for Payment Processor `acceptOffer` function. ```solidity function encodeAcceptOfferCalldata( address paymentProcessorAddress, bool isCollectionLevelOffer, Order memory saleDetails, SignatureECDSA memory signature, TokenSetProof memory tokenSetProof, Cosignature memory cosignature, FeeOnTop memory feeOnTop ) external view returns (bytes memory); ``` *Note: When filling an item offer, `isCollectionLevelOffer` MUST be set to `false`, and `tokenSetProof` MUST be set to the empty `TokenSetProof({rootHash: bytes32(0), proof: bytes32[](0)})`. *Note: When filling a collection offer, `isCollectionLevelOffer` MUST be set to `true`, and `tokenSetProof` MUST be set to the empty `TokenSetProof({rootHash: bytes32(0), proof: bytes32[](0)})`. *Note: When filling a token set offer, `isCollectionLevelOffer` MUST be set to `true`, and `tokenSetProof` MUST contain the root hash of the merkle tree signed by the offer maker, and the proof must be the valid merkle proof for the collection and token id being filled. ### encodeBulkBuyListingsCalldata Used to encode calldata for Payment Processor `bulkBuyListings` function. ```solidity function encodeBulkBuyListingsCalldata( address paymentProcessorAddress, Order[] calldata saleDetailsArray, SignatureECDSA[] calldata signatures, Cosignature[] calldata cosignatures, FeeOnTop[] calldata feesOnTop ) external view returns (bytes memory); ``` *Note: The length of the `saleDetailsArray`, `signatures`, `cosignatures`, and `feesOnTop` MUST match and MUST be non-zero. `saleDetailsArray[index]` MUST correspond to `signatures[index]`, `cosignatures[index]`, and `feesOnTop[index]`.* *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: As fees on top may not apply to all orders being filled, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})` for any order that is not subject to a fee on top.* ### encodeBulkAcceptOffersCalldata Used to encode calldata for Payment Processor `bulkAcceptOffers` function. ```solidity function encodeBulkAcceptOffersCalldata( address paymentProcessorAddress, bool[] memory isCollectionLevelOfferArray, Order[] memory saleDetailsArray, SignatureECDSA[] memory signatures, TokenSetProof[] memory tokenSetProofsArray, Cosignature[] memory cosignaturesArray, FeeOnTop[] memory feesOnTopArray ) external view returns (bytes memory); ``` *Note: The length of the `saleDetailsArray`, `signatures`, `tokenSetProofsArray`, `isCollectionLevelOfferArray`, `cosignatures`, and `feesOnTopArray` MUST match and MUST be non-zero. `saleDetailsArray[index]` MUST correspond to `signatures[index]`, `tokenSetProofsArray[index]`, `isCollectionLevelOfferArray[index]`, `cosignatures[index]`, and `feesOnTopArray[index]`.* *Note: When filling an item offer, `isCollectionLevelOfferArray[index]` MUST be set to `false`, and `tokenSetProofsArray[index]` MUST be set to the empty `TokenSetProof({rootHash: bytes32(0), proof: bytes32[](0)})`. *Note: When filling a collection offer, `isCollectionLevelOfferArray[index]` MUST be set to `true`, and `tokenSetProofsArray[index]` MUST be set to the empty `TokenSetProof({rootHash: bytes32(0), proof: bytes32[](0)})`. *Note: When filling a token set offer, `isCollectionLevelOfferArray[index]` MUST be set to `true`, and `tokenSetProofsArray[index]` MUST contain the root hash of the merkle tree signed by the offer maker, and the proof must be the valid merkle proof for the collection and token id being filled. *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: As fees on top may not apply to all orders being filled, `feesOnTop[index]` MUST be set to the empty `FeeOnTop({recipient: address(0), amount: 0})` for any order that is not subject to a fee on top.* ### encodeSweepCollectionCalldata Used to encode calldata for Payment Processor `sweepCollection` function. ```solidity function encodeSweepCollectionCalldata( address paymentProcessorAddress, FeeOnTop memory feeOnTop, SweepOrder memory sweepOrder, SweepItem[] calldata items, SignatureECDSA[] calldata signatures, Cosignature[] calldata cosignatures ) external view returns (bytes memory); ``` *Note: The length of the `items`, `signatures`, and `cosignatures` MUST match and MUST be non-zero. `items[index]` MUST correspond to `signatures[index]` and `cosignatures[index]`.* *Note: As co-signatures may not apply to all orders being filled, `cosignatures[index]` MUST be set to the empty `Cosignature({signer: address(0), taker: address(0), expiration: 0, v: 0, r: bytes32(0), s: bytes32(0)})` for any order that is not co-signed.* *Note: A single fee on top may be applied to the entire sweep order.* ### encodeRevokeSingleNonceCalldata Used to encode calldata for Payment Processor `revokeSingleNonce` function (on-chain order cancellation). ```solidity function encodeRevokeSingleNonceCalldata(address paymentProcessorAddress, uint256 nonce) external view returns (bytes memory); ``` ### encodeRevokeOrderDigestCalldata Used to encode calldata for Payment Processor `revokeOrderDigest` function (on-chain order cancellation). ```solidity function encodeRevokeOrderDigestCalldata(address paymentProcessorAddress, bytes32 digest) external view returns (bytes memory); ``` ### encodeDestroyCosignerCalldata Used to encode calldata for Payment Processor `destroyCosignerCalldata` function. ```solidity function encodeDestroyCosignerCalldata( address paymentProcessorAddress, address cosigner, SignatureECDSA memory signature ) external view returns (bytes memory); ``` # Nonces Payment Processor uses two types of nonces to manage orders - master nonces and order nonces. Every order maker on Payment Processor has their own master nonce and set of order nonces. The master nonce defaults to zero for an order maker and allows the maker to cancel all open orders that were signed using that master nonce by calling revokeMasterNonce on Payment Processor. revokeMasterNonce will increment the maker's master nonce by one and emit a MasterNonceInvalidated event with the order maker's address and the master nonce that was revoked, the maker's new master nonce will be the revoked nonce plus one. Master nonce is part of an order signature and will result in all previously signed orders with that nonce to recover as a different signer and revert. An order nonce is a 32 byte integer that is used to prevent the replay of orders on Payment Processor and allow for orders to be explicitly cancelled prior to fulfillment by the order maker. When a nonce is consumed by fulfillment or cancellation it cannot be used again. ## How are nonces stored? Order nonces are stored in a bitmap data structure for each order maker where each storage slot is a "bucket" of 256 nonces where nonces 0-255 are in the first bucket, 256-511 are in the second bucket, and so forth. Nonces are packed this way for efficient use of storage within the EVM and to reduce gas costs on transaction execution. At a high level - consuming the first nonce in a bucket costs 20,000 gas units, each additional nonce consumed from the same bucket in the same transaction costs 100 gas units (a 99.5% savings over one slot per nonce or using nonces from different buckets), and an additional nonce consumed from a bucket that previously had a nonce consumed but in a different transaction costs 5,000 gas units (75% saving over one slot per nonce or using nonces from different buckets). Payment Processor does not account for the order book/marketplace in nonce validation - order books must generate nonces for users that will not overlap with nonces created by another order book to prevent fulfilled orders on one order book from interfering with open orders on the other. ## What is an ideal nonce? An ideal nonce for a Payment Processor order will include as many zero bytes in the 32 byte nonce as possible to save calldata gas cost (a zero byte in calldata costs 4 gas units while a non-zero byte costs 16 gas units), pack as many fillable order nonces into a 256 nonce "bucket" as possible, and not overlap with nonces used by other order books. To meet these objectives of an ideal nonce we recommend using the upper 4 bytes of the 32 byte nonce value as a marketplace differentiator and incrementing the nonce used for order signing by one for each marketplace as a user signs an order as a simple method for generating an order nonce. ### Example ```js maker = "0x1234...FFFF" orderbookDifferentiator = keccak256("YOUR_ORDER_BOOK") << 224 // 0x3cd2fd820000...0000 orderMakerNextOrderbookNonce = getNextNonce(orderbookDifferentiator, maker) // query database for next nonce, for example assume this value is 123 orderNonce = orderbookDifferentiator | orderMakerNextOrderbookNonce // 0x3cd2fd820000..007B ``` A more advanced solution could track prior nonces used for order signing where another factor on the order, such as expiration time or master nonce revocation, has made a prior order unfillable and re-use those nonces to better pack fulfilled nonces. # Contract Deployments #### Deployed Chains: - [Ethereum Mainnet](#ethereum) - [Base Mainnet](#base) - [Optimism Mainnet](#optimism) - [Arbitrum One Mainnet](#arbitrum) - [Polygon Mainnet](#polygon) - [Polygon zkEVM Mainnet](#polygon-zkevm) - [Avalanche C-Chain Mainnet](#avalanche-c-chain) - [Binance Smart Chain (BSC) Mainnet](#binance-smart-chain) - [Ethereum Sepolia Testnet](#ethereum-sepolia) #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Payment Processor V2 | `0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834` | | Payment Processor V2 Encoder | `0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC` | | Payment Processor V2 Configuration | `0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008` | | Payment Processor Module On Chain Cancellation | `0x9A1D00fcC838601680c731640A070e56fA605c81` | | Payment Processor Module Payment Settings | `0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA` | | Payment Processor Module Trades | `0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc` | | Payment Processor Module Advanced Trades | `0x9A1D00899099D06fe50FB31F03Db5345c45ABb36` | ## Mainnet Deployments ### Ethereum - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://etherscan.io/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://etherscan.io/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://etherscan.io/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://etherscan.io/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://etherscan.io/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://etherscan.io/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://etherscan.io/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Base - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://basescan.org/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://basescan.org/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://basescan.org/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://basescan.org/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://basescan.org/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://basescan.org/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://basescan.org/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Optimism - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://optimistic.etherscan.io/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://optimistic.etherscan.io/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://optimistic.etherscan.io/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://optimistic.etherscan.io/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://optimistic.etherscan.io/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://optimistic.etherscan.io/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://optimistic.etherscan.io/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Arbitrum - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://arbiscan.io/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://arbiscan.io/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://arbiscan.io/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://arbiscan.io/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://arbiscan.io/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://arbiscan.io/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://arbiscan.io/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Polygon - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://polygonscan.com/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://polygonscan.com/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://polygonscan.com/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://polygonscan.com/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://polygonscan.com/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://polygonscan.com/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://polygonscan.com/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Polygon zkEVM - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://zkevm.polygonscan.com/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://zkevm.polygonscan.com/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://zkevm.polygonscan.com/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://zkevm.polygonscan.com/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://zkevm.polygonscan.com/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://zkevm.polygonscan.com/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://zkevm.polygonscan.com/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Binance Smart Chain - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://bscscan.com/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://bscscan.com/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://bscscan.com/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://bscscan.com/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://bscscan.com/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://bscscan.com/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://bscscan.com/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ### Avalanche C-Chain - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://snowtrace.io/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://snowtrace.io/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://snowtrace.io/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://snowtrace.io/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://snowtrace.io/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://snowtrace.io/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://snowtrace.io/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) ## Testnet Deployments ### Ethereum Sepolia - Payment Processor V2 - [0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834](https://sepolia.etherscan.io/address/0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) - Payment Processor V2 Encoder - [0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC](https://sepolia.etherscan.io/address/0x9A1D005743777cbc7e9ae7eC75BCb9b314aaeFbC) - Payment Processor V2 Configuration - [0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008](https://sepolia.etherscan.io/address/0x9A1D00Bc981DA5cea8300a999c0d15E2f7F03008) - Payment Processor Module On Chain Cancellation - [0x9A1D00fcC838601680c731640A070e56fA605c81](https://sepolia.etherscan.io/address/0x9A1D00fcC838601680c731640A070e56fA605c81) - Payment Processor Module Payment Settings - [0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA](https://sepolia.etherscan.io/address/0x9A1D007a3E9d558a6dAbF413a34810E85834e3BA) - Payment Processor Module Trades - [0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc](https://sepolia.etherscan.io/address/0x9A1D0059f5534E7a6C6c4DAe390ebD3a731Bd7Dc) - Payment Processor Module Advanced Trades - [0x9A1D00899099D06fe50FB31F03Db5345c45ABb36](https://sepolia.etherscan.io/address/0x9A1D00899099D06fe50FB31F03Db5345c45ABb36) # TokenMaster # Introduction TokenMaster is a next-generation fungible token protocol optimized for onchain consumer economies. Built on the advanced ERC20-C standard, TokenMaster enables the creation of highly customizable tokens tailored for diverse applications. Tokens launched via TokenMaster are paired with an existing token (e.g., the chain's native token or another ERC20), functioning as both a pool and a token. Creators can select from multiple pool types to tailor the behavior of their token to their application's needs. # Features: - **Token Deployment:** Deploy ERC20-C tokens paired with an existing token to establish market value. - **Flexible Transactions:** Enable buy, sell, and ***spend*** operations with robust event hooks for seamless integration across onchain and offchain systems. - **Creator Empowerment:** Fine-grained creator controls to define tokenomics and ecosystem behavior, free from external constraints. - **User Safety:** Built-in guardrails to ensure secure and user-friendly token interactions. - **Future-Ready Architecture:** Modular and extensible design, enabling new token mechanics and protocol wrapping for enhanced customizability. # Benefits: * Near-zero cost to bootstrap a token. * Tokens scale to market demand without artificial limitations. * Multiple sources of revenue for creators while maintaining price stability. # Definitions - **Apptoken:** - A programmable token created for use within a specific application or ecosystem. - **Advanced Order:** - A signed instruction enabling advanced buy, sell, or spend transactions. These orders allow creators to integrate onchain hooks and optional offchain actions seamlessly. - **Cosigner**: An optional signer in advanced orders that adds an additional layer of permissioning, allowing creators to restrict execution to specific executors. - **Hook Extra Data**: Arbitrary bytes data passed to hooks during transactions, allowing for custom logic execution tailored to specific use cases. - **Oracle Extra Data**: Arbitrary bytes data passed to oracles during transactions, providing context or parameters for dynamic value adjustments. - **Pairing Restrictions**: Rules defined by the creator to control which tokens can be paired with a TokenMaster token, ensuring compatibility and avoiding unintentional pairings. - **PermitC**: A permit-based system allowing token transfers using advanced logic for expiration and bulk cancellation of open approvals, leveraging EIP-712 signatures for secure and efficient interactions. - **TokenMaster Router:** The core contract that facilitates token deployment, enforces pairing rules, processes transactions (including advanced orders), and triggers onchain hooks for extended functionality. - **Trusted Channels:** - Pre-approved addresses authorized to initiate transactions through the TokenMaster Router for specific tokens. # TokenMaster Router TokenMaster Router serves as the primary hub for all TokenMaster transactions allowing creators to control all aspects of their token lifecycle. TokenMaster Router implements the Limit Break TrustedForwarder system to allow creators and protocol integrators to route transactions through an external entry point, that may be permissioned with a signing authority for offchain authorization, as well as a `Trusted Channels` list that restricts the caller address to being in a list specified by the creator. `Trusted Channels` may be used to require a specific TrustedForwarder or a custom wrapper that adds logic to TokenMaster transactions. # Token Deployment Flow TokenMaster allows creators to deploy tokens with precise configurations tailored to their application's needs. The deployment process involves selecting the desired token type, configuring deployment parameters, and leveraging deterministic address computation for predictability. ## Steps to Deploy a Token 1. **Choose Token Type**: - Decide the type of pool (e.g., Standard, Stable, Promotional) that best suits your application's requirements. 2. **Construct Deployment Parameters**: - Use the following key structures to define the token's behavior and settings: - [**DeploymentParameters**](./datatypes#deploymentparameters): General deployment settings such as the token factory, deterministic address salt, and transaction rules. - [**PoolDeploymentParameters**](./datatypes#pooldeploymentparameters): Pool-specific settings, including token metadata, pairing token, and initial supply. 3. **Compute the Deterministic Deployment Address**: - Use the factory contract's helper function `computeDeploymentAddress` to calculate the token's address before deployment: ```solidity function computeDeploymentAddress( bytes32 tokenSalt, PoolDeploymentParameters calldata poolParams, uint256 pairedValueIn, uint256 infrastructureFeeBPS ) external view returns(address deploymentAddress); ``` - This ensures predictability and prevents address conflicts. 4. **Deploy the Token**: - Call the `deployToken` function on the TokenMaster Router with the constructed parameters: ```solidity function deployToken( DeploymentParameters calldata deploymentParameters, SignatureECDSA calldata signature ) external payable; ``` - Note: The `signature` parameter is only required when TokenMaster Router has a signing authority enabled for token deployments. If a signing authority is not configured, the `signature` parameter is validated. 5. **Token Activation**: - Upon successful deployment, the token will be fully operational and managed via the TokenMaster Router. # Transactions ## Buy Transaction Flow 1. **Specify Purchase Amount** The buyer inputs the desired number of tokens. 2. **Calculate Cost** The platform computes the expected cost in the paired token, accounting for slippage (refer to [TokenMaster Pools](../Pools/tokenmaster-pools) for cost calculations by pool type). 3. **Approval Handling (ERC20)** - **Using PermitC**: 1. Verify user’s token approval for PermitC. If insufficient, prompt an approval transaction. 2. Generate a signed PermitC transfer order. See [PermitTransfer](./advanced-orders#permit-transfer). - **Using Direct Approvals**: - Verify user’s token approval for TokenMaster Router. If insufficient, prompt an approval transaction. 4. **Cosignature (Optional)** Obtain cosignature if required by the advanced order. See [Cosigning](./advanced-orders#cosigning). 5. **Transaction Execution** - **Advanced Buy or PermitC Orders**: - Call `buyTokensAdvanced` with the required parameters ([BuyOrder](./datatypes#buyorder), [SignedOrder](./datatypes#signedorder), and [PermitTransfer](./datatypes#permittransfer)). - Set `signedOrder.hook` to `address(0)` for basic orders. - Set `permitTransfer.permitProcessor` to `address(0)` for direct approvals. - **Basic Buy Orders**: - Call `buyTokens` with the [BuyOrder](./datatypes#buyorder). - **Native Token Pairing**: - Include the native token value as `msg.value` when invoking the router. --- ## Sell Transaction Flow 1. **Specify Sale Amount** The user inputs the desired number of tokens to sell. 2. **Calculate Expected Value** The platform computes the expected value in the paired token, accounting for slippage (refer to [TokenMaster Pools](../Pools/tokenmaster-pools) for sell value calculations by pool type). 3. **Cosignature (Optional)** Obtain cosignature if the advanced order requires it. See [Cosigning](./advanced-orders#cosigning). 4. **Transaction Execution** - **Advanced Sell Orders**: - Call `sellTokensAdvanced` with the [SellOrder](./datatypes#sellorder) and [SignedOrder](./datatypes#signedorder). - **Basic Sell Orders**: - Call `sellTokens` with the [SellOrder](./datatypes#sellorder). --- ## Spend Transaction Flow 1. **Select Spend Order** The user selects a signed spend order to execute. 2. **Specify Multiples** The user inputs the number of multiples of the spend order to execute. 3. **Calculate Maximum Spend** The platform determines the maximum spend amount based on the signed spend order and the specified multiples, adjusting for value and slippage if an oracle price adjustment is involved. 4. **Cosignature (Optional)** Obtain cosignature if the advanced order requires it. See [Cosigning](./advanced-orders#cosigning). 5. **Transaction Execution** - Call `spendTokens` with the [SpendOrder](./datatypes#spendorder) and [SignedOrder](./datatypes#signedorder). --- ### Example Use Cases - **Game Development** A game developer could use spends to sell items and lives to players using event indexing to issue the items offchain. - **NFT Minting** A project could use spends with onchain hooks to mint NFTs using their ecosystem apptoken. - **Retail Payments** A retail point-of-sale system could generate signed orders for customers to pay onchain using the store's token. # Advanced Order Signing TokenMaster enables creators to enhance user interactions by supporting optional signed advanced orders for buy and sell transactions in addition to spend orders. These advanced orders allow creators to integrate onchain [hooks](./hooks) and provide incentives or custom logic. ## Features of Advanced Orders: - **Custom Incentives**: Creators can reward buyers and sellers through advanced order mechanisms. - **Spend Transactions**: Require signed orders but allow hooks to be optional. Offchain monitoring of the [SpendOrderFilled](./events#order-fill-events) event can substitute for hooks. ### Advanced Buy Order EIP-712 Primary Message Type: ```solidity BuyTokenMasterToken(bytes32 creatorBuyIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner) ``` ### Advanced Sell Order EIP-712 Primary Message Type: ```solidity SellTokenMasterToken(bytes32 creatorSellIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner) ``` ### Spend Order EIP-712 Primary Message Type: ```solidity SpendTokenMasterToken(bytes32 creatorSpendIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner) ``` * `creator*Identifier` is a value specified by the creator to identify the order during onchain hook execution or for offchain processing. * `tokenMasterOracle` must be set to the zero address if not using an oracle to adjust `baseValue`. * `baseToken` is only applicable to orders that are using an oracle to adjust the `baseValue`. * `baseValue` is the amount of token value required to execute the order. * `maxPerWallet` is the maximum amount that one wallet can execute against the specific signed order (Note for clarity: a specific signed order encompasses all fields in the message type, not just the creator identifier). For spends this amount is specified in multiples, for buys and sells this amount is specified in tokens. * `maxTotal` is the maximum amount that all wallets can execute against the specific signed order (Note for clarity: a specific signed order encompasses all fields in the message type, not just the creator identifier). For spends this amount is specified in multiples, for buys and sells this amount is specified in tokens. * `expiration` is the Unix timestamp after which the signed order is invalid. * `hook` is the onchain hook to call after the buy/sell/spend is executed. A hook is required for buy and sell advanced orders but optional for spends. * `cosigner` is the address of the cosigner for the signed order (see [Cosigning](#cosigning)). ## EIP-712 Signature Requirements: - Advanced orders must be signed by an authorized account designated on the TokenMaster Router through the [setOrderSigner](./functions#token-deployment--settings-functions) function. - **Domain Separator**: - **Name**: `TokenMasterRouter` - **Version**: `1` - **Chain ID**: The chain where the order is executed. - **Verifying Contract**: Address of TokenMaster Router. Refer to the [SignedOrder](./datatypes#signedorder) struct for formatting advanced order parameters and signatures. ## Cosigning Advanced orders can include an optional cosigner to add an extra layer of permissioning. Cosigners enable creators to enforce execution restrictions, such as limiting specific transactions to authorized executors. ### Key Features: - **Permissioning**: Restrict advanced orders to pre-approved executors. - **EIP-712 Cosignature**: - Cosignatures include the `v`, `r`, and `s` components of the signed advanced order. - These must match the cosigner specified in the [Cosignature](./datatypes#cosignature) struct. ### Cosignature Format: Cosignature EIP-712 Primary Message Type: ```solidity Cosignature(uint8 v, bytes32 r, bytes32 s, uint256 expiration, address executor) ``` ### Developer Notes: - Cosigner addresses are not permissioned on TokenMaster Router the only requirement is that when a non-zero cosigner address is specified in the [Cosignature](./datatypes#cosignature) struct the cosignature's signature recovers to that address. ### Example Use Cases: - Require a transaction is originating from within a creator's application. - Require a user has completed certain actions prior to executing a transaction. ## Permit Transfer TokenMaster supports PermitC permit transfers for paired tokens when buying TokenMaster tokens. To execute a permit transfer the platform *must* get a signed permit from the user and ensure the PermitC contract being utilized has sufficient allowance from the user for the token being permitted. The domain separator for the permit will be the domain separator of the PermitC contract being utilized. Permit Transfer EIP-712 Primary Message Type: ```solidity PermitTransferFromWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce,AdvancedBuyOrder advancedBuyOrder)AdvancedBuyOrder(address tokenMasterToken,uint256 tokensToBuy,uint256 pairedValueIn,bytes32 creatorBuyIdentifier,address hook,uint8 buyOrderSignatureV,bytes32 buyOrderSignatureR,bytes32 buyOrderSignatureS) ``` # Hooks Hooks are custom smart contracts that execute additional onchain actions after advanced orders are processed. Creators can deploy hooks to extend functionality and integrate with their ecosystem. ## Supported Hook Types: - **Buy Order Hooks**: Triggered after advanced buy orders. - **Implementation Requirement**: Must implement [ITokenMasterBuyHook](https://github.com/limit-break-inc/tm-tokenmaster/tree/main/src/interfaces/ITokenMasterBuyHook.sol). ```solidity //SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0 pragma solidity 0.8.24; /** * @title ITokenMasterBuyHook * @author Limit Break, Inc. * @notice Interface that must be implemented by contracts acting as a buy hook * @notice for advanced buy orders. */ interface ITokenMasterBuyHook { function tokenMasterBuyHook( address tokenMasterToken, address buyer, bytes32 creatorBuyIdentifier, uint256 amountPurchased, bytes calldata hookExtraData ) external; } ``` - **Sell Order Hooks**: Triggered after advanced sell orders. - **Implementation Requirement**: Must implement [ITokenMasterSellHook](https://github.com/limit-break-inc/tm-tokenmaster/tree/main/src/interfaces/ITokenMasterSellHook.sol). ```solidity //SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0 pragma solidity 0.8.24; /** * @title ITokenMasterSellHook * @author Limit Break, Inc. * @notice Interface that must be implemented by contracts acting as a sell hook * @notice for advanced sell orders. */ interface ITokenMasterSellHook { function tokenMasterSellHook( address tokenMasterToken, address seller, bytes32 creatorSellIdentifier, uint256 amountSold, bytes calldata hookExtraData ) external; } ``` - **Spend Order Hooks**: Triggered after spend orders. - **Implementation Requirement**: Must implement [ITokenMasterSpendHook](https://github.com/limit-break-inc/tm-tokenmaster/tree/main/src/interfaces/ITokenMasterSpendHook.sol). ```solidity //SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0 pragma solidity 0.8.24; /** * @title ITokenMasterSpendHook * @author Limit Break, Inc. * @notice Interface that must be implemented by contracts acting as a spend hook * @notice for spend orders. */ interface ITokenMasterSpendHook { function tokenMasterSpendHook( address tokenMasterToken, address spender, bytes32 creatorSpendIdentifier, uint256 multiplier, bytes calldata hookExtraData ) external; } ``` ## Key Parameters: - **Transaction Context**: Hooks receive details such as token being transacted, executor address, creator identifier, and quantity transacted. Quantity transacted is amount of token for buy and sells, multiples for spends. ## Hook Design Notes: - **Validation**: Hook contracts must validate the caller as the TokenMaster Router and confirm the token involved is valid for the hook's logic. - **Extra Data**: Hooks can receive additional contextual data through the `extraData` parameter for custom use cases. Validation of this data is the responsibility of the hook logic. # Oracles Oracles in TokenMaster allow dynamic adjustment of transaction values by referencing external data, such as market conditions or token price feeds. They enable greater flexibility in token mechanics and creator-defined customizations. ## Key Features: - **Dynamic Adjustments**: Modify transaction requirements (e.g., token value or spend amount) based on external inputs. - **Transaction Types Supported**: - **Buy Orders**: Specify the minimum required paired token value for token purchases. - **Sell Orders**: Determine the paired token value received for token sales. - **Spend Orders**: Define the per-multiple spend amount in paired tokens. ## Oracle Parameters and Flow: ### Key Parameters: - **Base Value**: The required token value for the transaction. If no oracle is used, `tokenMasterOracle` is set to `address(0)` in the `SignedOrder`. - **Extra Data**: Optional additional information passed to the oracle via the `extraData` field for advanced logic. ### Transaction Type Values: | Transaction | Type Value | | ----------- | ---------- | | Buy | `0` | | Sell | `1` | | Spend | `2` | ## Example Use Cases: 1. **Price Pegging**: Use oracles to dynamically adjust token requirements to maintain a stable price (e.g., pegged to a stablecoin). 2. **Discounts and Promotions**: Apply promotional rates or discounts by dynamically altering the required token value based on demand or time-limited campaigns. ## Developer Notes: - **Implementation Requirement**: Must implement [ITokenMasterOracle](https://github.com/limit-break-inc/tm-tokenmaster/tree/main/src/interfaces/ITokenMasterOracle.sol). ```solidity //SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0 pragma solidity 0.8.24; /** * @title ITokenMasterOracle * @author Limit Break, Inc. * @notice Interface that must be implemented by contracts acting as an oracle * @notice for advanced orders. */ interface ITokenMasterOracle { function adjustValue( uint256 transactionType, address executor, address tokenMasterToken, address baseToken, uint256 baseValue, bytes calldata oracleExtraData ) external view returns(uint256 tokenValue); } ``` - **Data Decoding**: Decoding and validating the `extraData` field is the responsibility of the oracle logic. # Data Types ## DeploymentParameters This struct defines parameters used by the TokenMasterRouter and factories for deploying tokens. - **tokenFactory**: The token factory to use to deploy a specific pool type. - **tokenSalt**: The salt value to use when deploying the token to control the deterministic address. - **tokenAddress**: The deterministic address of the token that will be deployed. - **blockTransactionsFromUntrustedChannels**: Initial setting for blocking transactions from untrusted channels. - **restrictPairingToLists**: Initial setting for restricting pairing of the new token with other tokens. - **poolParams**: The parameters that will be sent during token contract construction. - **maxInfrastructureFeeBPS**: The maximum infrastructure fee that is allowed without reverting the deployment. ```solidity struct DeploymentParameters { address tokenFactory; bytes32 tokenSalt; address tokenAddress; bool blockTransactionsFromUntrustedChannels; bool restrictPairingToLists; PoolDeploymentParameters poolParams; uint16 maxInfrastructureFeeBPS; } ``` ## PoolDeploymentParameters This struct defines parameters that are sent by token factories to create a token contract. - **name**: The name of the token. - **symbol**: The symbol of the token. - **decimals**: The number of decimals of the token. - **initialOwner**: Address to set as the initial owner of the token. - **pairedToken**: Address of the token to pair with the new token, for native token use `address(0)`. - **initialPairedTokenToDeposit**: Amount of paired token to deposit to the new token pool. - **encodedInitializationArgs**: Bytes array of ABI encoded initialization arguments to allow new pool types with different types of constructor arguments that are decoded during deployment. - **defaultTransferValidator**: Address of the initial transfer validator for a token. - **useRouterForPairedTransfers**: If true, the pool will default to allowing the router to transfer paired tokens during operations that require the paired token to transfer from the pool. This is useful when pairing with ERC20C tokens that utilize the default operator whitelist which includes the TokenMasterRouter but does not include individual token pools. - **partnerFeeRecipient**: The address that will receive partner fee shares. - **partnerFeeBPS**: The fee rate in BPS for partner fees. ```solidity struct PoolDeploymentParameters { string name; string symbol; uint8 tokenDecimals; address initialOwner; address pairedToken; uint256 initialPairedTokenToDeposit; bytes encodedInitializationArgs; address defaultTransferValidator; bool useRouterForPairedTransfers; address partnerFeeRecipient; uint256 partnerFeeBPS; } ``` ## BuyOrder This struct defines buy order base parameters. - **tokenMasterToken**: The address of the TokenMaster token to buy. - **tokensToBuy**: The amount of tokens to buy. - **pairedValueIn**: The amount of paired tokens to transfer in to the token contract for the purchase. ```solidity struct BuyOrder { address tokenMasterToken; uint256 tokensToBuy; uint256 pairedValueIn; } ``` ## PermitTransfer This struct defines a permit transfer parameters used in advanced buy orders that utilize PermitC advanced permit transfer functionality. - **permitProcessor**: The address of the PermitC-compliant permit processor to use for the transfer. - **nonce**: The permit nonce to use for the permit transfer signature validation. - **permitAmount**: The amount that the permit was signed for. - **expiration**: The time, in seconds since the Unix epoch, that the permit will expire. - **signedPermit**: The permit signature bytes authorizing the transfer. ```solidity struct PermitTransfer { address permitProcessor; uint256 nonce; uint256 permitAmount; uint256 expiration; bytes signedPermit; } ``` ## SellOrder This struct defines sell order base parameters. - **tokenMasterToken**: The address of the TokenMaster token to sell. - **tokensToSell**: The amount of tokens to sell. - **minimumOut**: The minimum output of paired tokens to be received by the seller without the transaction reverting. ```solidity struct SellOrder { address tokenMasterToken; uint256 tokensToSell; uint256 minimumOut; } ``` ## SpendOrder This struct defines spend order base parameters. It must be combined with a `SignedOrder` to execute a token spend transaction. - **tokenMasterToken**: The address of the TokenMaster token to spend. - **multiplier**: The multiplier of the signed spend order's `baseValue`, adjusted by an oracle if specified, to be spent. - **maxAmountToSpend**: The maximum amount the spender will spend on the order without the transaction reverting. ```solidity struct SpendOrder { address tokenMasterToken; uint256 multiplier; uint256 maxAmountToSpend; } ``` ## SignedOrder This struct defines advanced order execution parameters for advanced buy orders, advanced sell orders and spend orders. - **creatorIdentifier**: A value specified by the creator to identify the order for any onchain or offchain benefits to the order executor for executing the order. - **tokenMasterOracle**: An address for an onchain oracle that can adjust the `baseValue` for an advanced order. - **baseToken**: An address for a token to base the `baseValue` on when adjusting value through a TokenMaster Oracle. - **baseValue**: The amount of token required for the order to be executed. * If `tokenMasterOracle` is set to `address(0)`, the `baseToken` will not be utilized and the advanced order will execute with `baseValue` being the amount of the TokenMaster token to be required for the order. - **maxPerWallet**: The maximum amount per wallet that can be executed on the order. For buy and sell advanced orders this amount is in the TokenMaster token amount, for spend orders it is multipliers. - **maxPerWallet**: The maximum amount for all wallets that can be executed on the order. For buy and sell advanced orders this amount is in the TokenMaster token amount, for spend orders it is multipliers. - **expiration**: The time, in seconds since the Unix epoch, that the order will expire. - **hook**: An address for an onchain hook for an order to execute after the buy, sell or spend is executed. - **signature**: The signature from an allowed order signer to authorize the order. - **cosignature**: The cosignature from the cosigner specified by the order signer. - **hookExtraData**: Extra data to send with the call to the onchain hook contract. - **oracleExtraData**: Extra data to send with the call to the oracle contract. ```solidity struct SignedOrder { bytes32 creatorIdentifier; address tokenMasterOracle; address baseToken; uint256 baseValue; uint256 maxPerWallet; uint256 maxTotal; uint256 expiration; address hook; SignatureECDSA signature; Cosignature cosignature; bytes hookExtraData; bytes oracleExtraData; } ``` ## SignatureECDSA The `v`, `r`, and `s` components of an ECDSA signature. For more information [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7). ```solidity struct SignatureECDSA { uint256 v; bytes32 r; bytes32 s; } ``` ## Cosignature This struct defines the cosignature for verifying an order that is a cosigned order. - **signer**: The address that signed the cosigned order. This must match the cosigner that is part of the order signature. * **expiration**: The time, in seconds since the Unix epoch, that the cosignature will expire. * The `v`, `r`, and `s` components of an ECDSA signature. For more information [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7). ```solidity struct Cosignature { address signer; uint256 expiration; uint256 v; bytes32 r; bytes32 s; } # Functions ## Order Functions --- Functions related to buying, selling and spending TokenMaster Tokens through the TokenMaster Router. Platforms integrating TokenMaster ***MUST*** implement these functions to allow users to buy, sell and spend tokens through TokenMaster, validate current orders, and allow creators to manage existing orders. ```solidity // Callable by users to buy, sell and spend tokens. function buyTokens(BuyOrder calldata buyOrder) external payable; function buyTokensAdvanced( BuyOrder calldata buyOrder, SignedOrder calldata signedOrder, PermitTransfer calldata permitTransfer ) external payable; function sellTokens(SellOrder calldata sellOrder) external; function sellTokensAdvanced(SellOrder calldata sellOrder, SignedOrder calldata signedOrder) external; function spendTokens( SpendOrder calldata spendOrder, SignedOrder calldata signedOrder ) external; // Retrieves advanced order tracking data for orders with wallet/total limits, validates order and signatures. function getBuyTrackingData( address tokenMasterToken, SignedOrder calldata signedOrder, address buyer ) external view returns ( uint256 totalBought, uint256 totalWalletBought, bool orderDisabled, bool signatureValid, bool cosignatureValid ); function getSellTrackingData( address tokenMasterToken, SignedOrder calldata signedOrder, address seller ) external view returns ( uint256 totalSold, uint256 totalWalletSold, bool orderDisabled, bool signatureValid, bool cosignatureValid ); function getSpendTrackingData( address tokenMasterToken, SignedOrder calldata signedOrder, address spender ) external view returns ( uint256 totalMultipliersSpent, uint256 totalWalletMultipliersSpent, bool orderDisabled, bool signatureValid, bool cosignatureValid ); // Callable by the token owner, default admin role holder, or order manager role holder to disable or re-enable an advanced order. function disableBuyOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external; function disableSellOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external; function disableSpendOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external; ``` ## Token Deployment / Settings Functions Functions related to deploying TokenMaster Tokens, managing and retrieving their current settings. Platforms integrating TokenMaster *must* implement these functions to allow creators to fully manage their tokens. ```solidity // Deploys a TokenMaster Token. function deployToken( DeploymentParameters calldata deploymentParameters, SignatureECDSA calldata signature ) external payable; // Updates token settings on router, may be called by owner or default admin role holder. function updateTokenSettings( address tokenAddress, bool blockTransactionsFromUntrustedChannels, bool restrictPairingToLists ) external; function setOrderSigner(address tokenMasterToken, address signer, bool allowed) external; function setTokenAllowedPairToDeployer(address tokenAddress, address deployer, bool allowed) external; function setTokenAllowedPairToToken(address tokenAddress, address tokenAllowedToPair, bool allowed) external; // Retrieves current settings for tokens. function getTokenSettings( address tokenAddress ) external view returns ( bool deployedByTokenMaster, bool blockTransactionsFromUntrustedChannels, bool restrictPairingToLists, address partnerFeeRecipient ); function getOrderSigners(address tokenMasterToken) external view returns (address[] memory orderSigners); function getTrustedChannels(address tokenMasterToken) external view returns (address[] memory trustedChannels); function getAllowedPairToDeployers(address tokenMasterToken) external view returns (address[] memory allowedPairToDeployers); function getAllowedPairToTokens(address tokenMasterToken) external view returns (address[] memory allowedPairToTokens); ``` ## Creator / Partner / Infrastructure Functions Functions relating to the withdrawal and transfer of creator, partner and infrastructure fees from TokenMaster Tokens. Platforms integrating TokenMaster *MUST* implement the `withdrawCreatorShare` and `transferCreatorShareToMarket` functions for creators to withdraw or manage their earnings. Platforms *should* implement `partnerProposeFeeReceiver`, `acceptProposedPartnerFeeReceiver` and `withdrawFees` to manage partner fee receiver changes and fee withdrawal. Partner fee receivers utilize a two-step transfer process where the current partner fee receiver proposes a new receiving address and the token owner accepts the proposed address. Token owners ***MUST*** inspect the proposed address to ensure it is valid for receiving fees and will not be subject to any transfer restrictions. ```solidity // Callable by the token owner or default admin role holder. function withdrawCreatorShare(ITokenMasterERC20C tokenMasterToken, address withdrawTo, uint256 withdrawAmount) external; function transferCreatorShareToMarket(ITokenMasterERC20C tokenMasterToken, uint256 transferAmount) external; function acceptProposedPartnerFeeReceiver(address tokenMasterToken, address expectedPartnerFeeRecipient) external; // Callable by the partner fee receiver for a token to propose a new fee receiver address. function partnerProposeFeeReceiver(address tokenMasterToken, address proposedPartnerFeeRecipient) external; // Callable by TokenMaster fee collector or a partner for a token. function withdrawFees(ITokenMasterERC20C[] calldata tokenMasterTokens) external; ``` ## Administrative Functions For reference only, these functions are only callable by the TokenMaster admin to update the protocol fee and add/remove allowed token factories. ```solidity // Callable by the TokenMaster admin. function setAllowedTokenFactory(address tokenFactory, bool allowed) external; function setInfrastructureFee(uint16 _infrastructureFeeBPS) external; ``` # Events ## Administrative Events Platforms integrating TokenMaster *should* monitor these event emissions from TokenMaster Router to be aware of infrastructure fee changes and factories being enabled or disabled. ```solidity /// @dev Emitted when the TokenMaster admin updates the infrastructure fee for new token deployments. event InfrastructureFeeUpdated(uint16 infrastructureFeeBPS); /// @dev Emitted when the TokenMaster admin updates an allowed token factory. event AllowedTokenFactoryUpdated(address indexed tokenFactory, bool allowed); ``` --- ## Token Configuration Events Platforms integrating TokenMaster *should* monitor these events from TokenMaster Router for any token that is relevant to their platform. ```solidity /// @dev Emitted when a token has been deployed. event TokenMasterTokenDeployed(address indexed tokenMasterToken, address indexed pairedToken, address indexed tokenFactory); /// @dev Emitted when a token's settings have been updated. event TokenSettingsUpdated( address indexed tokenMasterToken, bool blockTransactionsFromUntrustedChannels, bool restrictPairingToLists ); /// @dev Emitted when a trusted channel has been added or removed. event TrustedChannelUpdated( address indexed tokenAddress, address indexed channel, bool allowed ); /// @dev Emitted when a token's partner has proposed a new fee recipient address. event PartnerFeeRecipientProposed( address indexed tokenAddress, address proposedPartnerFeeRecipient ); /// @dev Emitted when the creator has accepted the token partner's proposed fee recipient address. event PartnerFeeRecipientUpdated( address indexed tokenAddress, address partnerFeeRecipient ); /// @dev Emitted when a deployer has been added or removed as an allowed deployer for tokens pairing to a creator's token. event AllowedPairToDeployersUpdated( address indexed tokenAddress, address indexed deployer, bool allowed ); /// @dev Emitted when a specific token has been added or removed as an allowed token for pairing to a creator's token. event AllowedPairToTokensUpdated( address indexed tokenAddress, address indexed tokenAllowedToPair, bool allowed ); ``` --- ## Order Fill Events Platforms integrating TokenMaster *should* monitor these events from TokenMaster Router for any token that is relevant to their platform. ```solidity /// @dev Emitted when a buy tokens order has been filled. event BuyOrderFilled( address indexed tokenMasterToken, address indexed buyer, uint256 amountPurchased, uint256 totalCost ); /// @dev Emitted when a sell tokens order has been filled. event SellOrderFilled( address indexed tokenMasterToken, address indexed seller, uint256 amountSold, uint256 totalReceived ); /// @dev Emitted when a spend tokens order has been filled. event SpendOrderFilled( address indexed tokenMasterToken, bytes32 indexed creatorSpendIdentifier, address indexed spender, uint256 amountSpent, uint256 multiplier ); ``` --- ## Order Configuration Events Platforms integrating TokenMaster *should* monitor these events from TokenMaster Router for any token that is relevant to their platform. ```solidity /// @dev Emitted when a order signer has been updated. event OrderSignerUpdated( address indexed tokenMasterToken, address indexed signer, bool allowed ); /// @dev Emitted when an advanced buy order has been disabled or enabled. event BuyOrderDisabled( address indexed tokenMasterToken, bytes32 indexed creatorBuyIdentifier, bool disabled ); /// @dev Emitted when an advanced sell order has been disabled or enabled. event SellOrderDisabled( address indexed tokenMasterToken, bytes32 indexed creatorSellIdentifier, bool disabled ); /// @dev Emitted when an spend order has been disabled or enabled. event SpendOrderDisabled( address indexed tokenMasterToken, bytes32 indexed creatorSpendIdentifier, bool disabled ); ``` # TokenMaster Pools TokenMaster has three pool types to choose from: Standard Pool, Stable Pool, and Promotional Pool. ## Common Pool Functionality ### Functions --- The following functions are general to all TokenMaster Tokens. Platforms integrating TokenMaster should use these functions to retrieve pool data and (optionally) to generate administrative transactions to recover funds deposited by user errors. Functions listed below `Calls restricted to only TokenMaster Router` are for reference only and may only be called by the TokenMaster Router contract through its corresponding function to enforce reentrancy guarding, creator control logic, supply relevant parameters that are stored in the router contract, and provide primary or fallback router token transfer support. ```solidity // Permits the token owner to withdraw native tokens or ERC20 tokens that are mistakenly deposited to the pool. function withdrawUnrelatedToken(address tokenAddress, address withdrawTo, uint256 withdrawAmount) external; // Address of the token the TokenMaster Token is paired with. Zero address if paired with chain native token. function PAIRED_TOKEN() external view returns(address); // Current splits of the paired token value by market, creator, infrastructure and partner. function pairedTokenShares() external view returns(uint256 marketShare, uint256 creatorShare, uint256 infrastructureShare, uint256 partnerShare); // Calls restricted to only TokenMaster Router function buyTokens( address buyer, uint256 pairedTokenIn, uint256 pooledTokenToBuy ) external payable returns(uint256 totalCost, uint256 refundByRouterAmount); function sellTokens( address seller, uint256 pooledTokenToSell, uint256 pairedTokenMinimumOut ) external returns (address pairedToken, uint256 pairedValueToSeller, uint256 transferByRouterAmount); function spendTokens(address spender, uint256 pooledTokenToSpend) external; function withdrawCreatorShare( address withdrawTo, uint256 withdrawAmount, address infrastructureFeeRecipient, address partnerFeeRecipient ) external returns ( address pairedToken, uint256 transferByRouterAmountCreator, uint256 transferByRouterAmountInfrastructure, uint256 transferByRouterAmountPartner ); function transferCreatorShareToMarket( uint256 transferAmount, address infrastructureFeeRecipient, address partnerFeeRecipient ) external returns(address pairedToken, uint256 transferByRouterAmountInfrastructure, uint256 transferByRouterAmountPartner); function withdrawFees( address infrastructureFeeRecipient, address partnerFeeRecipient ) external returns ( address pairedToken, uint256 transferByRouterAmountInfrastructure, uint256 transferByRouterAmountPartner ); ``` ### Events --- Platforms integrating TokenMaster *should* index the following events on all tokens that are relevant to the platform to monitor withdrawal/transfer of creator share and fees. ```solidity /// @dev Emitted when a creator withdraws their share or when fees are withdrawn. event CreatorShareWithdrawn(address to, uint256 withdrawAmount, uint256 infrastructureAmount, uint256 partnerAmount); /// @dev Emitted when a creator transfers a portion of their share to the market bonded value. Infrastructure and partner amounts are transferred to their respective receivers. event CreatorShareTransferredToMarket(address to, uint256 transferAmount, uint256 infrastructureAmount, uint256 partnerAmount); ``` ### Ownership --- All TokenMaster Pools utilize a two-step ownership transfer process, implement access control for granting role-based permissions, and prevent ownership renouncing. #### Ownership Transfer Process --- 1. Current owner calls `transferOwnership(address newOwner)` specifying the address to transfer ownership to. * A `OwnershipTransferStarted(address,address)` event will be emitted when this function is called. * Until the new owner accepts ownership, the current owner will remain the owner and may transfer ownership to another account, canceling out the previous pending new owner. 2. New owner calls `acceptOwnership()` to become the contract owner. #### Roles --- In addition to the primary owner of a TokenMaster Token, tokens have role-based permissions that may be granted and revoked by the token owner. To grant a role the owner must call `grantRole(bytes32 role, address account)` to grant `role` (using role identifier) to `account`. To revoke a role the owner must call `revokeRole(bytes32 role, address account)` to revoke `role` (using role identifier) from `account`. There are two roles that are used in TokenMaster generally - `DEFAULT_ADMIN` and `ORDER_MANAGER`. An account with the default admin role will have permissions equivalent to the token owner in TokenMaster Router for setting token settings. An account with the order manager role will have permission to disable and re-enable specific advanced buy, sell and spend orders in TokenMaster Router. | Role | Role Identifier | | ------------- | -------------------------------------------------------------------- | | DEFAULT_ADMIN | `0x0000000000000000000000000000000000000000000000000000000000000000` | | ORDER_MANAGER | `0x3c6581d0b5cf40e66836017f19054f4ad048ab86f3953ec1b557d00fbaf735ac` | ### ERC-165 Interfaces --- TokenMaster Pools can be inspected with ERC-165's `supportsInterface(bytes4 interfaceId)` function to detect the pool's type and functionality it supports. This enables developers to programmatically query supported features of a pool, ensuring seamless integration. #### Overview ERC-165 is a standard for interface detection in Ethereum smart contracts. It defines a method to query whether a contract implements a specific interface. In the context of TokenMaster, it allows developers to identify pool types and their supported functionalities. #### Supported Interface IDs The following table lists the interface IDs and the corresponding pool types or functionalities they represent: | **Interface ID** | **Type / Functionality** | |-------------------|-------------------------------------------------------------------| | `0xb080171e` | General TokenMaster ERC20C interface (required by all pools). | | `0x1f4456b6` | Standard Pool functionality. | | `0x049ac6b3` | Creator emissions support. | | `0xcffb014c` | Promotional Pool functionality. | | `0xa18b736c` | Direct minting and burning support. | | `0x511e5ebf` | Stable Pool functionality. | #### Example Usage Below is an example of how developers can use the `supportsInterface` function to check for specific features in a pool: ```typescript // Address of the TokenMaster Pool contract const poolAddress: Address = '0xYourPoolAddressHere'; // Replace with the pool address // ERC-165 interface ID for Stable Pool functionality const stablePoolInterfaceId = '0x511e5ebf'; async function checkStablePoolSupport() { try { // Encode the supportsInterface call const callData = encodeFunctionData({ abi, functionName: 'supportsInterface', args: [stablePoolInterfaceId], }); // Perform the call const result = await client.readContract({ address: poolAddress, abi, functionName: 'supportsInterface', args: [stablePoolInterfaceId], }); if (result) { console.log('This pool supports Stable Pool functionality.'); } else { console.log('This pool does NOT support Stable Pool functionality.'); } } catch (error) { console.error('Error checking Stable Pool support:', error); } } ``` #### Ownership Renouncing --- TokenMaster Tokens prevent renouncing ownership due to the nature of the tokens creator controls and fees generated. Creators that may have been considering renouncing should consider all implications of not having full ownership access to a TokenMaster Token and alternatives such as governance contracts or timelocks that can hold ownership for a period of time. # Standard Pool Standard Pools offer flexibility and control with a wide range of levers for the creator to adjust, making them suitable for a wide range of tokenomics. The market value of tokens in a Standard Pool is derived from the ratio of paired tokens in the market to the total token supply. ## Standard Pool Settings --- - **Buy Fee** - Amount added to the market value cost of buying tokens. Creator may set both a percentage of market value buy fee and a flat rate per token. - **Sell Fee** - Amount subtracted from the market value of tokens being sold. Creator specified as a percentage. - **Buy Spread** - Amount as a percentage that tokens cost over the current market value. - **Sell Spread** - Amount as a percentage that tokens are worth under the current market value. - **Target Supply** - Scales buy cost when target supply is exceeded by the ratio of new supply to expected supply - charged as a "demand fee". - **Creator Share of Demand Fee** - Percentage of the demand fee that is allocated to the creator, the remainder goes to market value of tokens. - **Creator Share of Spends** - Percentage of the value of tokens spent that are allocated to the creator, the remainder goes to market value of tokens. - **Emissions** - Tokens the creator may mint at zero cost, up to a self-imposed limit that may only be lowered. Emissions are accrued over time and creator may reset their accrual / only claim a portion to delay the introduction of those zero cost tokens into the market. Since emissions are minted at zero cost they devalue the rest of the supply. ### Guardrails: To maintain fairness and predictability, these settings are constrained by immutable limits established during deployment: - **Fee Limits**: Caps on buy and sell fees. - **Spread Limits**: Minimum and maximum buy/sell spreads. - **Spend Share Limits**: Maximum percentage of spent tokens that can be allocated to the creator. ## Transactions --- **Buy:** A buyer will pay the market value of the tokens, adjusted by the buy spread, plus the creator buy fee plus a demand fee if the new supply exceeds the target supply (if applicable). Buy spread greater than zero will increase the market value of tokens. **Sell:** A seller will receive the market value of the tokens, adjusted by the sell spread, less the creator sell fee. Sell spread greater than zero will increase the market value of tokens. **Spend:** A spender will receive no market value for their tokens, market value will instead be transferred to the creator share at a rate specified by the creator. Creator share of spend less than 100% will increase the market value of tokens. **Claim Emissions:** Creator emissions are accrued at a rate specified during the deployment of the token and may be claimed at any time. Emissions are capped at an amount set at deployment, the cap may be lowered post-deployment but not increased. Claimed emissions by the creator will mint tokens at zero cost and decrease the market value of tokens. Creators may opt to forfeit a portion of a claim which will require the forfeitted emissions to be re-accrued before they can be claimed. ## Data Types --- ### StandardPoolInitializationParameters This struct defines parameters used for standard pool initialization. - **initialSupplyRecipient**: Address to receive the initial supply amount. - **initialSupplyAmount**: Initial amount of supply to mint to the initial supply recipient. - **minBuySpreadBPS**: Immutable minimum spread rate in BPS for buys. - **maxBuySpreadBPS**: Immutable maximum spread rate in BPS for buys. - **maxBuyFeeBPS**: Immutable maximum buy fee rate in BPS that may be set by the creator. - **maxBuyDemandFeeBPS**: Immutable maximum buy demand fee rate in BPS that may be set by the creator. - **maxSellFeeBPS**: Immutable maximum sell fee rate in BPS that may be set by the creator. - **maxSpendCreatorShareBPS**: Immutable maximum creator share rate in BPS that may be set by the creator. - **creatorEmissionRateNumerator**: Immutable numerator for the rate in tokens per second that a creator earns emissions. - **creatorEmissionRateDenominator**: Immutable denominator for the rate in tokens per second that a creator earns emissions. - **creatorEmissionsHardCap**: Initial value for the hard cap of total emissions that a creator may claim over time. - **initialBuyParameters**: Initial settings for buy parameters. - **initialSellParameters**: Initial settings for sell parameters. - **initialSpendParameters**: Initial settings for spend parameters. - **initialPausedState**: Initial paused state for pausable functions. ```solidity struct StandardPoolInitializationParameters { address initialSupplyRecipient; uint256 initialSupplyAmount; uint256 minBuySpreadBPS; uint256 maxBuySpreadBPS; uint256 maxBuyFeeBPS; uint256 maxBuyDemandFeeBPS; uint256 minSellSpreadBPS; uint256 maxSellSpreadBPS; uint256 maxSellFeeBPS; uint256 maxSpendCreatorShareBPS; uint256 creatorEmissionRateNumerator; uint256 creatorEmissionRateDenominator; uint256 creatorEmissionsHardCap; StandardPoolBuyParameters initialBuyParameters; StandardPoolSellParameters initialSellParameters; StandardPoolSpendParameters initialSpendParameters; uint256 initialPausedState; } ``` --- ### StandardPoolBuyParameters This struct defines parameters used for buys. - **buySpreadBPS**: Spread rate in BPS for the cost of tokens above the current market value. - **buyFeeBPS**: Fee rate in BPS for the fee applied to buys. - **buyCostPairedTokenNumerator**: The numerator for the ratio of paired token to pool token that will be added as a fee on top of buys. - **buyCostPoolTokenDenominator**: The denominator for the ratio of paired token to pool token that will be added as a fee on top of buys. - **useTargetSupply**: True if the pool uses a target supply for determining the application of demand fees. - **reserved**: Unused. Spacing for storage optimization. - **buyDemandFeeBPS**: Rate in BPS of the amount of demand fee that is allocated to the creator. - **targetSupplyBaseline**: Baseline supply amount for determining if a buy will exceed the target supply. - **targetSupplyBaselineScaleFactor**: Scale factor for the baseline supply. Stored as a separate value to optimize storage. - `Actual Supply Baseline = targetSupplyBaseline * 10^targetSupplyBaselineScaleFactor` - **targetSupplyGrowthRatePerSecond**: Rate in tokens per second that the target supply grows when calculating expected supply. - **targetSupplyBaselineTimestamp**: Unix timestamp for the time when target supply growth rate per second starts accumulating. - When the current block timestamp is less than `targetSupplyBaselineTimestamp`: - `Expected Supply = Actual Supply Baseline.` - When the current block timestamp is greater than `targetSupplyBaselineTimestamp`: - `Expected Supply = Actual Supply Baseline + targetSupplyGrowthRatePerSecond * (block.timestamp - targetSupplyBaselineTimestamp)` ```solidity struct StandardPoolBuyParameters { uint16 buySpreadBPS; uint16 buyFeeBPS; uint96 buyCostPairedTokenNumerator; uint96 buyCostPoolTokenDenominator; bool useTargetSupply; uint24 reserved; uint16 buyDemandFeeBPS; uint48 targetSupplyBaseline; uint8 targetSupplyBaselineScaleFactor; uint96 targetSupplyGrowthRatePerSecond; uint48 targetSupplyBaselineTimestamp; } ``` --- ### StandardPoolSellParameters This struct defines parameters used for sells. - **sellSpreadBPS**: Spread rate in BPS to adjust value of tokens being sold below market value. - **sellFeeBPS**: Sell fee rate in BPS that will be subtracted from a sell order. ```solidity struct StandardPoolSellParameters { uint16 sellSpreadBPS; uint16 sellFeeBPS; } ``` --- ### StandardPoolSpendParameters This struct defines parameters used for spends. - **creatorShareBPS**: Rate in BPS of the value of tokens that will be allocated to the creator when tokens are spent. - Any value not allocated to the creator remains in the market value of tokens and increases the market value of all remaining tokens. ```solidity struct StandardPoolSpendParameters { uint16 creatorShareBPS; } ``` --- ## Standard Pool Functions The following functions are specific to Standard Pools. Platforms integrating TokenMaster with Standard Pools should use these functions to update token settings, claim creator emissions, retrieve the current settings, and retrieve the guardrail settings for display purposes. ```solidity // Administrative functions. function setBuyParameters(StandardPoolBuyParameters calldata _buyParameters) external; function setSellParameters(StandardPoolSellParameters calldata _sellParameters) external; function setSpendParameters(StandardPoolSpendParameters calldata _spendParameters) external; function setEmissionsHardCap(uint256 newHardCapAmount) external; function claimEmissions(address claimTo, uint256 forfeitAmount) external; // Current settings, guardrails and target supply. function getBuyParameters() external view returns(StandardPoolBuyParameters memory); function getSellParameters() external view returns(StandardPoolSellParameters memory); function getSpendParameters() external view returns(StandardPoolSpendParameters memory); function targetSupply() external view returns(bool useTargetSupply, uint256 target); function getParameterGuardrails() external view returns( uint16 minBuySpreadBPS, uint16 maxBuySpreadBPS, uint16 maxBuyFeeBPS, uint16 maxBuyDemandFeeBPS, uint16 minSellSpreadBPS, uint16 maxSellSpreadBPS, uint16 maxSellFeeBPS, uint16 maxSpendCreatorShareBPS ); function getCreatorEmissions() external view returns( uint256 claimed, uint256 claimable, uint256 hardCap, uint48 lastClaim, uint128 creatorEmissionRateNumerator, uint128 creatorEmissionRateDenominator ); ``` ## Standard Pool Events Platforms integrating TokenMaster _should_ index the following events on all tokens that are relevant to the platform to know when a token's buy, sell or spend parameters need to be checked for updates, when a creator updates their emissions hard cap, or when a creator claims emissions. ```solidity /// @dev Emitted when the token's buy settings have been updated. event BuyParametersUpdated(); /// @dev Emitted when the token's sell settings have been updated. event SellParametersUpdated(); /// @dev Emitted when the token's spend settings have been updated. event SpendParametersUpdated(); /// @dev Emitted when the creator updates their hard cap for creator emissions. event CreatorEmissionsHardCapUpdated(uint256 newHardCapAmount); /// @dev Emitted when a creator claims emissions. event CreatorEmissionsClaimed(address to, uint256 claimedAmount, uint256 forfeitedAmount); ``` # Stable Pool Stable Pools provide fixed pricing for tokens, ideal for use cases where consistent value is essential, such as stablecoins or tightly controlled economies. Unlike Standard Pools, the market price in a Stable Pool remains constant, with fees applied to buy and sell transactions. ## Stable Pool Settings: Stable Pools include straightforward adjustable parameters: - **Buy Fee**: Additional cost applied to token purchases, defined as a percentage of the stable price. - **Sell Fee**: Reduction applied to token sales, defined as a percentage of the stable price. ## Guardrails: To maintain predictability and user trust, Stable Pools enforce immutable fee limits established during deployment: - **Fee Limits**: Caps on buy and sell fees to prevent excessive transaction costs. ## Transactions --- **Buy:** A buyer will pay the market value of the tokens plus the creator buy fee. **Sell:** A seller will receive the market value of the tokens less the creator sell fee. **Spend:** A spender will receive no market value for their tokens, market value will instead be transferred to the creator share. ## Data Types --- ### StablePoolInitializationParameters This struct defines parameters used for stable pool initialization. - **initialSupplyRecipient**: Address to receive the initial supply amount. - **initialSupplyAmount**: Initial amount of supply to mint to the initial supply recipient. - **maxBuyFeeBPS**: Immutable maximum buy fee rate in BPS that may be set by the creator. - **maxSellFeeBPS**: Immutable maximum sell fee rate in BPS that may be set by the creator. - **initialBuyParameters**: Initial settings for buy parameters. - **initialSellParameters**: Initial settings for sell parameters. - **stablePairedPricePerToken**: Immutable ratio of paired token to pool token for stable pricing. ```solidity struct StablePoolInitializationParameters { address initialSupplyRecipient; uint256 initialSupplyAmount; uint256 maxBuyFeeBPS; uint256 maxSellFeeBPS; StablePoolBuyParameters initialBuyParameters; StablePoolSellParameters initialSellParameters; PairedPricePerToken stablePairedPricePerToken; } ``` --- ### StablePoolBuyParameters This struct defines parameters used for buys. - **buyFeeBPS**: Buy fee rate in BPS that will be applied to a buy order. ```solidity struct StablePoolBuyParameters { uint16 buyFeeBPS; } ``` --- ### StablePoolSellParameters This struct defines parameters used for sells. - **sellFeeBPS**: Sell fee rate in BPS that will be subtracted from a sell order. ```solidity struct StablePoolSellParameters { uint16 sellFeeBPS; } ``` --- ### PairedPricePerToken This struct defines parameters used for spends. - **numerator**: The numerator for the ratio of paired token to pool token that will be the stable price of a token before fees. - **denominator**: The denominator for the ratio of paired token to pool token that will be the stable price of a token before fees. ```solidity struct PairedPricePerToken { uint96 numerator; uint96 denominator; } ``` --- ## Stable Pool Functions The following functions are specific to Stable Pools. Platforms integrating TokenMaster with Stable Pools should use these functions to update token settings, retrieve the current settings, and retrieve the guardrail settings for display purposes. ```solidity // Administrative functions. function setBuyParameters(StablePoolBuyParameters calldata _buyParameters) external; function setSellParameters(StablePoolSellParameters calldata _sellParameters) external; // Current settings and guardrails. function getBuyParameters() external view returns(StablePoolBuyParameters memory); function getSellParameters() external view returns(StablePoolSellParameters memory); function getStablePriceRatio() external view returns(uint96 numerator, uint96 denominator); function getParameterGuardrails() external view returns (uint16 maxBuyFeeBPS, uint16 maxSellFeeBPS); ``` ## Stable Pool Events Platforms integrating TokenMaster _should_ index the following events on all tokens that are relevant to the platform to know when a token's buy or sell parameters need to be checked for updates. ```solidity /// @dev Emitted when the token's buy settings have been updated. event BuyParametersUpdated(); /// @dev Emitted when the token's sell settings have been updated. event SellParametersUpdated(); ``` # Promotional Pool Promotional Pools are designed for tokens without inherent market value, typically used to incentivize user actions or reward participation. These tokens are still paired with another token to establish a buy rate for specific use cases. Creators may grant minting and burning roles to addresses to allow the minting of tokens without limitations and burning of tokens from any account without limitations or approvals. ## Promotional Pool Settings --- - **Buy Cost**: Specifies the flat rate of paired tokens required to purchase promotional tokens. Buy cost may be changed by the creator any time after deployment and has no guardrail to limit the cost. ## Transactions --- **Buy:** A buyer will pay the buy cost for the promotional tokens being purchased. **Sell:** Sales are disabled for this pool type as there is no market value. **Spend:** Share values are unchanged as there is no market value. ## Data Types --- ### PromotionalPoolInitializationParameters This struct defines parameters used for promotional pool initialization. - **initialSupplyRecipient**: Address to receive the initial supply amount. - **initialSupplyAmount**: Initial amount of supply to mint to the initial supply recipient. - **initialBuyParameters**: Initial settings for buy parameters. ```solidity struct PromotionalPoolInitializationParameters { address initialSupplyRecipient; uint256 initialSupplyAmount; PromotionalPoolBuyParameters initialBuyParameters; } ``` --- ### PromotionalPoolBuyParameters This struct defines parameters used for buys. - **buyCostPairedTokenNumerator**: The numerator for the ratio of paired token to pooled tokens when buying. - **buyCostPoolTokenDenominator**: The denominator for the ratio of paired token to pooled tokens when buying. ```solidity struct PromotionalPoolBuyParameters { uint96 buyCostPairedTokenNumerator; uint96 buyCostPoolTokenDenominator; } ``` --- ## Promotional Pool Functions The following functions are specific to Promotional Pools. Platforms integrating TokenMaster with Promotional Pools should use these functions to update token settings, retrieve the current settings for display purposes, or building infrastructure to support direct minting and burning of promo tokens. To call the `mint` and `mintBatch` functions, the caller must be granted the `MINTER_ROLE` for the Promotional Pool. To call the `burn` function, the caller must be granted the `BURNER_ROLE` for the Promotional Pool. | Role | Role Identifier | | ----------- | -------------------------------------------------------------------- | | MINTER_ROLE | `0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6` | | BURNER_ROLE | `0x3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848` | ```solidity // Administrative functions. function setBuyParameters(PromotionalPoolBuyParameters calldata _buyParameters) external; // Current settings and guardrails. function getBuyParameters() external view returns(PromotionalPoolBuyParameters memory); // Minting / Burning functions. function mint(address to, uint256 amount) external; function mintBatch(address[] calldata toAddresses, uint256[] calldata amounts) external; function burn(address from, uint256 amount) external; ``` ## Promotional Pool Events Platforms integrating TokenMaster *should* index the following events on all tokens that are relevant to the platform to know when a token's buy parameters need to be checked for updates. ```solidity /// @dev Emitted when the token's buy settings have been updated. event BuyParametersUpdated(); ``` # Contract Deployments TokenMaster can be permissionlessly deployed by any wallet to the same addresses on any EVM compatible chain. #### Contract Addresses: | Contract | Address | | ------------------------------------- | -------------------------------------------- | | **TokenMaster Router** | `0x0E00009d00d1000069ed00A908e00081F5006008` | | **Standard Pool Factory** | `0x0000008fA9E16d40F879402FB580bAc7bdFA7d7e` | | **Stable Pool Factory** | `0x0000001bdD41D6eec104FA70DB06cA0869c96A46` | | **Promotional Pool Factory** | `0x000000B3F05aC48e679d211B2892BC08F02150C9` | ***Note: If TokenMaster is not yet deployed on your chain, use [the infrastructure deployment tool at developers.apptokens.com/infrastructure.](https://developers.apptokens.com/infrastructure)*** # Trusted Forwarder # Overview Trusted forwarders allow creators and marketplaces the ability to establish channels that become part of the order routing through Payment Processor. Channels allow for more granular trade attribution and metrics as well as the ability to block hostile apps from piggy-backing on the open Payment Processor protocol to vampire attack creators and marketplaces. There are two modes that a trusted forwarder can operate in - _open_ or _permissioned_. In _open_ mode the forwarder will relay all transactions with the caller's address appended to the transaction calldata. In _permissioned_ mode the forwarder will require the transaction data be signed by an address set by the forwarder's owner in order to be relayed. Creators may configure Payment Processor to only allow transactions that originate through specific channels (see [add trusted channel](../payment-processor/v3.0/for-creators/payment-settings#addtrustedchannelforcollection)) which, combined with a permissioned forwarder, allows creators to enforce their sales to be routed through specific frontend interfaces. ## How They Work Trusted forwarders work by receiving a function call from a user that contains the destination target and calldata to be relayed by the forwarder, appending the caller's address to the calldata and executing the call to the destination. Contracts that receive calls from the trusted forwarder check that the caller is a valid trusted forwarder and extract the original caller's address from the end of the calldata to use in place of the trusted forwarder's address as the caller of the function. For more information about deploying a trusted forwarder, continue to the [deployment](./deployment) page. # Deploying a Trusted Forwarder Trusted Forwarders ***MUST*** be deployed through the Limit Break Trusted Forwarder Factory to be considered a trusted forwarder for Payment Processor. To deploy a trusted forwarder call the `cloneTrustedForwarder` function on the Trusted Forwarder Factory. - `admin`: The initial owner of the trusted forwarder. The owner may update the forwarder's signer address, transfer and renounce ownership. - `appSigner`: For a *permissioned* forwarder this will be the signer address. For an *open* forwarder this must be set to `address(0)`. - `salt`: A 32-byte hex value that can only be used once per deployer address to create a deterministic deployment address for the trusted forwarder. *Note: Users that are mining a salt value for vanity/optimized addresses should be aware that the provided salt is hashed with their deployer address to determine the salt passed to the CREATE2 instruction.* ```solidity function cloneTrustedForwarder( address admin, address appSigner, bytes32 salt ) external returns (address trustedForwarder) ``` After a trusted forwarder is deployed, creators may set the trusted forwarder as an allowed channel with [add trusted channel](../payment-processor/v3.0/for-creators/payment-settings#addtrustedchannelforcollection) and block unauthorized channels by setting the `blockTradesFromUntrustedChannels` flag to true in [set collection payment settings](../payment-processor/v3.0/for-creators/payment-settings#setcollectionpaymentsettings). # Management Trusted forwarders are ownable smart contracts with the initial owner being the account address specified during their deployment. Ownership transfer and renounce follows standard Ownable contract methods. There are two functions on a trusted forwarder that are restricted to the contract owner - `updateSigner` and `deactivateSigner`. ## Update Signer `updateSigner` is used for one of two purposes: - To change the signer address from `address(0)` to a non-zero address to convert an *open* forwarder to a *permissioned* forwarder. - To update the signer address from a non-zero address to a different non-zero address if the dApp needs to sign with a different address for any reason. To make any of the above changes call `updateSigner` directly on the trusted forwarder contract from the account that is the forwarder owner with the appropriate `signer_` parameter value. ## Deactivate Signer `deactivateSigner` is used to change the signer address from a non-zero address to `address(0)` to convert a *permissioned* forwarder into an *open* forwarder. To deactivate the signer and convert the trusted forwarder into an *open* forwarder call `deactivateSigner` directly on the trusted forwarder contract from the account that is the forwarder owner. ## Functions ```solidity function updateSigner(address signer_) external; function deactivateSigner() external ``` # Usage Both *open* and *permissioned* trusted forwarders are designed to work with any smart contract that implements `TrustedForwarderERC2771Context` to create permissioned forwarding channels by routing user calls through the trusted forwarder. Trusted forwarder will *ALWAYS* append the caller of the trusted forwarder to the end of the calldata sent to the target address and forward the entire `msg.value` amount to the target. *Note: Trusted Forwarders do not have a fallback or receive function so they **CANNOT** receive payments from other contracts to refund excess payment amounts. Contracts that utilize a trusted forwarder should issue refunds directly to the appended caller address as native or wrapped tokens.* ## Open Trusted Forwarder Steps to execute a call through an *open* trusted forwarder: 1. Determine the target contract address for the call. 2. Determine the calldata for the transaction being sent to the target including the 4-byte function selector. 3. Call the trusted forwarder `forwardCall` with the `target`, `message`, and value to send. ```solidity function forwardCall( address target, bytes calldata message ) external payable returns (bytes memory returnData); ``` ## Permissioned Trusted Forwarder Steps to execute a call through an *open* trusted forwarder: 1. Determine the target contract address for the call. 2. Determine the calldata for the transaction being sent to the target including the 4-byte function selector. 3. Sign the message using the private key for the signer account that is set for the trusted forwarder. - The signature will be for an EIP712 structured message using: - The domain separator returned by the `domainSeparatorV4` view function - *with* - The message data being an AppSigner message defined [here](#appsigner-type-hash). 4. Call the trusted forwarder `forwardCall` with the `target`, `message`, `signature` and value to send. ```solidity function forwardCall( address target, bytes calldata message, SignatureECDSA calldata signature ) external payable returns (bytes memory returnData); ``` ### AppSigner Type Hash ``` AppSigner(bytes32 messageHash,address target,address sender) ``` # Contract Deployments #### Deployed Chains: - [Ethereum Mainnet](#ethereum) - [Base Mainnet](#base) - [Optimism Mainnet](#optimism) - [Arbitrum One Mainnet](#arbitrum) - [Polygon Mainnet](#polygon) - [Polygon zkEVM Mainnet](#polygon-zkevm) - [Avalanche C-Chain Mainnet](#avalanche-c-chain) - [Binance Smart Chain (BSC) Mainnet](#binance-smart-chain) - [Ethereum Sepolia Testnet](#ethereum-sepolia) #### Contract Addresses: | Contract | Address | |------------------------------------------------|----------------------------------------------| | Trusted Forwarder Implementation | `0xFF000047aBEA9064C699c0727148776e4E17771C` | | Trusted Forwarder Factory | `0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE` | ## Mainnet Deployments ### Ethereum - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://etherscan.io/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://etherscan.io/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Base - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://basescan.org/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://basescan.org/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Optimism - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://optimistic.etherscan.io/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://optimistic.etherscan.io/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Arbitrum - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://arbiscan.io/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://arbiscan.io/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Polygon - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://polygonscan.com/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://polygonscan.com/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Polygon zkEVM - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://zkevm.polygonscan.com/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://zkevm.polygonscan.com/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Binance Smart Chain - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://bscscan.com/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://bscscan.com/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ### Avalanche C-Chain - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://snowtrace.io/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://snowtrace.io/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) ## Testnet Deployments ### Ethereum Sepolia - Trusted Forwarder Implementation - [0xFF000047aBEA9064C699c0727148776e4E17771C](https://sepolia.etherscan.io/address/0xFF000047aBEA9064C699c0727148776e4E17771C) - Trusted Forwarder Factory - [0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE](https://sepolia.etherscan.io/address/0xFF0000B6c4352714cCe809000d0cd30A0E0c8DcE) # Wrapped Native # Overview **The Wrapped Native contract wraps native tokens (e.g. Ether) into an ERC-20 token. Wrapped Native is designed to be a more gas efficient, modern, more fully featured canonical replacement for WETH9 that can be deployed to a consistent, deterministic address on all chains.** Wrapped Native features the following improvements over WETH9. - **Deterministically Deployable By Anyone To A Consistent Address On Any Chain!** - **More Gas Efficient Operations Than WETH9!** - **`approve` and `transfer` functions are payable** - will auto-deposit when `msg.value > 0`. This feature will allow a user to wrap and approve a protocol in a single action instead of two, improving UX and saving gas. - **`depositTo`** - allows a depositor to specify the address to give WNATIVE to. Much more gas efficient for operations such as native refunds from protocols compared to `deposit + transfer`. - **`withdrawToAccount`** - allows a withdrawer to withdraw to a different address. - **`withdrawSplit`** - allows a withdrawer to withdraw and send native tokens to several addresses at once. - **Permit Functions** - allows for transfers and withdrawals to be approved to spenders/operators gaslessly using EIP-712 signatures. Permitted withdrawals allow gas sponsorship to unwrap wrapped native tokens on the user's behalf, for a small convenience fee specified by the app. This is useful when user has no native tokens on a new chain but they have received wrapped native tokens. ## Deployment Wrapped Native can be deployed to any EVM blockchain at address `0x6000030000842044000077551D00cfc6b4005900` using Limit Break's infrastructure deployment tool at https://developers.apptokens.com/infrastructure. # Usage Wrapped Native is backwards compatible with WETH9 - it includes all interfaces available in the original WETH. However, it includes additional features for better quality of life and better protocol integrations. This page covers typical usage scenarios. ## Deployment Wrapped Native can be deployed to any EVM blockchain at address `0x6000030000842044000077551D00cfc6b4005900` using Limit Break's infrastructure deployment tool at https://developers.apptokens.com/infrastructure. ## Depositing/Wrapping Native Tokens ### Native value transfer to contract A native token transfer to the contract without calldata will trigger a deposit via the `receive()` function. A native token transfer to the contract with calldata will trigger a deposit via the `fallback()` function. The `msg.sender` account will be credited with WNATIVE. ### deposit Function Calling the `deposit` function and including native value will credit the `msg.sender` account with WNATIVE. ```solidity /** * @notice Deposits `msg.value` funds into the `msg.sender` account, increasing their wrapped native token balance. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has increased by `msg.value`. * @dev 2. The `msg.sender`'s native token balance has decreased by `msg.value`. * @dev 3. The `msg.sender`'s wrapped native token balance has increased by `msg.value`. * @dev 4. A `Deposit` event has been emitted. The `msg.sender` address is logged in the event. */ function deposit() public payable; ``` ### depositTo Function Calling the `depositTo` function and including native value will credit the specified `to` account with WNATIVE, while depositing the `msg.sender` account's native funds. For protocols that issue native refunds by wrapping them and transferring the ERC-20 to the refundee, `depositTo` offers the most direct, gas-efficient mechanism. Some protocols may issue refunds this way to limit re-entrancy risk. ```solidity /** * @notice Deposits `msg.value` funds into specified user's account, increasing their wrapped native token balance. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has increased by `msg.value`. * @dev 2. The `msg.sender`'s native token balance has decreased by `msg.value`. * @dev 3. The `to` account's wrapped native token balance has increased by `msg.value`. * @dev 4. A `Deposit` event has been emitted. Caveat: The `to` address is logged in the event, not `msg.sender`. * * @param to The address that receives wrapped native tokens. */ function depositTo(address to) public payable; ``` ## Withdrawing/Unwrapping Native Tokens ### withdraw Function Calling the `withdraw` function will debit the `msg.sender` account's WNATIVE balance and credit them with the equivalent amount of native token. ```solidity /** * @notice Withdraws `amount` funds from the `msg.sender` account, decreasing their wrapped native token balance. * * @dev Throws when the `msg.sender`'s wrapped native token balance is less than `amount` to withdraw. * @dev Throws when the unwrapped native funds cannot be transferred to the `msg.sender` account. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has decreased by `amount`. * @dev 2. The `msg.sender`'s wrapped native token balance has decreased by `amount`. * @dev 3. The `msg.sender`'s native token balance has increased by `amount`. * @dev 4. A `Withdrawal` event has been emitted. The `msg.sender` address is logged in the event. * * @param amount The amount of wrapped native tokens to withdraw. */ function withdraw(uint256 amount) public; ``` ### withdrawToAccount Function Calling the `withdrawToAccount` function will debit the `msg.sender` account's WNATIVE balance, but credit the `to` account with the equivalent amount of native token. ```solidity /** * @notice Withdraws `amount` funds from the `msg.sender` account, decreasing their wrapped native token balance. * * @dev Throws when the `msg.sender`'s wrapped native token balance is less than `amount` to withdraw. * @dev Throws when the unwrapped native funds cannot be transferred to the `to` account. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has decreased by `amount`. * @dev 2. The `msg.sender`'s wrapped native token balance has decreased by `amount`. * @dev 3. The `to` account's native token balance has increased by `amount`. * @dev 4. A `Withdrawal` event has been emitted. Caveat: The `msg.sender` address is logged in the event, not `to`. * * @param to The address that receives the unwrapped native tokens. * @param amount The amount of wrapped native tokens to withdraw. */ function withdrawToAccount(address to, uint256 amount) public; ``` ### withdrawSplit Function Calling the `withdrawSplit` function will debit the `msg.sender` account's WNATIVE balance, but credit the specified amounts the multiple `to` accounts. ```solidity /** * @notice Withdraws funds from the `msg.sender` and splits the funds between multiple receiver addresses. * * @dev Throws when the `msg.sender`'s wrapped native token balance is less than the sum of `amounts` to withdraw. * @dev Throws when the unwrapped native funds cannot be transferred to one or more of the receiver addresses. * @dev Throws when the `toAddresses` and `amounts` arrays are not the same length. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has decreased by the sum of `amounts`. * @dev 2. The `msg.sender`'s wrapped native token balance has decreased by the sum of `amounts`. * @dev 3. The receiver addresses' native token balances have increased by the corresponding amounts in `amounts`. * @dev 4. A `Withdrawal` event has been emitted for each receiver address. Caveat: The `msg.sender` address is * logged in the events, not the receiver address. * * @param toAddresses The addresses that receive the unwrapped native tokens. * @param amounts The amounts of wrapped native tokens to withdraw for each receiver address. */ function withdrawSplit(address[] calldata toAddresses, uint256[] calldata amounts) external; ``` ## Approvals and Transfers ### approve Function ERC-20 WNATIVE approval for spenders, but with optional deposit feature. This is a powerful UX improvement, as protocols such as Seaport and Payment Processor no longer need separate deposit and approval calls when making offers. ```solidity /** * @notice Approves `spender` to spend/transfer `amount` of the `msg.sender`'s wrapped native tokens. * When `amount` is set to `type(uint256).max`, the approval is unlimited. * * @notice Unlike a typical ERC-20 token, this function is payable, allowing for a `deposit` and approval to be * executed simultaneously. If `msg.value` is greater than zero, the function will deposit the funds * into the `msg.sender` account before approving the `spender` to spend/transfer the funds. * If `msg.value` is zero, the function will only approve the `spender` to spend/transfer the funds. * This feature is intended to improve the UX of users using wrapped native tokens so that users don't have * to perform two transactions to first deposit, then approve the spending of their tokens, saving gas in * the process. * * @dev

Postconditions:

* @dev 1. The `spender` is approved to spend/transfer `amount` of the `msg.sender`'s wrapped native tokens. * @dev 2. A `Approval` event has been emitted. The `msg.sender` address, `spender` address, and `amount` of the * updated approval are logged in the event. * @dev 3. If `msg.value` is greater than zero, the `msg.sender`'s wrapped native token balance has increased by * `msg.value`. * @dev 4. If `msg.value` is greater than zero, a `Deposit` event has been emitted. The `msg.sender` address is * logged in the event. * * @param spender The address that is approved to spend/transfer the `msg.sender`'s wrapped native tokens. * @param amount The amount of wrapped native tokens that the `spender` is approved to spend/transfer. Approved * spending is unlimited when this values is set to `type(uint256).max`. * * @return Always returns `true`. */ function approve(address spender, uint256 amount) public payable returns (bool); ``` ### transfer Function ERC-20 WNATIVE transfer, but with optional deposit feature. ```solidity /** * @notice Transfers an `amount` of wrapped native tokens from the `msg.sender` to the `to` address. * * @notice If the `msg.value` is greater than zero, the function will deposit the funds into the `msg.sender` account * before transferring the wrapped funds. Otherwise, the function will only transfer the funds. * * @dev Throws when the `msg.sender` has an insufficient balance to transfer `amount` of wrapped native tokens. * * @dev

Postconditions:

* @dev 1. When `msg.value` is greater than zero, this contract's native token balance has increased by `msg.value`. * @dev 2. When `msg.value` is greater than zero, the `msg.sender`'s native token balance has decreased by `msg.value`. * @dev 3. When `msg.value` is greater than zero, the `msg.sender`'s wrapped native token balance has increased by `msg.value`. * @dev 4. When `msg.value` is greater than zero, a `Deposit` event has been emitted. The `msg.sender` address is logged in the event. * @dev 5. The `amount` of wrapped native tokens has been transferred from the `msg.sender` account to the `to` account. * @dev 6. A `Transfer` event has been emitted. The `msg.sender` address, `to` address, and `amount` are logged in the event. * * @param to The address that receives the wrapped native tokens. * @param amount The amount of wrapped native tokens to transfer. * * @return Always returns `true`. */ function transfer(address to, uint256 amount) public payable returns (bool); ``` ### transferFrom Function ERC-20 WNATIVE transferFrom, but with optional deposit feature. ```solidity /** * @notice Transfers an `amount` of wrapped native tokens from the `from` to the `to` address. * * @notice If the `msg.value` is greater than zero, the function will deposit the funds into the `from` account * before transferring the wrapped funds. Otherwise, the function will only transfer the funds. * @notice **As a reminder, the `msg.sender`'s native tokens will be deposited and the `from` (not the `msg.sender`) * address will be credited before the transfer. Integrating spender/operator protocols MUST be aware that * deposits made during transfers will not credit their own account.** * * @dev Throws when the `from` account has an insufficient balance to transfer `amount` of wrapped native tokens. * @dev Throws when the `msg.sender` is not the `from` address, and the `msg.sender` has not been approved * by `from` for an allowance greater than or equal to `amount`. * * @dev

Postconditions:

* @dev 1. When `msg.value` is greater than zero, this contract's native token balance has increased by `msg.value`. * @dev 2. When `msg.value` is greater than zero, the `msg.sender`'s native token balance has decreased by `msg.value`. * @dev 3. When `msg.value` is greater than zero, the `from` account's wrapped native token balance has increased by `msg.value`. * @dev 4. When `msg.value` is greater than zero, a `Deposit` event has been emitted. The `from` address is logged in the event. * @dev 5. The `amount` of wrapped native tokens has been transferred from the `from` account to the `to` account. * @dev 6. A `Transfer` event has been emitted. The `from` address, `to` address, and `amount` are logged in the event. * * @param from The address that transfers the wrapped native tokens. * @param to The address that receives the wrapped native tokens. * @param amount The amount of wrapped native tokens to transfer. * * @return Always returns `true`. */ function transferFrom(address from, address to, uint256 amount) public payable returns (bool); ``` ## Permits ***Note: Permit signatures may be signed by EOAs or smart contracts supporting EIP-1271.*** ### permitTransfer Function Allows a spender / protocol to transfer a user's WNATIVE tokens without a prior on-chain approval. Transfer permits are single-use, and can be used to pre-approve a spender to transfer a specific amount of WNATIVE token with a gasless signature. ```solidity /** * @notice Allows a spender/operator to transfer wrapped native tokens from the `from` account to the `to` account * using a gasless signature from the `from` account so that the `from` account does not need to pay gas * to set an on-chain allowance. * * @notice If the `msg.value` is greater than zero, the function will deposit the funds into the `from` account * before transferring the wrapped funds. Otherwise, the function will only transfer the funds. * @notice **As a reminder, the `msg.sender`'s native tokens will be deposited and the `from` (not the `msg.sender`) * address will be credited before the transfer. Integrating spender/operator protocols MUST be aware that * deposits made during transfers will not credit their own account.** * * @dev Throws when the `from` account is the zero address. * @dev Throws when the `msg.sender` does not match the operator/spender from the signed transfer permit. * @dev Throws when the permitAmount does not match the signed transfer permit. * @dev Throws when the nonce does not match the signed transfer permit. * @dev Throws when the expiration does not match the signed transfer permit. * @dev Throws when the permit has expired. * @dev Throws when the requested transfer amount exceeds the maximum permitted transfer amount. * @dev Throws when the permit nonce has already been used or revoked/cancelled. * @dev Throws when the master nonce has been revoked/cancelled since the permit was signed. * @dev Throws when the permit signature is invalid, or was not signed by the `from` account. * @dev Throws when the `from` account has an insufficient balance to transfer `transferAmount` of wrapped native tokens. * * @dev

Postconditions:

* @dev 1. When `msg.value` is greater than zero, this contract's native token balance has increased by `msg.value`. * @dev 2. When `msg.value` is greater than zero, the `msg.sender`'s native token balance has decreased by `msg.value`. * @dev 3. When `msg.value` is greater than zero, the `from` account's wrapped native token balance has increased by `msg.value`. * @dev 4. When `msg.value` is greater than zero, a `Deposit` event has been emitted. The `from` address is logged in the event. * @dev 5. `nonce` for `from` account is invalidated. * @dev 6. A `PermitNonceInvalidated` event has been emitted. * @dev 7. The `transferAmount` of wrapped native tokens has been transferred from the `from` account to the `to` account. * @dev 8. A `Transfer` event has been emitted. The `from` address, `to` address, and `transferAmount` are logged in the event. * * @param from The address that transfers the wrapped native tokens. * @param to The address that receives the wrapped native tokens. * @param transferAmount The amount of wrapped native tokens to transfer. * @param permitAmount The maximum amount of wrapped native tokens that can be transferred, signed in permit. * @param nonce The nonce, signed in permit. * @param expiration The expiration timestamp, signed in permit. * @param signedPermit The signature of the permit. */ function permitTransfer( address from, address to, uint256 transferAmount, uint256 permitAmount, uint256 nonce, uint256 expiration, bytes calldata signedPermit ) external payable ``` The permit is a signed EIP-712 message that matches the following format/typehash and domain separator. Domain separator values are: - name: "Wrapped Native" - version: "1" - chainId: ***Specify the chainId of the chain where the permit is valid.*** - verifyingContract: `0x6000030000842044000077551D00cfc6b4005900` ```solidity bytes32 private constant DOMAIN_SEPARATOR = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 constant PERMIT_TRANSFER_TYPEHASH = keccak256("PermitTransfer(address operator,uint256 amount,uint256 nonce,uint256 expiration,uint256 masterNonce)"); ``` ### doPermittedWithdraw Function Allows a user to withdraw or unwrap WNATIVE tokens on a user's behalf. This is useful when a user has WNATIVE tokens, but has insufficient native funds to withdraw or unwrap themselves. In this case, the permitted operator could be an app that charges a small convenience fee for the withdrawal service. Note that convenience fees are taxed by the protocol at a rate of 10%. So if the convience fee is 0.25%, the protocol fee will be 0.025% and the convenience fee receiver gets to keep 0.225% of the withdrawn amount. Convenience fees for the app are acknowledged in the user's signed permit. ```solidity /** * @notice Allows a spender/operator to withdraw wrapped native tokens from the `from` account to the `to` account * using a gasless signature signed by the `from` account to prove authorization of the withdrawal. * * @dev Throws when the `from` account is the zero address. * @dev Throws when the `msg.sender` does not match the operator/spender from the signed withdrawal permit. * @dev Throws when the permit has expired. * @dev Throws when the amount does not match the signed withdrawal permit. * @dev Throws when the nonce does not match the signed withdrawal permit. * @dev Throws when the expiration does not match the signed withdrawal permit. * @dev Throws when the convenience fee reciever and fee does not match the signed withdrawal permit. * @dev Throws when the `to` address does not match the signed withdrawal permit. * @dev Throws when the permit nonce has already been used or revoked/cancelled. * @dev Throws when the master nonce has been revoked/cancelled since the permit was signed. * @dev Throws when the permit signature is invalid, or was not signed by the `from` account. * @dev Throws when the `from` account has an insufficient balance to transfer `transferAmount` of wrapped native tokens. * * @dev

Postconditions:

* @dev 1. This contract's native token balance has decreased by `amount`, less convenience and infrastructure * fees that remain wrapped. * @dev 2. The `from` account's wrapped native token balance has decreased by `amount`. * @dev 3. The `to` account's native token balance has increased by `amount`, less convenience and/or infrastructure fees. * @dev 4. The `convenienceFeeReceiver` account's wrapped native token balance has increased by the convenience fee. * @dev 5. The infrastructure tax account's wrapped native token balance has increased by the infrastructure fee. * @dev 6. `nonce` for `from` account is invalidated. * @dev 7. A `PermitNonceInvalidated` event has been emitted. * @dev 8. A `Withdrawal` event has been emitted. Caveat: The `from` address is logged in the event, not `to` or `msg.sender`. * * @param from The address that from which funds are withdrawn. * @param to The address that receives the withdrawn funds. * @param amount The amount of wrapped native tokens to withdraw. * @param nonce The nonce, signed in permit. * @param expiration The expiration timestamp, signed in permit. * @param convenienceFeeReceiver The address that receives the convenience fee. * @param convenienceFeeBps The basis points of the convenience fee. * @param signedPermit The signature of the permit. */ function doPermittedWithdraw( address from, address to, uint256 amount, uint256 nonce, uint256 expiration, address convenienceFeeReceiver, uint256 convenienceFeeBps, bytes calldata signedPermit ) external; ``` The withdrawal permit is a signed EIP-712 message that matches the following format/typehash and domain separator. Domain separator values are: - name: "Wrapped Native" - version: "1" - chainId: ***Specify the chainId of the chain where the permit is valid.*** - verifyingContract: `0x6000030000842044000077551D00cfc6b4005900` ```solidity bytes32 private constant DOMAIN_SEPARATOR = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 constant PERMIT_WITHDRAWAL_TYPEHASH = keccak256("PermitWithdrawal(address operator,uint256 amount,uint256 nonce,uint256 expiration,uint256 masterNonce,address to,address convenienceFeeReceiver,uint256 convenienceFeeBps)"); ``` ## Cancelling Permits ### revokeMyOutstandingPermits Function To cancel ALL outstanding permits (both transfer and withdrawal), use the `revokeMyOutstandingPermits` function, which will increment the user's master nonce. The user must call this function themselves. ```solidity /** * @notice Allows the `msg.sender` to revoke/cancel all prior permitted transfer and withdrawal signatures. * * @dev

Postconditions:

* @dev 1. The `msg.sender`'s master nonce has been incremented by `1` in contract storage, rendering all signed * permits using the prior master nonce unusable. * @dev 2. A `MasterNonceInvalidated` event has been emitted. */ function revokeMyOutstandingPermits() external; ``` ### revokeMyNonce Function To cancel a single transfer or withdrawal permit, use the `revokeMyNonce` function, which will permanently invalidate the supplied permit nonce. ```solidity /** * @notice Allows the `msg.sender` to revoke/cancel a single, previously signed permitted transfer or withdrawal * signature by specifying the nonce of the individual permit. * * @dev Throws when the `msg.sender` has already revoked the permit nonce. * @dev Throws when the permit nonce was already used successfully. * * @dev

Postconditions:

* @dev 1. The specified `nonce` for the `msg.sender` has been revoked and can * no longer be used to execute a permitted transfer or withdrawal. * @dev 2. A `PermitNonceInvalidated` event has been emitted. * * @param nonce The nonce that was signed in the permitted transfer or withdrawal. */ function revokeMyNonce(uint256 nonce) external; ``` # Gas Benchmarks ## Raw Savings Per Function | Function | Gas Saved WNATIVE vs WETH9 | |-----------------------------------|----------------------------| | balanceOf | 88 | | native transfer (deposit) | 48 | | deposit | 108 | | withdraw | 395 | | approve | 213 | | transfer | 627 | | transferFrom (Self Transfer) | 481 | | transferFrom (Operator Allowance) | 1080 | | transferFrom (Operator Unlimited) | 508 | ## Aggregate L1 Savings ($2189.25 Avg USD/ETH) | Operation | Savings Per Operation | ETH Mainnet Operations (2022/09/15 - 2024/10/28) | Gas Units Saved | Avg Post-Merge Gas Price | ETH Saved | |---------------|-------------------------|--------------------------------------------------|-----------------|--------------------------|---------------| | balanceOf | 88 | 327,453,459 | 29B | 21.6 gwei | 626E | | transfer | 627 | 179,154,025 | 112B | 21.6 gwei | 2419E | | withdraw | 395 | 61,370,490 | 24B | 21.6 gwei | 518E | | transferFrom | 794 | 24,489,452 | 19.5B | 21.6 gwei | 421E | | approve | 213 | 6,936,611 | 1.5B | 21.6 gwei | 32E | | deposit | 108 | 125,633 | 13.5M | 21.6 gwei | 0E | | total | | | 186B | 21.6 gwei | 4016E ($8.8M) | ***Fun Fact: The eight zero-bytes mined in the official Wrapped Native address would save an additional 96 gas in calldata. Accounting for additional calldata gas savings, it is estimated that and additional $1.2M would have been saved!*** ***Note: Benchmarks focused only on interfaces that are shared between WETH9 and WNATIVE, and do not include measurements for features that did not exist in WETH9. Benchmarks were prepared and executed so that the identical conditions were recreated on both contracts in order to achieve and accurate comparison between contracts.***