diff --git a/pages/stack/transactions/fees.mdx b/pages/stack/transactions/fees.mdx
index bcd878248..7c57e6d77 100644
--- a/pages/stack/transactions/fees.mdx
+++ b/pages/stack/transactions/fees.mdx
@@ -15,6 +15,7 @@ categories:
- gas
- l1-data-fee
- execution-gas-fee
+ - operator-fee
- eip-1559
- sequencer-fee-vault
is_imported_content: 'false'
@@ -26,7 +27,7 @@ import { Callout } from 'nextra/components'
The OP Stack maintains a distinct gas limit compared to the Ethereum mainnet.
While both chains use the same underlying transaction formats, Optimism's gas limits are tailored for optimal Layer 2 performance and scalability.
As a result, transactions on Optimism may behave differently from the mainnet regarding gas usage and fee estimation.
- For a detailed comparison of gas limits between Optimism and Ethereum, see the [Optimism Chain Differences documentation](https://docs.optimism.io/chain/differences?utm_source=op-docs&utm_medium=docs#transaction-fees).
+ For a detailed comparison of gas limits between Optimism and Ethereum, see the [Differences between Ethereum and OP Stack Chains](https://docs.optimism.io/chain/differences?utm_source=op-docs\&utm_medium=docs#transaction-fees).
# Transaction fees on OP Mainnet
@@ -35,8 +36,23 @@ OP Mainnet is designed to be [EVM equivalent](https://web.archive.org/web/202311
However, transaction fees on all Layer 2 systems need to diverge from Ethereum to some extent for a number of reasons.
This page provides a detailed look at exactly how transaction fees work on OP Mainnet so you can properly account for them in your application.
-OP Mainnet transaction fees are composed of an [Execution Gas Fee](#execution-gas-fee) and an [L1 Data Fee](#l1-data-fee).
-The total cost of a transaction is the sum of these two fees.
+OP Mainnet transaction fees are composed of an [Execution gas fee](#execution-gas-fee), an [L1 data fee](#l1-data-fee), and after the [Isthmus upgrade](/notices/upgrade-14#operator-fee), an [operator fee](#operator-fee).
+The total cost of a transaction is the sum of these fee components:
+
+**Before Isthmus:**
+
+```
+totalFee = gasUsed * (baseFee + priorityFee) + l1Fee
+```
+
+**After Isthmus:**
+
+```
+operatorFee = operatorFeeConstant + operatorFeeScalar * gasUsed / 1e6
+
+totalFee = operatorFee + gasUsed * (baseFee + priorityFee) + l1Fee
+```
+
Continue reading to learn more about exactly how each of these fee components are charged.
## Execution gas fee
@@ -112,7 +128,7 @@ the L1 Data Fee are generally quite small and should not impact the average tran
The L1 Data Fee formula changed with the Ecotone upgrade.
- Refer to the [Network Upgrade Overview](/operators/node-operators/network-upgrades) for network upgrade activation timestamps for OP Sepolia and OP Mainnet.
+ Refer to the [Network upgrade overview](/operators/node-operators/network-upgrades) for network upgrade activation timestamps for OP Sepolia and OP Mainnet.
Prior to the Ecotone upgrade, the L1 Data Fee is calculated based on the following parameters:
@@ -224,20 +240,75 @@ l1Cost = estimatedSizeScaled * l1FeeScaled / 1e12
The new cost function takes into account the compression ratio, so chain operators will need to adjust their `baseFeeScalar` and `blobFeeScalar` to account for any previous compression ratios that they encountered on their chains. Chain operators can use the [Fjord fee parameter calculator](https://docs.google.com/spreadsheets/d/1V3CWpeUzXv5Iopw8lBSS8tWoSzyR4PDDwV9cu2kKOrs/edit#gid=186414307) to get a better estimate of scalar values to use for their chain.
-## Sequencer Fee Vault
+## Operator fee
+
+
+ The Operator fee is introduced with the [Isthmus upgrade](/notices/upgrade-14#operator-fee) and provides OP Stack chains with more flexible pricing models.
+ Refer to the [Network upgrade overview](/operators/node-operators/network-upgrades) for network upgrade activation timestamps.
+
+
+The Operator fee is a new fee component introduced with the Isthmus upgrade that enables more customizable fee structures for OP Stack chains. This fee is integrated directly into the EVM alongside the standard gas fee and L1 data fee, allowing chain operators to implement additional revenue models or cover specific operational costs.
+
+### Mechanism
+
+The Operator fee is automatically charged for any transaction that is included in a block after the Isthmus activation. This fee is deducted directly from the address that sent the transaction and follows the same semantics as existing fees charged in the EVM. The collected operator fees are sent to the **Operator Fee Vault**, a new vault similar to existing fee vaults.
+
+
+ **Deposit transactions do not get charged operator fees.** For all deposit transactions, regardless of the operator fee parameter configuration, the operator fee is always zero.
+
+
+### Formula
+
+The Operator fee is calculated using the following formula:
+
+```
+operatorFee = (gas × operatorFeeScalar ÷ 10^6) + operatorFeeConstant
+```
+
+Where:
+
+* `gas` is the amount of gas that the transaction used
+* `operatorFeeScalar` is a `uint32` scalar set by the chain operator, scaled by 1e6
+* `operatorFeeConstant` is a `uint64` scalar set by the chain operator
+
+### Configuration
+
+The `operatorFeeScalar` and `operatorFeeConstant` parameters can be accessed in two ways:
+
+1. **From deposited L1 attributes** of the current L2 block
+2. **From the L1 Block Info contract** (`0x4200000000000000000000000000000000000015`):
+ * Using solidity getter functions (`operatorFeeScalar`, `operatorFeeConstant`)
+ * Using direct storage reads:
+ * Operator fee scalar as big-endian `uint32` in slot `8` at offset `0`
+ * Operator fee constant as big-endian `uint64` in slot `8` at offset `4`
+
+### EVM Integration
+
+The Operator fee follows standard EVM fee semantics:
+
+1. **Pre-execution validation**: Account must have enough ETH to cover worst-case gas + L1 data fees + worst-case operator fee
+2. **Gas purchase**: Account is charged the worst-case operator fee before execution
+3. **Refunds**: After execution, unused operator fee gas is refunded to the account
+4. **Fee distribution**: The spent operator fee is sent to the Operator Fee Vault
+
+### Transaction pool impact
+
+Transaction pools must account for the additional operator fee when validating transactions. Transactions without sufficient balance to cover the worst-case total cost (including operator fee) will be rejected.
+
+## Sequencer fee vault
-The Sequencer Fee Vault collects and holds transaction fees paid to the sequencer during block production on OP Mainnet. These fees cover the cost of posting transaction data to L1, ensuring network sustainability and data availability.
+The Sequencer fee vault collects and holds transaction fees paid to the sequencer during block production on OP Mainnet. These fees cover the cost of posting transaction data to L1, ensuring network sustainability and data availability.
-### Fee Collection and Distribution
+### Fee collection and distribution
-* **Purpose**: The sequencer deposits collected fees into the Sequencer Fee Vault. These fees reimburse the sequencer for gas costs when submitting transaction batches to L1.
-* **Vault Address**: The Sequencer Fee Vault is predeployed at the address `0x4200000000000000000000000000000000000011` on the OP Mainnet.
-* **Fee Usage**: Stored fees are eventually transferred to a designated recipient address (e.g., a treasury or distribution contract).
+* **Purpose**: The sequencer deposits collected fees into the Sequencer fee vault. These fees reimburse the sequencer for gas costs when submitting transaction batches to L1.
+* **Vault address**: The Sequencer fee vault is predeployed at the address `0x4200000000000000000000000000000000000011` on the OP Mainnet.
+* **Fee usage**: Stored fees are eventually transferred to a designated recipient address (e.g., a treasury or distribution contract).
### How it works
-1. **Fee Collection**: During the processing of transactions, the sequencer collects fees from users as part of their transaction costs. These fees are primarily used to cover the gas expenses of posting transaction data to Ethereum L1.
-2. **Storage**: Collected fees are deposited into the Sequencer Fee Vault contract.
+1. **Fee collection**: During the processing of transactions, the sequencer collects fees from users as part of their transaction costs. These fees are primarily used to cover the gas expenses of posting transaction data to Ethereum L1.
+2. **Storage**: Collected fees are deposited into the Sequencer fee vault contract.
3. **Distribution**: The fees are later distributed to the appropriate recipient, typically covering operational costs like L1 gas fees for data availability.
This system ensures effective fee management, maintaining the security and operation of the Optimism network.
diff --git a/pages/stack/transactions/withdrawal-flow.mdx b/pages/stack/transactions/withdrawal-flow.mdx
index 76a215339..162f1cb52 100644
--- a/pages/stack/transactions/withdrawal-flow.mdx
+++ b/pages/stack/transactions/withdrawal-flow.mdx
@@ -33,12 +33,12 @@ Withdrawals require the user to submit three transactions:
3. **Withdrawal finalizing transaction**, which the user submits on L1 after the fault challenge period has passed, to actually run the transaction on L1.
- You can see an example of how to do this [in the bridging tutorials](/app-developers/tutorials/bridging/cross-dom-bridge-erc20).
+ You can see an example of how to implement this process [in the bridging tutorials](/app-developers/tutorials/bridging/cross-dom-bridge-erc20).
## Withdrawal initiating transaction
-1. On L2, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`sendMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L249-L289) function of the [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol) contract.
+1. On L2, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`sendMessage`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol#L191) function of the [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21) contract.
This function accepts three parameters:
@@ -46,48 +46,39 @@ Withdrawals require the user to submit three transactions:
* `_message`, the L1 transaction's calldata, formatted as per the [ABI](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html) of the target address.
* `_minGasLimit`, The minimum amount of gas that the withdrawal finalizing transaction can provide to the withdrawal transaction. This is enforced by the `SafeCall` library, and if the minimum amount of gas cannot be met at the time of the external call from the `OptimismPortal` -> `L1CrossDomainMessenger`, the finalization transaction will revert to allow for re-attempting with a higher gas limit. In order to account for the gas consumed in the `L1CrossDomainMessenger.relayMessage` function's execution, extra gas will be added on top of the `_minGasLimit` value by the `CrossDomainMessenger.baseGas` function when `sendMessage` is called on L2.
-2. `sendMessage` is a generic function that is used in both cross domain messengers.
- It calls [`_sendMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2CrossDomainMessenger.sol#L48-L60), which is specific to [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2CrossDomainMessenger.sol).
+2. `sendMessage` is a generic function that is used in both cross domain messengers. It calls [`_sendMessage`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L45), which is specific to [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21).
-3. `_sendMessage` calls [`initiateWithdrawal`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol#L91-L129) on [`L2ToL1MessagePasser`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol).
- This function calculates the hash of the raw withdrawal fields.
- It then [marks that hash as a sent message in `sentMessages`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol#L114) and [emits the fields with the hash in a `MessagePassed` event](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol#L116-L124).
+3. `_sendMessage` calls [`initiateWithdrawal`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L71) on [`L2ToL1MessagePasser`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L19). This function calculates the hash of the raw withdrawal fields. It then marks that hash as a sent message in [`sentMessages`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L30) and emits the fields with the hash in a [`MessagePassed`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L40) event.
The raw withdrawal fields are:
* `nonce` - A single use value to prevent two otherwise identical withdrawals from hashing to the same value
- * `sender` - The L2 address that initiated the transfer, typically [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L2/L2CrossDomainMessenger.sol)
+ * `sender` - The L2 address that initiated the transfer, typically [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21)
* `target` - The L1 target address
* `value` - The amount of WEI transferred by this transaction
- * `gasLimit` - Gas limit for the transaction, the system guarantees that at least this amount of gas will be available to the transaction on L1.
- Note that if the gas limit is not enough, or if the L1 finalizing transaction does not have enough gas to provide that gas limit, the finalizing transaction returns a failure, it does not revert.
+ * `gasLimit` - Gas limit for the transaction, the system guarantees that at least this amount of gas will be available to the transaction on L1. Note that if the gas limit is not enough, or if the L1 finalizing transaction does not have enough gas to provide that gas limit, the finalizing transaction returns a failure, it does not revert.
* `data` - The calldata for the withdrawal transaction
-4. When `op-proposer` [proposes a new output](https://github.com/ethereum-optimism/optimism/blob/4a3d3fb444f50bed6a6991785ea5634e0efa07a4/op-proposer/proposer/driver.go#L311), the output proposal includes [the output root](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/op-proposer/proposer/l2_output_submitter.go#L316), provided as part of the block by `op-node`.
+4. When `op-proposer`proposes a new `output`, the output proposal includes the [output root](https://specs.optimism.io/glossary.html?utm_source=op-docs\&utm_medium=docs#l2-output-root), provided as part of the block by `op-node`.
This new output root commits to the state of the `sentMessages` mapping in the `L2ToL1MessagePasser` contract's storage on L2, and it can be used to prove the presence of a pending withdrawal within it.
## Withdrawal proving transaction
-Once an output root that includes the `MessagePassed` event is published to L1, the next step is to prove that the message hash really is in L2.
-Typically this is done [by the SDK](https://sdk.optimism.io/classes/crosschainmessenger#proveMessage-2).
+Once an output root that includes the `MessagePassed` event is published to L1, the next step is to prove that the message hash really is in L2. Typically this is done by viem.
### Offchain processing
-1. A user calls the SDK's [`CrossDomainMessenger.proveMessage()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L1452-L1471) with the hash of the L2 message.
- This function calls [`CrossDomainMessenger.populateTransaction.proveMessage()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L1746-L1798).
+1. A user calls viem's `proveWithdrawal()` function with the withdrawal transaction receipt. This function internally handles the preparation of the proving transaction parameters.
-2. To get from the L2 transaction hash to the raw withdrawal fields, the SDK uses [`toLowLevelMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L368-L450).
- It gets them from the `MessagePassed` event in the receipt.
+2. To get the withdrawal details from the L2 transaction, viem uses the `getWithdrawals()` function which extracts the raw withdrawal fields from the `MessagePassed` event in the transaction receipt.
-3. To get the proof, the SDK uses [`getBedrockMessageProof`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L1348-L1395).
+3. To get the proof, viem uses the withdrawal proving functionality to generate the necessary Merkle proof.
-4. Finally, the SDK calls [`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L234-L318) on L1.
+4. Finally, viem calls [`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L242) on L1.
### Onchain processing
-[`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L234-L318) runs a few sanity checks.
-Then it verifies that in `L2ToL1MessagePasser.sentMessages` on L2 the hash for the withdrawal is turned on, and that this proof has not been submitted before.
-If everything checks out, it writes the output root, the timestamp, and the L2 output index to which it applies in `provenWithdrawals` and emits an event.
+[`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L242) runs a few sanity checks. Then it verifies that in `L2ToL1MessagePasser.sentMessages` on L2 the hash for the withdrawal is turned on, and that this proof has not been submitted before. If everything checks out, it writes the output root, the timestamp, and the L2 output index to which it applies in `provenWithdrawals` and emits an event.
The next step is to wait the fault challenge period, to ensure that the L2 output root used in the proof is legitimate, and that the proof itself is legitimate and not a hack.
@@ -95,7 +86,7 @@ The next step is to wait the fault challenge period, to ensure that the L2 outpu
Finally, once the fault challenge period passes, the withdrawal can be finalized and executed on L1.
-To do so, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`finalizeWithdrawalTransaction`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320-L420) function of the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol) contract.
+To do so, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`finalizeWithdrawalTransaction`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320) function of the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L19) contract.
## Expected internal reverts in withdrawal transactions
@@ -115,13 +106,12 @@ The Chugsplash Proxy operates differently than standard proxies. During a withdr
### Offchain processing
-1. A user calls the SDK's [`CrossDomainMessenger.finalizeMessage()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L1473-L1493) with the hash of the L1 message.
- This function calls [`CrossDomainMessenger.populateTransaction.finalizeMessage()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L1800-L1853).
+1. A user calls viem's `finalizeWithdrawal()` function with the withdrawal transaction receipt.
+ This function internally handles the preparation of the finalization transaction parameters.
-2. To get from the L2 transaction hash to the raw withdrawal fields, the SDK uses [`toLowLevelMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/sdk/src/cross-chain-messenger.ts#L368-L450).
- It gets them from the `MessagePassed` event in the receipt.
+2. To get the withdrawal details from the L2 transaction, viem uses the `getWithdrawals()` function which extracts the raw withdrawal fields from the `MessagePassed` event in the transaction receipt.
-3. Finally, the SDK calls [`OptimismPortal.finalizeWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320-L420) on L1.
+3. Finally, viem calls [`OptimismPortal.finalizeWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320-L420) on L1.
### Onchain processing