Gas and transaction fees

This section describes fees that are paid on L2 starting in Starknet 0.13.0. For information about messaging fees that are paid on L1, see L1 → L2 message fees.

Overall transaction fee

This section shows the formula for determining a transaction’s fee. The following sections describe how this formula was derived.

The following formula describes the overall fee, \(F\), for a transaction:

\[\begin{align} F = \text{gas_price}\cdot&\Bigg(\max_k v_k w_k + \\ & + \; \text{da_calldata_cost}\left(2(n-1)+2(m-1) + \ell + 3t + \sum\limits_{i=1}^t q_i\right)\\ & - \; \text{contract_update_discount}\cdot (n-1) - 240 \\ & + \; \text{message_calldata_cost}\cdot\left(3t + \sum\limits_{i=1}^t q_i\right) \\ & + \; \text{storage_write_cost}\cdot t\Bigg) \end{align}\]

where:

  • \(v\) is a vector that represents resource usage, where each of its entries, \(v_k\), corresponds to different resource types: Cairo steps and number of applications of each builtin.

    For more information see Calculation of computation costs.

  • \(n\) is the number of unique contracts updated, which also includes changes to classes of existing contracts and contract deployments, even if the storage of the newly deployed contract is untouched. In other words, \(n\ge\ell\). Notice that \(n\ge 1\) always holds, because the fee token contract is always updated, which does not incur any fee.

  • \(m\) is the number of values updated, not counting multiple updates for the same key. Notice that \(m\ge 1\) always holds, because the sequencer’s balance is always updated, which does not incur any fee.

  • \(t\) is the number of L2→L1 messages sent, where the corresponding payload sizes are denoted by \(q_1,...,q_t\).

  • \(\ell\) is the number of contracts whose class was changed, which happens on contract deployment and when applying the replace_class syscall.

  • \(w\) is the CairoResourceFeeWeights vector.

  • \(\text{da_calldata_cost}\) is 551 gas per 32-byte word. This cost is derived as follows:

    • 512 gas per 32-byte word for calldata.

    • ~100 gas for onchain hashing that happens for every sent word.

    • a 10% discount, because the sequencer does not incur additional costs for repeated updates to the same storage slot within a single block.

  • \(\text{message_calldata_cost}\) is 512 gas per 32-byte word. For more details, see Onchain data: L2→L1 messages.

  • \(240\) is the gas discount for updating the sender’s balance, for the derivation of this number see Onchain data: Storage updates.

  • \(\text{contract_update_discount}\) is 312 gas, for the derivation of this discount see Onchain data: Storage updates.

  • \(\text{storage_write_cost}\) is 20,000 gas, the cost of allocating a new storage cell on Ethereum.

When is the fee charged?

The fee is charged atomically with the transaction execution on L2. The Starknet OS injects a transfer of the fee-related ERC-20, with an amount equal to the fee paid, the sender equal to the transaction submitter, and the sequencer as a receiver.

Fee limitations

Users can specify the maximum fee that they are willing to pay for a transaction.

v3 Transactions

v3 transactions use max_amount and max_price_per_unit to set the maximum amount of a resource that can be used on L1, and the maximum price per unit of that resource that the user is willing to pay. These fields replace the max_fee field.

At the time of writing, the only resource involved is the marginal verification cost of the transaction, measured in units of L1 gas. An additional resource,L2_GAS will be added in the future.

For transaction versions before v3 transactions, the max_fee field specifies the maximum fee.

The only limitation on the sequencer, which is enforced by the Starknet OS, is that the actual fee charged is bounded by max_fee, but for now, StarkWare’s sequencer only charges the fee required to cover the proof cost, which is potentially less than the maximum fee.

Currently, the sequencer only takes into account L1 costs involving proof submission. The following two components affect the L1 footprint of a transaction:

Fee units

Each transaction is associated with an estimate of the amount of gas used. Combining this estimate with the price of gas yields the estimated fee.

For transactions prior to v3, the fee is denominated in WEI. For transactions v3 and later, the fee is denominated in STRK.

Fee calculation

Computation without builtins

Let’s analyze the correct metric for measuring transaction complexity. For simplicity, we will ignore Cairo’s builtins, and address them later.

A Cairo program execution yields an execution trace. When proving a Starknet block, we aggregate all the transactions appearing in that block to the execution trace.

Starknet’s prover generates proofs for execution traces, up to some maximal length \(L\), derived from the specs of the proving machine and the desired proof latency.

Tracking the execution trace length associated with each transaction is simple. Each assertion over field elements, such as verifying addition/multiplication over the field, requires the same, constant number of trace cells, which is where our "no-builtins" assumption kicks in: Pedersen occupies more trace cells than addition. Therefore, in a world without builtins, the fee of the transaction \(tx\) is correlated with \(\text{TraceCells}[tx]/L\).

Computation with builtins

In the Cairo execution trace each builtin has its own slot, which is important to consider when determining the fee.

For example, consider that the prover can process a trace with the following limits:

up to 500,000,000 Cairo Steps up to 20,000,000 Pedersen hashes up to 4,000,000 signature verifications up to 10,000,000 range checks

The proof is closed and sent to L1 when any of these slots is filled.

Suppose that a transaction uses 10,000 Cairo steps and 500 Pedersen hashes. At most 40,000 such transactions can fit into the hypothetical trace (20,000,000/500). Therefore, its gas price correlates with 1/40,000 of the cost of submitting proof.

Notice that this estimate ignores the number of Cairo steps, as it is not the limiting factor, since 500,000,000/10,000 > 20,000,000/500.

With this example in mind, it is possible to formulate the exact fee associated with L2 computation.

