Royalties have been handled in various ways on Ethereum, from centralized royalty systems and custom on-chain royalty solutions to the EIP2981 royalty standard. To support new NFT projects building on StarkNet as well as maintaining interoperability with existing Immutable X NFTs, we explored different ways to define and manage royalties for non-fungible assets. The goal was to design a golden standard for royalties on StarkNet, flexible and comprehensive enough to accommodate all current and future partner requirements.
This royalty design is incorporated into our recommended ERC721 preset on StarkNet. It will be compatible with our settlement contracts to trustlessly enforce royalty distributions for trades made on the Immutable X protocol.
Chosen solution: EIP2981
The current EIP-2981 royalty standard is an open-ended implementation of royalties for NFTs that specifies a single royalty recipient and percentage amount per token ID in a collection. Royalties are not enforced at the token contract level, and this standard simply lets the token contract signal a currency-agnostic royalty amount given a sale price.
This is due to the limitations of the contract standards themselves — NFT trade settlement involves a third party contract typically executing transferFrom on both the NFT and ERC20 contract to atomically swap ownership of the two assets. The NFT contract itself does not have any context for the transfer or visibility into the trade details. No fees can be enforced without impairing standard transfer functionality, and therefore the responsibility is on the exchange and settlement layer on which the trade is being executed to enforce and distribute these royalties.
While it hasn’t seen widespread adoption yet, the EIP2981 standard appears to be the best solution to establish as an ecosystem standard as we build on StarkNet. Our royalty design thus follows EIP2981 for the most compatibility and familiarity in the ecosystem while also exploring additional features that cover a range of different ways these royalties can be defined and managed.
Since EIP2981 only specifies the royaltyInfo function for royalty calculation, there was the flexibility to experiment with different mechanisms for managing royalty details. These were narrowed down to four versions of a base ERC2981 contract, each with varying degrees of mutability and implications for contract owners and NFT holders.
OpenZeppelin’s Solidity implementation of the EIP2981 royalty standard was used as a reference when designing the Cairo equivalents.
- Immutable - This is a fully immutable implementation of ERC2981, which handles royalties as an immutable part of the tokens that can never be changed after initialization. This approach gives the contract owner the least flexibility to adjust royalty details but is also a strong guarantee for the token holders that the royalties they see now won't change.
- Mutable - On the opposite end of the spectrum, an entirely mutable version of ERC2981 is the most common implementation of royalties on Ethereum to date. Here the contract owner has maximum flexibility to change royalty details, adjusting the percentage or royalty recipient as needed. Token holders, however, have no guarantee that royalties will be constant, and the trust is on the contract owner to not abuse that permission.
- Unidirectional Mutable - This is an interesting foray into 'unidirectional mutability' - a powerful concept not yet widely explored but has strong potential to provide flexibility and guarantees at the same time. This concept may be extended to other future primitives as well. In this implementation, the contract owner can only reduce royalty percentages after initialization but cannot increase them. Royalty recipient addresses can still be changed without restrictions, which is fine given that there is no impact on the NFT holder. This gives owners some flexibility to make (limited) changes to their business model or tokenomics while protecting NFT holders with the guarantee that their assets won't lose value overnight if the royalty changes negatively and impacts saleability.
- Flagged - An attempt to achieve the best of both worlds: The NFT collection has entirely mutable royalties upon initialization, allowing the contract owner to adjust and experiment with royalties as required, and then is only unidirectionally mutable after a flag is flipped in the contract, which cannot be unflipped. This implementation gives the most owner flexibility and holder guarantees after the owner turns full mutability off, at the cost of additional complexity and overhead both technically and operationally.
Our ERC721 preset includes the Unidirectional Mutable ERC2981 implementation as the recommended approach to royalties for NFTs. This approach provides some flexibility for contract owners to change royalty details while providing a strong guarantee for NFT holders that helps push the ecosystem towards a more fair and transparent playing field. We would love to see it adopted as a default standard for royalties going forwards, though all four variations of royalty implementations are available for use to accommodate bespoke project requirements.
In all versions of the ERC2981 implementation, the fee denominator (which determines the precision to which royalties can be defined) is fixed at 10,000. This means that the fee percentage is expressed in basis points — a relatively standard unit of measurement for percentages in finance. Given that this denomination supports percentages from 0.01% — 100.00% up to 2 decimal points of precision, it should cover most use cases. This, therefore, does not justify implementing a variable fee denominator, reducing complexity and mitigating any numeric representation risks.
If a project requires more precision in its royalty percentages, they can easily modify the base contract to change the fee denominator variable as needed.
Contract-wide vs token-specific royalties
Contract-wide royalties, also referred to as the default royalty, is a royalty applied to all tokens in the contract. Token-specific royalties refer to a royalty applied to one specific token ID out of the collection. Setting a token-specific royalty (where available) will override the default contract-wide royalty for that token ID. Leaving it undefined or resetting the token-specific royalty will result in the default royalty being used.
In most of the above royalty implementations, both contract-wide and token-specific royalties can be defined, except for the Immutable implementation, as no changes can be made to royalties after initialization. It isn't feasible to define every token-specific royalty upon initialization. This approach provides the most flexibility for contract owners to create custom royalty models, for example, minter/holder royalties, different royalties for different 'generations' within a collection, etc.
As part of the royalties design, there is also a requirement for royalty fees to be able to be split between multiple recipients. This is also needed to achieve parity with our current royalty features on StarkEx, which allow for multiple royalty recipients. Since the EIP2981 royalty standard only accommodates for a single recipient address and royalty percentage to be defined per token ID, an intermediary fee splitter contract can be used to handle the receipt royalty payments to achieve this. This contract trustlessly manages the splitting and distribution of funds paid to it and aims to be simple and extensible for any bespoke functionality around accounting, taxes, etc., to be implemented on top. While this approach requires more setup and contracts to be deployed, it lets us maintain compatibility with EIP2981 and avoid having to push for a completely new royalty standard.
The payment splitter implementation on StarkNet is based on the OpenZeppelin Solidity implementation.
Pull payments — The contract follows the pull payment model to decrease the complexity of payments made to the contract and mitigate various vulnerabilities and concerns around push payments. This means that payments are not automatically forwarded to the recipient accounts and are instead held in this intermediary contract until withdrawn by the respective recipients in a separate transaction. The contract is currency-agnostic and accepts payments in any standard ERC20.
Split mechanism — Following OpenZeppelin’s implementation, the payment splitter is initialized with a list of payees (accounts) and a corresponding list of shares representing each payee’s share of all funds received by the contract. A payee’s percentage share can be calculated by (shares)/(total_shares), and in the contract, shares is a felt type so that you can define an arbitrary precision for fee splits. Payees and shares are set once upon initialization and then are immutable for the lifetime of the contract. Having the ability to edit these fields adds complexity and additional scope to cover, and the current alternative is to deploy a new contract with the updated configuration and update the royalty recipient in the NFT contract.
Events — The Solidity implementation emits events on any ETH payments received; however, we can’t replicate this functionality in Cairo as ETH exists as an ERC20 on StarkNet. Implementing some kind of public callback function to handle general ERC20 receipts also does not guarantee an actual receipt of any funds because anyone can call it freely; however it is possible to whitelist and react to specified ERC20 payments if necessary. Since emitted blockchain events are treated as a source of truth for off-chain services, we decided to omit these types of events to avoid any desynchronization of states between on and off-chain, but a whitelist method can be implemented by the user. We still emit events on payment releases and expose additional getter methods to check the contract balances and amount owed to any given account to help with the tracking of funds.
Whether you are a game developer, artist, or other content creator exploring NFTs, royalties are a powerful tool that can provide a steady stream of income from your work or even enable interesting tokenomics for your collection. Royalties also help incentivize continued development of the product and ecosystem as the creators will benefit from higher trading volume and prices. When building your project on StarkNet, using these royalty implementations (in line with EIP2981) will ensure the most compatibility with the ecosystem, including Immutable X where we can trustlessly enforce royalty distributions.
As the StarkNet ecosystem is in its infancy, it is essential to scrutinize any libraries and both written and imported contracts, as everything is quite experimental and not yet battle-tested. We welcome the community to deploy and play around with our contracts — try them out, break things, build on top of them.