Solidity help: Creating an EIP-2981 payment splitter for the royalty registry

Hi there!

I’m looking to deploy an override contract to the royalty registry.

Essentially, we need to deploy a new contract with some simple royalty rules which is also a payment splitter. We’ve had advice from other sources that although the registry supports multiple recipients, it’s still best to try and follow EIP-2981 (which is why we’re using a splitter).

Anyway, here is what I’ve come up with:

import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
 * Contract for use with the Royalty Registry as an override supporting EIP2981.
 *
 * This contract is also a payment splitter so multiple payees can be supported.
 *
 * Version: 1.0
 */
contract RegistrySplitterEIP2981 is PaymentSplitter, ERC165 {

    constructor(address[] memory _payees, uint256[] memory _bps)
        payable
        PaymentSplitter(_payees, _bps)
    {}

    bytes4 private constant _INTERFACE_ID_ROYALTIES_EIP2981 = 0x2a55205a;

    function supportsInterface(bytes4 interfaceId) public view override(ERC165) returns (bool) {
        return interfaceId == _INTERFACE_ID_ROYALTIES_EIP2981 || super.supportsInterface(interfaceId);
    }

    /**
     * ERC-2981
     */
    function royaltyInfo(uint256 /* tokenId */, uint256 value) external view virtual returns (address, uint256)
    {
        return (address(this), (totalShares() * value) / 10000);
    }
}

So the idea here is that this contract is both itself a payment splitter but also implements the 2981 spec.

I’ve tried this experimentally with the royalty registry and it seems to work.

My question is this: is there any problem with having a single contract which is both itself a payment splitter and a 2981?

Is it weird or problematic that when anyone asks “where should I send royalties?” this contract will return itself (its own address)?

I’m thinking here about some subtle Solidity/EVM behaviour that maybe I’m not aware of.

I guess this is an inheritances vs. composition question. E.g. an alternative could have been something like:

contract RegistrySplitterEIP2981 is ERC165 {

    PaymentSplitterImpl paymentSplitter;
    
    constructor(address[] memory _payees, uint256[] memory _bps)
    {
        paymentSplitter = PaymentSplitterImpl(_payees, _bps);
    }
    
    ...
    
    function royaltyInfo(uint256 /* tokenId */, uint256 value) external view virtual returns (address, uint256)
    {
        return (address(paymentSplitter), (paymentSplitter.totalShares() * value) / 10000);
    }
}

I did also have a play with the second approach but I struggled to get the inner contract verified on etherscan, so at the moment I slightly prefer the first approach.

Does anyone have any guidance about this?

Thanks!