Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curvy-eyes-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added estimateOperatorFee action for OP Stack chains
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ vectors/**/*.json
site/dist
.vercel
vocs.config.tsx.timestamp*
site/.cache
174 changes: 174 additions & 0 deletions site/pages/op-stack/actions/estimateOperatorFee.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
description: Estimates the operator fee to execute an L2 transaction.
---

# estimateOperatorFee

Estimates the [operator fee](https://docs.optimism.io/stack/transactions/fees#operator-fee) to execute an L2 transaction.

The operator fee is part of the Isthmus upgrade and allows OP Stack operators to recover costs related to Alt-DA, ZK proving, or custom gas tokens. Returns 0 for pre-Isthmus chains or when operator fee functions don't exist.

The fee is calculated using the formula: `operatorFee = operatorFeeConstant + operatorFeeScalar * gasUsed / 1e6`

## Usage

:::code-group

```ts [example.ts]
import { account, publicClient } from './config'

const fee = await publicClient.estimateOperatorFee({ // [!code focus:7]
account,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

```ts [config.ts]
import { createPublicClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { publicActionsL2 } from 'viem/op-stack'

// JSON-RPC Account
export const account = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
// Local Account
export const account = privateKeyToAccount(...)

export const publicClient = createPublicClient({
chain: base,
transport: http()
}).extend(publicActionsL2())
```

:::

## Returns

`bigint`

The operator fee (in wei).

## Parameters

### account

- **Type:** `Account | Address`

The Account to estimate fee from.

Accepts a [JSON-RPC Account](/docs/clients/wallet#json-rpc-accounts) or [Local Account (Private Key, etc)](/docs/clients/wallet#local-accounts-private-key-mnemonic-etc).

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### data (optional)

- **Type:** `0x${string}`

Contract code or a hashed method call with encoded args.

```ts
const fee = await publicClient.estimateOperatorFee({
data: '0x...', // [!code focus]
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### l1BlockAddress (optional)

- **Type:** [`Address`](/docs/glossary/types#address)

Address of the L1Block predeploy contract.

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
l1BlockAddress: '0x4200000000000000000000000000000000000015', // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### maxFeePerGas (optional)

- **Type:** `bigint`

Total fee per gas (in wei), inclusive of `maxPriorityFeePerGas`.

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
maxFeePerGas: parseGwei('20'), // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### maxPriorityFeePerGas (optional)

- **Type:** `bigint`

Max priority fee per gas (in wei).

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('2'), // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### nonce (optional)

- **Type:** `number`

Unique number identifying this transaction.

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('2'),
nonce: 69, // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### to (optional)

- **Type:** [`Address`](/docs/glossary/types#address)

Transaction recipient.

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus]
value: parseEther('1')
})
```

### value (optional)

- **Type:** `bigint`

Value (in wei) sent with this transaction.

```ts
const fee = await publicClient.estimateOperatorFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1') // [!code focus]
})
```
23 changes: 19 additions & 4 deletions site/pages/op-stack/actions/estimateTotalFee.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
description: Estimates the L1 + L2 fee to execute an L2 transaction.
description: Estimates the L1 + L2 + operator fee to execute an L2 transaction.
---

# estimateTotalFee

Estimates the [L1 data fee](https://docs.optimism.io/stack/transactions/fees#l1-data-fee) + L2 fee to execute an L2 transaction.
Estimates the [L1 data fee](https://docs.optimism.io/stack/transactions/fees#l1-data-fee) + L2 fee + [operator fee](https://docs.optimism.io/stack/transactions/fees#operator-fee) to execute an L2 transaction.

It is the sum of [`estimateL1Fee`](/op-stack/actions/estimateL1Fee) (L1 Gas) and [`estimateGas`](/docs/actions/public/estimateGas.md) * [`getGasPrice`](/docs/actions/public/getGasPrice.md) (L2 Gas * L2 Gas Price).
It is the sum of [`estimateL1Fee`](/op-stack/actions/estimateL1Fee) (L1 Gas), [`estimateOperatorFee`](/op-stack/actions/estimateOperatorFee) (Operator Fee), and [`estimateGas`](/docs/actions/public/estimateGas.md) * [`getGasPrice`](/docs/actions/public/getGasPrice.md) (L2 Gas * L2 Gas Price).

## Usage

Expand Down Expand Up @@ -45,7 +45,7 @@ export const publicClient = createPublicClient({

`bigint`

The L1 fee (in wei).
The total fee (L1 + L2 + operator fee, in wei).

## Parameters

Expand Down Expand Up @@ -95,6 +95,21 @@ const fee = await publicClient.estimateTotalFee({
})
```

### l1BlockAddress (optional)

- **Type:** [`Address`](/docs/glossary/types#address)

Address of the L1Block predeploy contract.

```ts
const fee = await publicClient.estimateTotalFee({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
l1BlockAddress: '0x4200000000000000000000000000000000000015', // [!code focus]
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('1')
})
```

### maxFeePerGas (optional)

- **Type:** `bigint`
Expand Down
4 changes: 4 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,10 @@ export const sidebar = {
text: 'estimateL1Gas',
link: '/op-stack/actions/estimateL1Gas',
},
{
text: 'estimateOperatorFee',
link: '/op-stack/actions/estimateOperatorFee',
},
{
text: 'estimateTotalFee',
link: '/op-stack/actions/estimateTotalFee',
Expand Down
21 changes: 21 additions & 0 deletions src/op-stack/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ export const gasPriceOracleAbi = [
},
] as const

/**
* ABI for the OP Stack [`L1Block` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1Block.sol).
* @see https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000015
*/
export const l1BlockAbi = [
{
inputs: [],
name: 'operatorFeeScalar',
outputs: [{ internalType: 'uint32', name: '', type: 'uint32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'operatorFeeConstant',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function',
},
] as const

export const l2OutputOracleAbi = [
{
inputs: [
Expand Down
2 changes: 1 addition & 1 deletion src/op-stack/actions/estimateL1Fee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function estimateL1Fee<

const gasPriceOracleAddress = (() => {
if (gasPriceOracleAddress_) return gasPriceOracleAddress_
if (chain)
if (chain?.contracts?.gasPriceOracle)
return getChainContractAddress({
chain,
contract: 'gasPriceOracle',
Expand Down
2 changes: 1 addition & 1 deletion src/op-stack/actions/estimateL1Gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function estimateL1Gas<

const gasPriceOracleAddress = (() => {
if (gasPriceOracleAddress_) return gasPriceOracleAddress_
if (chain)
if (chain?.contracts?.gasPriceOracle)
return getChainContractAddress({
chain,
contract: 'gasPriceOracle',
Expand Down
73 changes: 73 additions & 0 deletions src/op-stack/actions/estimateOperatorFee.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, test } from 'vitest'

import { accounts } from '~test/src/constants.js'

import { anvilOptimism } from '../../../test/src/anvil.js'
import { parseGwei, type TransactionRequestEIP1559 } from '../../index.js'
import { parseEther } from '../../utils/unit/parseEther.js'
import { estimateOperatorFee } from './estimateOperatorFee.js'

const optimismClient = anvilOptimism.getClient()
const optimismClientWithAccount = anvilOptimism.getClient({ account: true })
const optimismClientWithoutChain = anvilOptimism.getClient({ chain: false })

const baseTransaction = {
maxFeePerGas: parseGwei('100'),
maxPriorityFeePerGas: parseGwei('1'),
to: accounts[1].address,
value: parseEther('0.1'),
} as const satisfies Omit<TransactionRequestEIP1559, 'from'>

test('default', async () => {
const fee = await estimateOperatorFee(
optimismClientWithAccount,
baseTransaction,
)
expect(fee).toBeDefined()
})

test('minimal', async () => {
const fee = await estimateOperatorFee(optimismClientWithAccount, {})
expect(fee).toBeDefined()
})

test('args: account', async () => {
const fee = await estimateOperatorFee(optimismClient, {
...baseTransaction,
account: accounts[0].address,
})
expect(fee).toBeDefined()
})

test('args: data', async () => {
const fee = await estimateOperatorFee(optimismClientWithAccount, {
...baseTransaction,
data: '0x00000000000000000000000000000000000000000000000004fefa17b7240000',
})
expect(fee).toBeDefined()
})

test('args: l1BlockAddress', async () => {
const fee = await estimateOperatorFee(optimismClientWithAccount, {
...baseTransaction,
l1BlockAddress: '0x4200000000000000000000000000000000000015',
})
expect(fee).toBeDefined()
})

test('args: nonce', async () => {
const fee = await estimateOperatorFee(optimismClientWithAccount, {
...baseTransaction,
nonce: 69,
})
expect(fee).toBeDefined()
})

test('args: nullish chain', async () => {
const fee = await estimateOperatorFee(optimismClientWithoutChain, {
...baseTransaction,
account: accounts[0].address,
chain: null,
})
expect(fee).toBeDefined()
})
Loading
Loading