Overview

Existing L2s like Optimism and Arbitrum keep sequencer fees for themselves. Blast redirects sequencer fees to the dapps that induced them, allowing smart contract developers to have an additional source of revenue.

Contracts have two options for their Gas Mode:

  • Void (DEFAULT): base + priority fees go to the sequencer operator
  • Claimable: base + priority fees spent on this contract can be claimed by the contract, net of L1 fees

Smart contracts must interact with the Blast contract located at 0x4300000000000000000000000000000000000002 to change their Gas Mode.

Setting Gas Mode to Claimable

The entity that is allowed to claim the gas fees spent on a contract is known as its “governor”. By default, the governor of a smart contract is the contract itself. The following example sets the Gas Mode to claimable and exposes a function to allow anybody to claim this contract’s gas fees.

interface IBlast {
  // Note: the full interface for IBlast can be found below
  function configureClaimableGas() external;
  function claimAllGas(address contractAddress, address recipient) external returns (uint256);
}

contract MyContract {
  IBlast public constant BLAST = IBlast(0x4300000000000000000000000000000000000002);

  constructor() {
    // This sets the Gas Mode for MyContract to claimable
    BLAST.configureClaimableGas();
  }

  // Note: in production, you would likely want to restrict access to this
  function claimMyContractsGas() external {
    BLAST.claimAllGas(address(this), msg.sender);
  }
}

Alternatively, you can specify an external governor. Once the governor is changed away from the contract itself, only the governor can set the gas mode, claim gas fees, and reconfigure the governor. In the following example, the governor could be an EOA belonging to the contract creator or another smart contract.

interface IBlast {
  // Note: the full interface for IBlast can be found below
  function configureClaimableGas() external;
  function configureGovernor(address governor) external;
}

contract MyContract {
  IBlast public constant BLAST = IBlast(0x4300000000000000000000000000000000000002);

  constructor(address governor) {
    BLAST.configureClaimableGas();
    // This sets the contract's governor. This call must come last because after
    // the governor is set, this contract will lose the ability to configure itself.
    BLAST.configureGovernor(governor);
  }
}

The governor now has the ability to claim the gas fees for this contract by sending a claimAllGas transaction to the Blast contract.

BLAST.claimAllGas(myContractAddress, recipient);

Claiming Gas Fees

If you claim the gas fees for a contract immediately after the fees are earned, then 50% of the fees will be sent to the claim recipient and 50% will be sent to the sequencer operator. This is known as the “claim rate”, and it starts at 50% and increases to 100% over time. For mainnet, the parameters will be set to a 50% initial claim rate, 30 day maturity period, and 100% final claim rate.

You can think of each transaction’s gas fees as having their own claim rate that evolves independently. As an example, suppose you have 1 ETH in fees from January 1st and 1 ETH in fees from January 15th. When you claim 1 month later on February 1st, you’ll be able to claim 100% of your January 1st fees and 75% of your January 15th fees. Only half a month has passed since your January 15th fees, so the claim rate has increased halfway to its max potential, from 50% to 75%. Your average claim rate for the full 2 ETH of fees will be the weighted average of these two claim rates: 87.5%.

Thinking about the effective claim rate can get very confusing when you have many transactions over time, so for convenience, there are a handful of utility methods to make the claiming process easy.

claimAllGas

function claimAllGas(address contractAddress, address recipient) external returns (uint256);

To claim all of your contract’s gas fees, regardless of your resulting claim rate, you can call claimAllGas. Your resulting claim rate may be anywhere from 50% to 100% depending on how long it has been since the fees were earned.

claimMaxGas

function claimMaxGas(address contractAddress, address recipient) external returns (uint256);

If you’d like to maximize the amount of gas fees claimed and you’re okay waiting for the fees to mature, then you can use claimMaxGas to guarantee a 100% claim rate. By guaranteeing the claim rate, you may not be able to claim all of your gas fees though. Any remaining fees after calling this function will remain in the Blast Gas contract for you to claim later.

claimGasAtMinClaimRate

function claimGasAtMinClaimRate(
  address contractAddress,
  address recipient,
  uint256 minClaimRateBips
) external returns (uint256);

This helper allows you to claim the maximum amount of gas fees while guaranteeing a specific claim rate. If you’re comfortable with an 80% claim rate, that translates to 8000 bips, so you would call claimGasAtMinClaimRate(contractAddress, recipient, 8000). Calling this function with a 100% min claim rate is the same as calling claimMaxGas.

How Gas Fees Are Allocated

The gas fees spent on Blast transactions can be broken up into three components: L1 data availability fees, L2 base fees, and L2 priority fees. The L1 data availability fee ensures that other nodes can trustlessly recover the state of the Blast L2, and it gets burned on the Ethereum L1. The L2 base and priority fees (“sequencer fees”) however either go to the sequencer operator or to the smart contracts that caused the gas to be spent.

Blast allocates sequencer fees to contracts based on the amount of gas that contract consumed in the transaction. This means that contracts that make nested calls to other smart contracts will get a proportional amount of the total gas fees spent on the transation, not the total amount.

In cases where delegatecall is used, gas fees are allocated to the contract that initiated the delegatecall and not the target contract where the implementation logic lives.

Example: Nested Calls

To further demonstrate how gas fees are allocated across nested calls, consider the following example:

  • Contract A (30,000 gas)
    • Contract B (50,000 gas)
    • Contract C (20,000 gas)
      • Contract A (30,000 gas)
      • Contract D (40,000 gas)

