Sequencer Fee Sharing

SFS - Sequencer Fee Sharing

Intro to the Sequencer Fee Sharing

When building a layer 2 with the Arbitrum Orbit stack, block production is primarily managed by a single party, called the sequencer, which helps the network by providing the following services:

  • Providing transaction confirmations and state updates

  • Constructing and executing L2 blocks

  • Submitting user transactions to L1

These sequencers are centralized for security and managed by AppChain/Arbitrum and that’s why AppChain benefits from the sequencer fees generated. That’s where the SFS comes in, giving part of these fees and sharing them with developers that deploy smart contracts.

Developers can earn a share of the network sequencer fees just by registering their contracts in the Fee Sharing Contract.

Overview

To earn SFS fees, you must register your contracts with the Sequencer Fee Sharing (SFS) smart contract deployed on AppChain testnet at 0x1f8bBAd193E65daAb9f89A36ff01A92f55B65e6f. The source code for FeeSharing.sol can be found here (opens in a new tab).

The Sequencer Fee Sharing (SFS) contract acts as a registry where all the balances are tracked and stored. Once you deploy your contract and register it in the SFS, you will instantly start earning fees whenever your contract is used.

Upon registering a contract, a transferrable NFT representing the right to claim that contract's revenue is minted to a _recipient address of your choice. The SFS NFT allows anyone who possesses it to claim the rewards for that particular smart contract. Whether the NFT is in your wallet or a smart contract, whatever entity tries to withdraw the earnings needs to hold the NFT in order to withdraw funds. Alternatively, you can assign a contract's revenue to an existing SFS NFT.

Registering a Contract

To register a smart contract for SFS, call the register method on the FeeSharing contract from the contract you wish to register.

This method takes one parameter: the address to which the SFS NFT should be minted. This can be an address that does not exist (i.e. an address that has never transacted before).

You can register a contract in the constructor or otherwise; however, SFS fees will only begin accruing once the contract is registered.

Example.sol
interface FeeSharing {
    function register(address) external returns(uint256);
}
 
contract Example {
    FeeSharing feeSharing = FeeSharing(0x1f8bBAd193E65daAb9f89A36ff01A92f55B65e6f);
 
    constructor() {
        // Registers the smart contract with FeeSharing
        // Mints the SFS NFT to the contract creator
        feeSharing.register(tx.origin);
    }
}

The register function receives (address _recipient) as a parameter. This is the address where the NFT will be minted to.

FeeSharing.sol
/**
 * @notice Mints ownership NFT that allows the owner to collect fees earned by the smart contract.
 * `msg.sender` is assumed to be a smart contract that earns fees.
 * Only smart contract itself can register a fee receipient.
 * @param _recipient recipient of the ownership NFT
 * @return tokenId of the ownership NFT that collects fees
 **/
function register(address _recipient) public onlyUnregistered returns (uint256 tokenId) {
    address smartContract = msg.sender;
 
    if (_recipient == address(0)) revert InvalidRecipient();
 
    tokenId = _tokenIdTracker.current();
    _mint(_recipient, tokenId);
    _tokenIdTracker.increment();
 
    emit Register(smartContract, _recipient, tokenId);
 
    feeRecipient[smartContract] = NftData({
        tokenId: tokenId,
        registered: true,
        balanceUpdatedBlock: block.number
    });
}

The SFS contract looks at the msg.sender in order to know what smart contract is being registered. This is why you need to call the register function from the contract you want to register. You can only do this once per contract. The register function returns the token ID of the SFS NFT that was minted.

Assignment

Assigning a contract to the SFS means linking your smart contract to an already existing SFS NFT. This also means that the revenue generated by that smart contract can be claimed with this NFT.

When you assign a contract there is no NFT being minted. This is useful when dApps have more than one smart contract then you can just register one contract to create the SFS NFT and assign the rest of the contracts to one SFS NFT. By doing this, you will have all the revenue from those contracts accumulated on one NFT.

Several contracts can be assigned to the same SFS NFT.

Every contract can only be assigned to one SFS NFT.

SFS fees will only begin accruing once the contract is registered.

Example.sol
interface FeeSharing {
    function assign(uint256) external returns(uint256);
}
 
contract Example {
    FeeSharing feeSharing = FeeSharing(0x1f8bBAd193E65daAb9f89A36ff01A92f55B65e6f);
 
    constructor() {
        // Registers the smart contract with FeeSharing
        // Assigns this contract's fee revenue to CSR NFT #1
        feeSharing.assign(1);
    }
}

assign function takes one parameter: the uint256 token ID of the SFS NFT to which fees should be assigned.

The same constraints apply:

You must call this method from the contract you wish to register.

The assign method returns the token ID of the SFS NFT that was passed to it.

FeeSharing.sol
/**
 * @notice Assigns smart contract to existing NFT. That NFT will collect fees generated by the smart contract.
 * Callable only by smart contract itself.
 * @param _tokenId tokenId which will collect fees
 * @return tokenId of the ownership NFT that collects fees
 **/
function assign(uint256 _tokenId) public onlyUnregistered returns (uint256) {
    address smartContract = msg.sender;
 
    if (!_exists(_tokenId)) revert InvalidTokenId();
 
    emit Assign(smartContract, _tokenId);
 
    feeRecipient[smartContract] = NftData({
        tokenId: _tokenId,
        registered: true,
        balanceUpdatedBlock: block.number
    });
 
    return _tokenId;
}

Withdrawing fees

You can check the amount of fees that have accrued for a SFS NFT by reading the value stored in the balances mapping for the NFT's token ID.

To withdraw SFS fees, call the withdraw function on the FeeSharing contract from the address that holds the SFS NFT. This method takes three parameters: the token ID of the SFS NFT, the address that should receive the fees, and the amount of fees to withdraw.

FeeSharing.sol
/**
 * @notice Withdraws earned fees to `_recipient` address. Only callable by NFT owner.
 * @param _tokenId token Id
 * @param _recipient recipient of fees
 * @param _amount amount of fees to withdraw
 * @return amount of fees withdrawn
 **/
function withdraw(uint256 _tokenId, address payable _recipient, uint256 _amount)
public onlyNftOwner(_tokenId) returns (uint256) {
    uint256 earnedFees = balances[_tokenId];
 
    if (earnedFees == 0 || _amount == 0) revert NothingToWithdraw();
    if (_amount > earnedFees) _amount = earnedFees;
 
    balances[_tokenId] = earnedFees - _amount;
 
    emit Withdraw(_tokenId, _recipient, _amount);
 
    Address.sendValue(_recipient, _amount);
 
    return _amount;
}

Factory contracts

You can implement the register and assign methods in factory contracts, such that child contracts are automatically registered for SFS when they are created.