Starknet v0.14.0 Pre-release Notes
This post is a technical deep dive into the suggested/expected changes in Starknet v0.14.0. A broader (and hopefully much shorter) SNIP about this version will be published soon, for the SN community to vote on.
Testnet – 7 July 2025
Mainnet – 28 July 2025
Stopping support for transactions v0, 1, 2 and non-0.8 txs v3
Meta-transaction syscall and usage
Renaming PENDING
to PRE_CONFIRMED
in RPC 0.9
Differences between PENDING
and PRE_CONFIRMED
block
Supporting queries on the PRE_CONFIRMED
block
Serving new statuses in getTransactionStatus
Gateway and feeder gateway API
Two distinct URLs for GW and FGW
New endpoint get_preconfirmed_block
Endpoint get_transaction_status
Deprecation of get_number_of_transactions_in_backlog
and get_oldest_transaction_age
Reverts for L1 handlers and resource limitation
Block internal contract calls to __execute__
Deprecation of old transactions
Calls to __execute__
with nonzero caller
**On-going development on Integration v0.14.0 **
Architecture changes
This Starknet version brings deep changes in the sequencing architecture, and is the first step towards decentralized sequencing. Most of the architecture changes are transparent to externals.
Up to now, the sequencer consisted of a single process in charge of building blocks. As there was only one entity in charge of block building, consensus was trivial. In v0.14.0, there will be four independent processes taking turns in building blocks and running Tendermint consensus on every block, with 1 block finality. Each sequencer maintains its own transaction mempool (see also the “Mempool” section below) and selects transactions from it when building a block. When a user broadcasts a transaction, a server routes the request to one of the four sequencers which then runs its local gateway logic.
For full nodes, the interface for syncing remains the same: after finalizing a block, the leader of the turn writes to a centralized DB all the relevant objects, and the feeder gateway serves requests as usual by using this DB as its state. Full nodes sync as usual by querying the feeder gateway. There are no changes to the gateway interface (i.e. no changes in how transactions are submitted).
Stopping support for transactions v0, 1, 2 and non-0.8 txs v3
Starknet v0.14.0 will only support transactions version 3 which are compatible with RPC 0.8 (i.e. that sign on bids over all three resources l1_gas
, l2_gas
, l1_data_gas
). This means that transactions of version 0,1,2 and transactions of version 3 which only sign on l1_gas
and l2_gas
will be rejected at the gateway. The motivation for the change is that transactions of version 0, 1, 2 are incompatible with a mempool architecture based on fee market (see SNIP 16 for more details).
This means that any code used to interact with Starknet needs to send transaction v3 compatible with RPC 0.8: this includes code of wallets, exchanges, bridges, oracles, or any DApp that sends transactions
Transactions v3 pay fees in STRK while transactions of version 0, 1, 2 pay fees in ETH. Any entity (end users, wallets or applications) that wishes to pay fees in a token other than STRK can integrate with an applicative paymaster service, e.g. AVNU.
For a guide on how to integrate with a paymaster using starknet.js see here.
In v0.13.5 we introduced a feature that allows accounts having certain class hashes to be compatible with transactions v3. Those accounts can be operated with transactions v3 without needing to upgrade. For more information, see also this github page.
Meta-transaction syscall and usage
This version introduces a new syscall called meta_tx_v0
. The purpose of this syscall is to “wrap” a transaction v0 inside a transaction v3. More precisely, this syscall receives a contract address, an entry point selector, the inputs to the entry point, and a signature over the hash of the v0 invoke transaction with those parameters (and zero max fee). Note that the entry point selector must be equal to __execute__
. It then performs a contract call to the contract address as if it was a fresh invoke transaction v0. This allows operating “from outside” with a transaction v3 an old account contract that has no __validate__
entry point.
The way that users should use the syscall is similar to how the deploy
syscall is used. There is a contract which wraps the meta_tx_v0
inside a function that can be called as usual by other contracts. This is analogous to how the Universal Deployer Contract wraps the deploy
syscall in the function deployContract
.
RPC 0.9
The main changes introduced in RPC 0.9 are:
- Addition of new transaction statuses as well as the removal of status
REJECTED
. - Renaming of block tag “pending” to “pre_confirmed” (as well as renaming the associated pending objects to “pre_confirmed”).
Below we explain in detail the previous points.
Transaction statuses
- RECEIVED
This status means that the transaction has been successfully submitted by the node that is polled.
Support for this status in v0.14.0 is planned only at the level of the nodes. The flow is:
- method
add_{invoke,deployAccount,declare}Transaction
is called, at a certain node A. - Node A submits the request to the gateway of one of the sequencers.
- If an error is received from the gateway, forward it to the user and the flow ends.
- Else, the transaction hash is received.
- Immediately querying node A for the status of the just-submitted transaction, results in
RECEIVED
. Note: querying a different node B for the status will result in an error ofTransaction hash not found
. There is no p2p among full nodes that updates on each other’s received transactions. (Note however that the sequencers do have p2p between their mempools.)
If you plan to rely on status RECEIVED
, you should initiate sticky HTTP sessions with your node provider.
- CANDIDATE
Whenever a block proposer tries to build a block, it takes chunks of transactions from the mempool and tries to run them. Each chunk is run upon the state after the previous chunk (if any). It does so until one of the block limits is reached or a maximum timeout is reached.
Before running the transactions in one chunk, the block proposer writes to the feeder gateway’s storage the transactions that they’re about to run, with status CANDIDATE
. Note that at this stage only the transaction hash is written to the feeder: any execution info is absent because the tx hasn’t been executed yet.
- PRE_CONFIRMED
Continuing the flow from point 2., when the block builder finishes running a chunk of transactions, it writes them to the feeder gateway storage with status PRE_CONFIRMED
along with their receipts.
- ACCEPTED_ON_L2
The meaning of this status is almost the same as in previous versions: a transaction is ACCEPTED_ON_L2
if it appears in a block finalized by the consensus.
- ACCEPTED_ON_L1
The meaning here is the same as in previous versions: a transaction is ACCEPTED_ON_L1
if the Starknet state on L1 has been moved to a block height which is greater than or equal to the height of the block containing the transaction.
There is no status REJECTED anymore.
Possible status transitions
We’ll explain below the more “interesting” arrows. (In what follows, “TTL” means time-to-live; this is a config attached to the various statuses in the corresponding DB entry, either in the feeder or the node).
- Arrows marked by A: this means that a transaction was picked up, and never made it to a block. After that, it returned to the mempool (meaning some nodes will return “ACCEPTED” and the others “not found”) or was dropped completely (if the TTL reached). This could happen because of:
- The transaction fails validation at block building time, until it gets thrown out of all mempools of the sequencers.
- Congestion or low bids: Transaction was picked up, didn’t make it to the block due to timeout, then got thrown out of the mempool because of congestion or reached TTL because of stale bids.
- Arrows B, going out of
PRE_CONFIRMED
(except for the one going toACCEPTED_TO_L2
) should be rare / very rare: they imply that consensus failed to finalize the block the proposal. - Arrow C, indicated a transaction was evicted from the mempool, most likely because its TTL was reached.
Renaming PENDING
to PRE_CONFIRMED
in RPC 0.9
The PRE_CONFIRMED
block of RPC 0.9 is the block of pre-confirmed transactions that the node knows about, by polling the feeder gateway.
Differences between PENDING
and PRE_CONFIRMED
block
In RPC 0.9, the pre_confirmed block is an object equal to the pending block of past Starknet versions except with a different finality status: transactions in it will have status PRE_CONFIRMED
and not ACCEPTED_ON_L2
.
What is the meaning of a different finality status? It’s possible that transactions in the pending block will not appear in the next published block. This happens only when consensus fails to finalize the proposal containing the pre-confirmed txs - this can happen e.g. if there are network connectivity issues, or multiple validators crash at the same time. This should be rare.
Supporting queries on the PRE_CONFIRMED
block
More precisely, to serve queries on “block_id = pre_confirmed”, nodes should implement the following flow:
- In an ongoing task, nodes poll a new endpoint
get_preconfirmed_block
from the feeder (more info on this below in the Feeder API section); - From the response, discard the
CANDIDATE
txs (caching them in another data structure), and keep only the pre-confirmed txs with receipts and state diff; - From it nodes build their internal representation of the pending object exactly as they did for Starknet versions <= v0.13.5.
Backwards compatibility
To serve queries on “block_id = pending” on RPC < 0.9, nodes will use the “latest” state and block.
Serving new statuses in getTransactionStatus
In order to serve the most up-to-date status for transactions that are currently CANDIDATE or PRE_CONFIRMED, nodes are expected to query the pre-confirmed block which is currently cached in the node.
Gateway and feeder gateway API
Two distinct URLs for GW and FGW
In v0.14.0 we plan to split the URLs of the feeder gateway and gateway. Until v0.13.5, they are at different routes on the same base URL {chain_name}.starknet.io
for Starknet chains (e.g. alpha-mainnet.starknet.io
). In v0.14.0, the URLs will be as follows:
- The feeder gateway will be at a new location:
feeder.{chain_name}.starknet.io/feeder_gateway
- The gateway will be at the same location:
{chain_name}.starknet.io/gateway
For example on mainnet:
[feeder.alpha-mainnet.starknet.io/feeder_gateway](https://feeder.alpha-mainnet.starknet.io/feeder_gateway)
[alpha-mainnet.starknet.io/gateway](http://alpha-mainnet.starknet.io/gateway)
Why split? In v0.14.0 there will be multiple sequencers and the “write” API doesn’t need to pass through the centralized component which manages the feeder. Splitting the URLs avoids a permanent redirect/forward for the gateway and is future-compatible.
While the chain is on v0.13.5, both URLs will point to their respective 0.13.5 locations so that users can upgrade their nodes to a 0.14.0-compatible version while the chain hasn’t upgraded yet.
Feeder API
New endpoint get_preconfirmed_block
{#new-endpoint-get_preconfirmed_block
}
This endpoint will receive a block number and return an object which is rather similar to the pending block object. More precisely, it has usual header properties
l1_da_mode
l1_data_gas_price
l1_gas_price
l2_gas_price
parent_block_hash
sequencer_address
starknet_version
status
timestamp
as well as three properties
transactions
transaction_receipts
transaction_state_diffs
The latter three properties have, as values, lists of the same length n >= 0. The objects of list transactions
(resp. transaction_receipts
, transaction_state_diffs
) are feeder objects of type Transaction
(resp. Option<Receipt>
, Option<StateDiff>
), in pseudo-Rust.
Moreover:
- Up until some position m <= n, the objects
transaction[i]
,transaction_receipts[i]
andtransaction_state_diffs[i]
are all non-empty objects that refer to the same transaction (i <= m). - In range [m+1,n-1] (possibly empty),
transaction_receipts[i] = null
andtransaction_state_diffs[i] = null
.
This means that in range [0,m] all the transactions have status PRE_CONFIRMED
(hence have a receipt and a state diff), and in range [m+1,n-1] all the transactions have status CANDIDATE
.
Rationale
The motivations for introducing this endpoint are:
- Make it possible for nodes to support, in RPC 0.9, a monotonically increasing
PRE_CONFIRMED
block at given height with fast latency, as was the case for the pending block. This is done by considering only the pre-confirmed transactions, and by squashing the state diffs of the txs (as explained in the relevant section in the above “RPC 0.9”). By doing this, in RPC 0.9 nodes can serve queries with “block_id = pre_confirmed” using the data and the state after the pre-confirmed block. - Serve a very fast transaction status of
CANDIDATE
.
Endpoint get_transaction_status
In v0.14.0, the finality status property of the get_transaction_status
response can only be ACCEPTED_ON_L2
or ACCEPTED_ON_L1
. Calling get_transaction_status
on a transaction that is still in the mempool and hasn’t entered a block yet will return an error Transaction hash not found
. For a wider discussion on how the nodes will support status RECEIVED
and other statuses, see section RPC 0.9 below.
Deprecation of get_number_of_transactions_in_backlog
and get_oldest_transaction_age
The endpoints get_oldest_transaction_age
and get_number_of_transactions_in_backlog
will be deprecated in v0.14.0.
Reverts for L1 handlers and resource limitation
In v0.14.0, L1 handlers that fail execution will appear in a block as REVERTED
. Previously, such L1 handlers would not appear in a block and would only be written in the feeder gateway’s DB as REJECTED
. This should simplify the tracking of messages L1 → L2, in particular the implementation of getMessageStatus
.
Moreover, the execution resource of L1 handlers will now be constrained: namely the three resources l1_gas, l2_gas and l1_data_gas will be bounded:
- The bound on l2_gas will be the same as the bound for
__validate__
entry point, i.e. 10^8 l2_gas. - The bound on l1_gas and l1_data_gas will be set to allow for sending one L2 → L1 message, i.e. l1_gas <= 410^4 and l1_data_gas <= 210^4.
Note: from an analysis of past activity, this bound should not break any existing flow.
EIP-1559
This version introduces a fee market on the l2_gas resource, following Ethereum’s EIP 1559. Each block will have three integers associated with it: l1_gas_price, l2_gas_price, l1_data_gas_price, which are the base fee (in FRI) of the corresponding resource. A transaction defines a bid on each of the three resources in the form of a pair (max_amount
, price_per_unit
) and a tip
. A necessary condition for a transaction to enter a block is that, for each resource, the bid price_per_unit
is greater than or equal to the corresponding base fee. The tip is a further addition to the price_per_unit
for l2_gas: the sequencer will receive (base_fee + tip) * l2_gas_used
. Two remarks are in order:
- In this version we won’t implement burning of the base fee.
- There will be a minimum base price, denominated in FRI, per unit of
l2_gas
. Concretely, when there is no congestion in the network, the base price per unit of l2_gas will be equal to this minimum.
Base fee calculation
- l1_gas and l1_data_gas: the base fee for these resources is determined by sampling the prices on Ethereum for the corresponding resource, over a fixed-length window of time.
For l1_data_gas, the price will actually be equal to the sampling price divided by a scaling factor, as is currently the case since v0.13.3. The division accounts (approximately) for the average rate of compression that we achieve when posting DA to Ethereum.
- l2_gas: the base fee of block is determined by congestion, i.e. the base fee for block n (base(n)) is given by
base(n) =((1 + gas_used(n-1) - TARGETTARGETC) base(n-1), MIN_PRICE),
where gas_used(k) denotes the total l2 gas used in a block, TARGET is a constant equal to half of the block capacity, and 0 <C < 1 is a constant that bounds the maximum percentage change. If the total gas used in a block is equal (resp. larger, smaller) to TARGET, then the base fee of the next block won’t change (resp. increase, decrease). The maximum percentage change between two consecutive blocks is C.
Mempool
Up to (and including) v0.13.5 of Starknet, transactions were received and ordered by the sequencer in a FIFO fashion. In v0.14.0, the sequencer instead maintains a mempool: this decouples the order of arrival from the transaction ordering in the blocks. The block ordering is decided by each block proposer according to their local policy. A transaction with a higher tip will likely be placed earlier in the block. The mempool architecture has some implications on how transactions are accepted and processed, which we describe below.
Nonces
Transactions with a nonce greater than current sender nonce + X will be rejected from the mempool (the “current sender nonce” refers to the sender nonce in the latest finalized state at the time of receival of the tx). The final config is still in discussion, but it should be 50 <= X <= 100.
Declare transactions whose nonce is invalid against the current state will be rejected (i.e. no declares with future nonces).
Fee escalation
A transaction (say from account A and with nonce n) is replaced if a more profitable transaction, i.e. with a higher tip, from the same account A and nonce n is received. To successfully replace a transaction, tip
and max_l2_gas_price
need to be bumped by at least 10%. Note that there are unlikely edge cases in which the transaction is not replaced, e.g. the lower paying transaction enters a block while the higher paying transaction is processed in the gateway.
Eviction policy
The mempool keeps track of the time in which a tx enters and it does a periodical clean-up of transactions that cannot be included in a block and which have been in the mempool for too long, i.e. have exceeded their time-to-live (TTL). The TTL is configured to be 5 mins.
Declare transaction behavior
When DECLARE
transactions are sent to the mempool, they first go through the usual validations (including nonce validation and fee escalation) and then are inserted into a “waiting room” before they are inserted into the pool. In particular, maturing transactions cannot enter a block.
While in the waiting room, no escalation is possible: i.e. a transaction with the same nonce from the same account will be rejected. This impedes a DoS where a DECLARE
transaction is sent, compiled and then removed by sending a transaction with the same nonce while it matures. After the transaction has matured and entered the mempool, escalation is possible again (maturation time << time to live).
Multiblock OS
Up until v0.14.0, after building a block, the sequencer would run the Starknet OS “on it” to prove that the Starknet state is advanced correctly. In v0.14.0 block times are substantially shorter (see the section “Configuration” below for the value) which lead to much smaller blocks. Every run of the OS on a block also incurs a fixed cost of one recursion, which is practically independent of the size of the block. If one produces x15 many blocks, one also multiplies the fixed cost by the same amount, which is quite wasteful.
To mitigate this increase in fixed cost, the OS of v0.14.0 has been modified to receive as inputs multiple blocks: this solves the issue of running wasteful recursions and also amortizes certain non-negligible costs over a larger number of blocks. For example, if both block 10 and 11 need to load the code of the contract at address A, the loading and hashing of the code (in the OS) is not duplicated, and instead is done only once. The same is true for reads from storage and updates to the Merkle Patricia tries.
Configurations
- Minimum l2 gas price. This parameter is currently in research, and will be set so that Starknet’s operating costs (i.e. on-chain and off-chain costs) will roughly break-even with the collected fees, on average.
- Block time and max
l2_gas
per transaction. The block time is dependent on the max allowed transaction size, in l2_gas. We’re currently debating between keeping the same max transaction size (10M Cairo steps or, equivalently, 1B L2 gas), which will result in a block time of 6 seconds, and reducing the max transaction size to 6M and have shorter block times of 4 seconds. Of course, reducing transaction max size to 6M steps comes with its downside (we wouldn’t like to limit computational power, and there are sporadically transactions in this range - and in some cases getting rid of them will require a significant refactor). Notice that in most cases the pre_confirmed finality status is sufficient, which is expected to be subsecond on average. - Max
l2_gas
per block and targetl2_gas
per block. Max block size will be 2B L2 gas, equivalent to 20M steps. This is done to keep blocks small enough for S-Two to prove efficiently (and in fact will be changed already in 0.13.6).
Paymaster API
While not directly linked with v0.14.0, SNIP-29 introduces a standardization of the interface between an applicative paymaster and a user (e.g. a wallet or Dapp). Since v0.14.0 removes support for tx versions 0,1,2 (that pay fees in ETH) leaving only tx version ≥ 3 (that pay fees in STRK), the demand for paymasters will increase, hence their importance. SNIP-29 proposes a JSON-RPC spec for communication. The spec is supported by starknet.js in versions >= 7.4.0.
Other
Block internal contract calls to __execute__
In v0.14.0, transactions with internal calls to an entry point called __execute__
(i.e. from a non-zero caller), will be reverted.
Breaking changes
Deprecation of old transactions
In v0.14.0, only v3 transactions that sign on all three resource bounds (namely
l1_gas
, l1_data_gas
and l2_gas
) will be accepted by the sequencer
In other words, only transactions that are compliant with JSON-RPC ≥ v0.8.0 will be supported. This only refers to broadcasting transactions: nodes plan to support the v0.7.0 spec for the read API and trace API.
Fee market
During periods of congestion, transactions may not be included in a block and/or dropped from the mempool (or both).
L1 handler resource bounds
Starting from v0.14.0, messages from L1 to L2 will be resource-bounded, namely the run of to-be-invoked function on L2 will be bounded in l2_gas and l1_gas, roughly like the __validate__
entry point of accounts. (Note: from an analysis of past activity, this bound should not break any existing flow.)
Feeder API
Endpoint get_oldest_transaction_age
and get_number_of_transactions_in_backlog
will be deprecated.
Calls to __execute__
with nonzero caller {#calls-to-__execute__
-with-nonzero-caller}
Transactions calling __execute__
during a transaction will be reverted. We are not aware of any use case that would break.
Action items
Full nodes
Full nodes are expected to release a new version that supports the two different URLs of the gateway and the feeder gateway, and with the updated dependencies on the blockifier and compiler. They should add support for RPC 0.9.
Nodes may drop support for all RPC versions before v0.7.0. Note that RPC v0.7.0 will still be supported, but only the “read” API: using it to submit transactions will lead to an error.
RPC providers
Once the corresponding node versions are released, RPC providers should upgrade to avoid falling out of sync as well as support RPC 0.9.
SDKs
SDKs are expected to release a new version with support for fee market and possibly an integration with the Paymaster API.
Wallets
New resource bounds in v3 transactions
As of v0.14.0, only v3 transactions that sign on all three resource bounds (namely l1_gas, l1_data_gas and l2_gas) will be accepted by the sequencer
Wallets should migrate from the currently used transaction structure pre-0.8 v3 (no l1_data_gas bounds specified) to the new one of RPC 0.8. Ideally, wallets should integrate with RPC 0.9 to preserve a low latency UX.
Wallets should make sure to support their v0-bound accounts by providing a backend that calls the “universal” meta-tx contract (see the sections “Meta transaction syscall and usage” and the relevant section in “On-going development on Integration v0.14.0” below).
Indexers and explorers
Indexers and explorers that rely on get_number_of_transactions_in_backlog
and get_oldest_transaction_age
should adapt their backends and drop the flows that use them.
On-going development on Integration v0.14.0 {#on-going-development-on-integration-v0.14.0}
Status get_preconfirmed_block
{#status-get_preconfirmed_block
}
The new feeder endpoint discussed in the section Feeder API
is currently in development and expected to be ready <= 15/6/24.