The allocation of resources among builtin operations must be predetermined; it is not possible to decide, post-execution, to include only 20,000,001 Pedersen hashes without additional components.

This safeguards fairness and prevents manipulation, ensuring integrity in proof generation and fee determination.

Calculation of computation costs

For each transaction, the sequencer calculates a vector, CairoResourceUsage, that contains the following:

  • The number of Cairo steps.

  • The number of applications of each Cairo builtin. For example, five range checks and two Pedersen hashes.

The sequencer crosses this information with the CairoResourceFeeWeights vector. For each resource type, either a Cairo step or a specific builtin application, CairoResourceFeeWeights has an entry that specifies the relative gas cost of that component in the proof.

Going back to the above example, if the cost of submitting a proof with 20,000,000 Pedersen hashes is roughly 5m gas, then the weight of the Pedersen builtin is 0.25 gas per application (5,000,000/20,000,000). The sequencer has a predefined weights vector, in accordance with the proof parameters.

The sequencer charges only according to the limiting factor. Therefore the fee is correlated with:

\[\max_k[\text{CairoResourceUsage}_k \cdot \text{CairoResourceFeeWeights}_k]\]

where \(k\) enumerates the Cairo resource components, that is the number of Cairo steps and builtins used.

Table 1. Amount of gas used per Cairo step or per each time a Cairo builtin is applied
Step or builtin Gas cost

Cairo step

0.005 gas/step

Pedersen

0.16 gas/application

Poseidon

0.16 gas/application

Range check

0.08 gas/application

ECDSA

10.24 gas/application

Keccak

10.24 gas/application

Bitwise

0.32 gas/application

EC_OP

5.12 gas/application

Onchain data components

The onchain data associated with a transaction is composed of three parts

  • Storage updates

  • L2→L1 messages

  • Deployed contracts

Onchain data: Storage updates

Whenever a transaction updates some value in the storage of some contract, the following data is sent to L1:

  • two 32-byte words per contract

  • two 32-byte words for every updated storage value

For information on the exact data and its construction, see Data availability.

Only the most recent value reaches L1. So the transaction’s fee only depends on the number of unique storage updates. If the same storage cell is updated multiple times within the transaction, the fee remains that of a single update.

The following formula describes the storage update fee for a transaction:

\[\underbrace{\textit{gas_price}\left(\text{da_calldata_cost} \cdot 2(n-1) - \text{contract_update_discount}\cdot (n-1)\right)}_{\text{contract addresses + new nonce and number of storage updates }} \\ + \\ \underbrace{\textit{gas_price} \cdot \left(\text{da_calldata_cost}(2(m-1))-240\right)}_{\text{storage updates}}\]

where:

  • \(n\) is the number of unique contracts updated, which also includes changes to classes of existing contracts and contract deployments, even if the storage of the newly deployed contract is untouched. In other words, \(n\ge\ell\). Notice that \(n\ge 1\) always holds, because the fee token contract is always updated at the end of each transaction, in order to update the sequencer’s and the sender’s balances. The fee token contract update is not taken into account when computing the fee.

  • \(m\) is the number of values updated, not counting multiple updates for the same key. Notice that \(m\ge 1\) always holds, because the sequencer’s balance is updated at the end of each transaction. The sequencer’s balance update is not taken into account when computing the fee.

  • \(\text{contract_update_discount}\) is 312 gas, which is discounted for every updated contract. This discount is a result of the fact that out of the \(2n\) words caused by updating contracts, \(n\) words are short, including at most 6 non-zero bytes:

    • three bytes for the nonce

    • two bytes for the number of storage updates

    • one byte for the class information flag

    Taking into account that zero bytes only cost 4 gas, the cost difference between a full 32-byte word, which does not contain zeros, and a word with only six non-zero bytes is \(32\cdot16-(6\cdot16+26\cdot4)=312\).

  • \(240\) is the gas discount for updating the sender’s balance, and is derived by assuming the balance requires at most 12 non-zero bytes, which is enough for 1.2B ETH or STRK, resulting in the following discount: \(512-(20\cdot4+12\cdot16)=240\).

Improvements to the above pessimistic estimation might be gradually implemented in future versions of Starknet.

For example, if different transactions within the same block update the same storage cell, there is no need to charge for both transactions, because only the last value reaches L1. In the future, Starknet might include a refund mechanism for such cases.

Onchain data: L2→L1 messages

When a transaction that raises the send_message_to_l1 syscall is included in a state update, the following data reaches L1:

  • L2 sender address

  • L1 destination address

  • Payload size

  • Payload (list of field elements)

Consequently, the gas cost associated with a single L2→L1 message is:

\[\text{storage_write_cost} + \left(\text{da_calldata_cost} + \text{message_calldata_cost}\right)\cdot\left(3+\text{payload_size}\right)\]

Where \(\text{da_calldata_cost}\) is 551 gas, and \(\text{message_calldata_cost}\) is 512 gas. Messages appear twice in the fee formula, and pay both in \(\text{da_calldata_cost}\) and \(\text{message_calldata_cost}\), because they are sent to Ethereum twice: once to the STARK verifier contract, where the additional hashing cost is incurred, and once when it is sent to the Starknet Core Contract as part of the state update transaction.

Onchain data: Deployed contracts

When a transaction that raises the deploy syscall is included in a state update, the following data reaches L1:

  • contract address

  • number of storage updates and the new nonce

  • class hash

The first two elements are counted in the number of unique modified contracts, denoted by \(n\) throughout this page. So the only additional word comes from publishing the class hash, which adds 551 gas. For more information, see \(\text{da_calldata_cost}\) in the final formula.