Suppose that only contracts A and D have set their Gas Mode to claimable. In this sample transaction, contract A would be allocated 30,000 + 30,000 = 60,000 gas worth of fees, and contract D would be allocated 40,000 gas worth of fees. The gas spent on contracts B and C, 50,000 + 20,000 = 70,000, would be allocated to the sequencer operator.

Example: Proxy / Delegatecall

Now, suppose that contract D is a proxy contract that forwards calls via delegatecall to an implementation contract, contract E.

  • Contract A (30,000 gas)
    • Contract B (50,000 gas)
    • Contract C (20,000 gas)
      • Contract A (30,000 gas)
      • (Proxy) Contract D (1,000 gas)
        • delegatecall Contract E (39,000 gas)

In this modified example, contract D will still be allocated 1,000 + 39,000 = 40,000 gas worth of fees because the execution of contract E’s code happens within the context of contract D.

Calculating Claimable ETH

To determine the maximum amount of ETH that contract A would be able to claim from the examples above, you need to multiply the gas amount by the gas price. If the gas price for this transaction was 15 gwei (10 gwei base + 5 gwei priority), then the ETH amount contract A would be able to claim a maxiumum of 60,000 gas * 15 gwei/gas = 0.0009 ETH.

How Gas Fees and Claim Rates Are Tracked

This section contains low level details on how the Blast Gas contract works. You don’t need to read or understand this section to configure your contracts or claim your gas, but if you’re interested in a optimizing your integration, or just a bit curious, read on.

The Blast Gas contract keeps track of the integral of your contract’s unclaimed fees over time. If you had 1 ETH of unclaimed fees for 1 day, the resulting integral would be 1 ether-day. If you instead had 2 ETH of unclaimed fees for 1 week, then the integral would be 14 ether-days.

To make this integration process clear, let’s build on top of this second scenario. If you suddenly accumulated 1 additional ETH of fees, your contract’s unclaimed fee balance would be 3 ETH, but the integral would still be 14 ether-days. If you waited 1 more day, then the integral would be 17 ether-days. The Blast Gas contract uses this integral to determine the effective maturity of your unclaimed fee balance.

Blast Gas Claim Parameters

There are five parameters that determine how your contract’s accumulated ether-days relates to your claim rate:

  • zeroClaimRate: your claim rate immediately after earning gas fees
  • baseGasSeconds: the number of seconds your gas fees need to mature to receive the baseClaimRate
  • ceilGasSeconds: the number of seconds your gas fees need to mature to receive the ceilClaimRate

Represented visually, this looks like:

Abstract gas parameters

The intended mainnet parameters look like this:

Abstract gas parameters

Custom Gas Fee Claims

The following function is the lowest level claim helper. It allows you to specify exactly how much ETH you’d like to claim and how many ether-seconds you’d like to use to contribute to your claim rate.

function claimGas(
  address contractAddress,
  address recipient,
  uint256 etherToClaim,
  uint256 etherSecondsToConsume
) external returns (uint256);

Claiming 1 ETH at the ceilClaimRate requires ceilGasSeconds ether-seconds. Providing fewer ceilGasSeconds than this would result in an effective claim rate less than the ceilClaimRate, based on the zeroClaimRate and baseClaimRate parameters.

IBlast Interface

In the interface below, when you see a function like configureX with corresponding configureXOnBehalf version, the difference is that configureX configures the caller and configureXOnBehalf configures the contract corresponding to the contractAddress parameter.

enum YieldMode {
    AUTOMATIC,
    VOID,
    CLAIMABLE
}

enum GasMode {
    VOID,
    CLAIMABLE
}

interface IBlast {
    // configure
    function configureContract(address contractAddress, YieldMode _yield, GasMode gasMode, address governor) external;
    function configure(YieldMode _yield, GasMode gasMode, address governor) external;

    // base configuration options
    function configureClaimableYield() external;
    function configureClaimableYieldOnBehalf(address contractAddress) external;
    function configureAutomaticYield() external;
    function configureAutomaticYieldOnBehalf(address contractAddress) external;
    function configureVoidYield() external;
    function configureVoidYieldOnBehalf(address contractAddress) external;
    function configureClaimableGas() external;
    function configureClaimableGasOnBehalf(address contractAddress) external;
    function configureVoidGas() external;
    function configureVoidGasOnBehalf(address contractAddress) external;
    function configureGovernor(address _governor) external;
    function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external;

    // claim yield
    function claimYield(address contractAddress, address recipientOfYield, uint256 amount) external returns (uint256);
    function claimAllYield(address contractAddress, address recipientOfYield) external returns (uint256);

    // claim gas
    function claimAllGas(address contractAddress, address recipientOfGas) external returns (uint256);
    function claimGasAtMinClaimRate(address contractAddress, address recipientOfGas, uint256 minClaimRateBips) external returns (uint256);
    function claimMaxGas(address contractAddress, address recipientOfGas) external returns (uint256);
    function claimGas(address contractAddress, address recipientOfGas, uint256 gasToClaim, uint256 gasSecondsToConsume) external returns (uint256);

    // read functions
    function readClaimableYield(address contractAddress) external view returns (uint256);
    function readYieldConfiguration(address contractAddress) external view returns (uint8);
    function readGasParams(address contractAddress) external view returns (uint256 etherSeconds, uint256 etherBalance, uint256 lastUpdated, GasMode);
}