Submit forced transactions
🚧 Forced transactions are not yet live. This documentation describes the intended design.
Submit a forced transaction
To submit a forced transaction, you first need to query the latest finalized state and rolling hashes from the L1.
The following example uses Ethers v6 and is provided for illustration only.
You must supply the correct Linea RPC endpoint, contract address, and ABI for LineaRollup.
See the Linea contract addresses and ABIs in the monorepo for production integration.
// Pseudocode example (ethers v6)
import {
AbiCoder,
keccak256,
Contract,
BrowserProvider
} from "ethers";
// assume provider + contract already initialized
// const provider = new BrowserProvider(window.ethereum);
// const lineaRollup = new Contract(address, abi, provider);
// 1. Get the latest FinalizedStateUpdated event
const events = await lineaRollup.queryFilter(
lineaRollup.filters.FinalizedStateUpdated()
);
if (events.length === 0) {
throw new Error("No FinalizedStateUpdated events found");
}
const latestEvent = events[events.length - 1];
// 2. Extract values from the event
const {
blockNumber, // L2 block number (indexed)
timestamp, // Finalization timestamp
messageNumber, // L1->L2 message number
forcedTransactionNumber // Last finalized forced tx number
} = latestEvent.args;
// 3. Query rolling hashes from contract state
const messageRollingHash = await lineaRollup.rollingHashes(messageNumber);
const forcedTransactionRollingHash =
await lineaRollup.forcedTransactionRollingHashes(
forcedTransactionNumber
);
// 4. Construct the LastFinalizedState object
const lastFinalizedState = {
timestamp,
messageNumber,
messageRollingHash,
forcedTransactionNumber,
forcedTransactionRollingHash
};
// 5. OPTIONAL: Verify it matches the contract's stored hash
const abiCoder = AbiCoder.defaultAbiCoder();
const encoded = abiCoder.encode(
["uint256", "bytes32", "uint256", "bytes32", "uint256"],
[
messageNumber,
messageRollingHash,
forcedTransactionNumber,
forcedTransactionRollingHash,
timestamp
]
);
const expectedHash = keccak256(encoded);
const storedHash = await lineaRollup.currentFinalizedState();
if (expectedHash !== storedHash) {
throw new Error("State mismatch - data may be stale");
}
Once you have the correct values, you can construct a forced transaction submission with the following fields: The last finalized L2 state on L1:
struct LastFinalizedState {
uint256 timestamp; // Last finalized L2 block timestamp
uint256 messageNumber; // L1→L2 message number at finalization
bytes32 messageRollingHash; // Rolling hash of L1→L2 messages
uint256 forcedTransactionNumber; // Last finalized forced tx number
bytes32 forcedTransactionRollingHash; // Rolling hash of forced transactions
}
A signed EIP-1559 transaction destined for the L2 in field form:
struct Eip1559Transaction {
uint256 nonce;
uint256 maxPriorityFeePerGas;
uint256 maxFeePerGas;
uint256 gasLimit;
address to;
uint256 value;
bytes input;
AccessList[] accessList;
uint8 yParity;
uint256 r;
uint256 s;
}
When submitting the transaction, the gateway will validate that:
- The last finalized state matches
- The transaction details are within limits
- The appropriate fee has been paid
The transaction fee is fixed, you may query the forcedTransactionFeeInWei on the LineaRollupcontract:
// Pseudocode
const forcedTransactionFeeInWei = await lineaRollup.forcedTransactionFeeInWei();
await forcedTransactionGateway.submitForcedTransaction(forcedTransactionStruct, lastFinalizedStateStruct, {
value: forcedTransactionFeeInWei
});
At this stage, the transaction will also be checked by the AddressFilter to ensure that certain addresses, such as
precompiles, are not called directly.
The gateway will then send the transaction to the LineaRollup for storage.
Next steps
- Learn more about forced transactions.