Builders
Developer Tools
SDK Javascript
Estimate Transaction Costs on OP Mainnet

Estimate Transaction Costs on OP Mainnet (L2)

This tutorial teaches you how to use the Optimism SDK to estimate the gas costs of L2 transactions. This calculation is complicated by the fact that the major cost is the cost of writing the transaction on L1, it doesn't work to just multiply the gas used by the transaction by the gas price, the same way you would on L1. You can read the details of the calculation here (opens in a new tab).

The SDK supports multiple OP Chains: OP, Base, etc. To see whether a specific OP Chain is supported directly, see the documentation (opens in a new tab). Chains that aren't officially supported just take a few extra steps. Get the L1 contract addresses, and provide them to the SDK. Once you do that, you can use the SDK normally.

Before You Begin

The node script, gas.js, makes these assumptions:

Running the script

Make a Project Folder and Initialize It

mkdir project-folder-name
cd folder-name
pnpm init

Create Three Files for the Folder

  • Create a .env file
.env file
# Put the mnemonic for an account on Optimism here
MNEMONIC=test test test test test test test test test test test junk
 
# API KEY for Alchemy
ALCHEMY_API_KEY=xxxx
 
# URL to access Optimism Goerli (if not using Alchemy)
OPTIMISM_GOERLI_URL=xxxx
 
 
# URL to access Optimism mainnet (if not using Alchemy)
OPTIMISM_MAINNET_URL=xxxx
  • Create an gas.js file
gas.js
#! /usr/local/bin/node
 
// Estimate the costs of an OP Mainnet or OP Goerli transaction
 
const ethers = require("ethers")
const optimismSDK = require("@eth-optimism/sdk")
const fs = require("fs")
require('dotenv').config()
const yargs = require("yargs")
const { boolean } = require("yargs")
 
 
const argv = yargs
  .option('network', {
    // All of those choices are :
    // mainnet - OP Mainnet, the production network
    // goerli - OP Goerli, the main test network
    choices: ["mainnet", "goerli"],
    description: 'L2 network to use'
  }).
  option('verify', {
    type: boolean,
    description: 'Run the transaction, compare to the estimate'
  })
  .help()
  .alias('help', 'h').argv;
 
 
const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length
validLength = [12, 15, 18, 24]
if (!validLength.includes(words)) {
    console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`)
    process.exit(-1)
}  
 
const greeterJSON = JSON.parse(fs.readFileSync("Greeter.json")) 
 
// These are the addresses of the Greeter.sol contract on the various OP networks:
// mainnet - OP Mainnet, the production network
// goerli - OP Goerli, the main test network
const greeterAddrs = {
  "mainnet":  "0xcf210488dad6da5fe54d260c45253afc3a9e708c",
  "goerli": "0x106941459a8768f5a92b770e280555faf817576f",
}
 
 
// Utilities
const displayWei = x => x.toString().padStart(20, " ")                        
const displayGas = x => x.toString().padStart(10, " ")
const sleep = ms => new Promise(resp => setTimeout(resp, ms));
 
// Get an L2 signer
const getSigner = async () => {
  let endpointUrl;
 
  if (argv.network == 'goerli')
    endpointUrl = 
      process.env.ALCHEMY_API_KEY ? 
        `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` :
        process.env.OPTIMISM_GOERLI_URL
  if (argv.network == 'mainnet')
    endpointUrl = 
      process.env.ALCHEMY_API_KEY ? 
        `https://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` :
        process.env.OPTIMISM_MAINNET_URL
 
    const l2RpcProvider = optimismSDK.asL2Provider(
      new ethers.providers.JsonRpcProvider(endpointUrl)
    )
    const wallet = ethers.Wallet.fromMnemonic(process.env.MNEMONIC).
      connect(l2RpcProvider)
 
    return wallet
}   // getSigner
 
 
// Get estimates from the SDK
const getEstimates = async (provider, tx) => {
  return {
    totalCost: await provider.estimateTotalGasCost(tx),
    l1Cost: await provider.estimateL1GasCost(tx),
    l2Cost: await provider.estimateL2GasCost(tx),
    l1Gas: await provider.estimateL1Gas(tx)
  }
}    // getEstimates
 
 
 
const displayResults = (estimated, real) => {
  console.log(`Estimates:`)
  console.log(`   Total gas cost: ${displayWei(estimated.totalCost)} wei`)
  console.log(`      L1 gas cost: ${displayWei(estimated.l1Cost)} wei`)
  console.log(`      L2 gas cost: ${displayWei(estimated.l2Cost)} wei`)
 
  if (argv.verify) {
    console.log(`\nReal values:`)    
    console.log(`   Total gas cost: ${displayWei(real.totalCost)} wei`)
    console.log(`      L1 gas cost: ${displayWei(real.l1Cost)} wei`)
    console.log(`      L2 gas cost: ${displayWei(real.l2Cost)} wei`)
 
    console.log(`\nL1 Gas:`)
    console.log(`      Estimate: ${displayGas(estimated.l1Gas)}`)
    console.log(`          Real: ${displayGas(real.l1Gas)}`)  
    console.log(`    Difference: ${displayGas(real.l1Gas-estimated.l1Gas)}`)
    
    console.log(`\nL2 Gas:`)
    console.log(`      Estimate: ${displayGas(estimated.l2Gas)}`)
    console.log(`          Real: ${displayGas(real.l2Gas)}`)  
    console.log(`    Difference: ${displayGas(real.l2Gas-estimated.l2Gas)}`)
  } else {   // if argv.verify
    console.log(`      L1 gas: ${displayGas(estimated.l1Gas)}`)
    console.log(`      L2 gas: ${displayGas(estimated.l2Gas)}`)
  }   // if argv.verify
 
}   // displayResults
 
 
 
const main = async () => {    
    
    const signer = await getSigner()
 
    if(!greeterAddrs[argv.network]) {
      console.log(`I don't know the Greeter address on chain: ${argv.network}`)
      process.exit(-1)  
    }
 
    const Greeter = new ethers.ContractFactory(greeterJSON.abi, greeterJSON.bytecode, signer)
    const greeter = Greeter.attach(greeterAddrs[argv.network])
 
    const greeting = "Hello!"
 
    let real = {}
 
    const fakeTxReq = await greeter.populateTransaction.setGreeting(greeting)
    const fakeTx = await signer.populateTransaction(fakeTxReq)
    console.log("About to get estimates")
    let estimated = await getEstimates(signer.provider, fakeTx)
    estimated.l2Gas = await greeter.estimateGas.setGreeting(greeting)
 
    if (argv.verify) {
      let realTx, realTxResp
      const weiB4 = await signer.getBalance()
 
     // If the transaction fails, error out with additional information
      try {
        console.log("About to create the transaction")
        realTx = await greeter.setGreeting(greeting)
        realTx.gasPrice = realTx.maxFeePerGas;
        console.log("Transaction created and submitted")
        realTxResp = await realTx.wait()
        console.log("Transaction processed")
      } catch (err) {
        console.log(`Error: ${err}`)
        console.log(`Coming from address: ${await signer.getAddress()} on Optimistic ${network}`)
        console.log(`            balance: ${displayWei(await signer.getBalance())} wei`)
        process.exit(-1)
      }
 
      // If the balance hasn't been updated yet, wait 0.1 sec
      real.totalCost = 0
      while (real.totalCost === 0) {
          const weiAfter = await signer.getBalance()
          real.totalCost= weiB4-weiAfter
          await sleep(100)
      }
 
      // Get the real information (cost, etc.) from the transaction response
      real.l1Gas = realTxResp.l1GasUsed
      real.l2Gas = realTxResp.gasUsed
      real.l1Cost = realTxResp.l1Fee 
      real.l2Cost = real.totalCost - real.l1Cost
    }  // if argv.verify
 
    displayResults(estimated, real)
        
}  // main
 
 
main().then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
  • Create a Greeter.json file
Greeter.json
{
  "_format": "hh-sol-artifact-1",
  "contractName": "Greeter",
  "sourceName": "contracts/Greeter.sol",
  "abi": [
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_greeting",
          "type": "string"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [],
      "name": "greet",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_greeting",
          "type": "string"
        }
      ],
      "name": "setGreeting",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ],
  "bytecode": "0x60806040523480156200001157600080fd5b5060405162000c3238038062000c32833981810160405281019062000037919062000278565b6200006760405180606001604052806022815260200162000c1060229139826200008760201b620001ce1760201c565b80600090805190602001906200007f92919062000156565b5050620004c5565b620001298282604051602401620000a0929190620002fe565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506200012d60201b60201c565b5050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b8280546200016490620003ea565b90600052602060002090601f016020900481019282620001885760008555620001d4565b82601f10620001a357805160ff1916838001178555620001d4565b82800160010185558215620001d4579182015b82811115620001d3578251825591602001919060010190620001b6565b5b509050620001e39190620001e7565b5090565b5b8082111562000202576000816000905550600101620001e8565b5090565b60006200021d620002178462000362565b62000339565b9050828152602081018484840111156200023657600080fd5b62000243848285620003b4565b509392505050565b600082601f8301126200025d57600080fd5b81516200026f84826020860162000206565b91505092915050565b6000602082840312156200028b57600080fd5b600082015167ffffffffffffffff811115620002a657600080fd5b620002b4848285016200024b565b91505092915050565b6000620002ca8262000398565b620002d68185620003a3565b9350620002e8818560208601620003b4565b620002f381620004b4565b840191505092915050565b600060408201905081810360008301526200031a8185620002bd565b90508181036020830152620003308184620002bd565b90509392505050565b60006200034562000358565b905062000353828262000420565b919050565b6000604051905090565b600067ffffffffffffffff82111562000380576200037f62000485565b5b6200038b82620004b4565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b60005b83811015620003d4578082015181840152602081019050620003b7565b83811115620003e4576000848401525b50505050565b600060028204905060018216806200040357607f821691505b602082108114156200041a576200041962000456565b5b50919050565b6200042b82620004b4565b810181811067ffffffffffffffff821117156200044d576200044c62000485565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61073b80620004d56000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061043d565b610075565b005b61005f61013c565b60405161006c91906104b7565b60405180910390f35b6101226040518060600160405280602381526020016106e3602391396000805461009e90610610565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca90610610565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b50505050508361026a565b8060009080519060200190610138929190610332565b5050565b60606000805461014b90610610565b80601f016020809104026020016040519081016040528092919081815260200182805461017790610610565b80156101c45780601f10610199576101008083540402835291602001916101c4565b820191906000526020600020905b8154815290600101906020018083116101a757829003601f168201915b5050505050905090565b61026682826040516024016101e49291906104d9565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610309565b5050565b61030483838360405160240161028293929190610510565b6040516020818303038152906040527f2ced7cef000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610309565b505050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b82805461033e90610610565b90600052602060002090601f01602090048101928261036057600085556103a7565b82601f1061037957805160ff19168380011785556103a7565b828001600101855582156103a7579182015b828111156103a657825182559160200191906001019061038b565b5b5090506103b491906103b8565b5090565b5b808211156103d15760008160009055506001016103b9565b5090565b60006103e86103e384610581565b61055c565b90508281526020810184848401111561040057600080fd5b61040b8482856105ce565b509392505050565b600082601f83011261042457600080fd5b81356104348482602086016103d5565b91505092915050565b60006020828403121561044f57600080fd5b600082013567ffffffffffffffff81111561046957600080fd5b61047584828501610413565b91505092915050565b6000610489826105b2565b61049381856105bd565b93506104a38185602086016105dd565b6104ac816106d1565b840191505092915050565b600060208201905081810360008301526104d1818461047e565b905092915050565b600060408201905081810360008301526104f3818561047e565b90508181036020830152610507818461047e565b90509392505050565b6000606082019050818103600083015261052a818661047e565b9050818103602083015261053e818561047e565b90508181036040830152610552818461047e565b9050949350505050565b6000610566610577565b90506105728282610642565b919050565b6000604051905090565b600067ffffffffffffffff82111561059c5761059b6106a2565b5b6105a5826106d1565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b838110156105fb5780820151818401526020810190506105e0565b8381111561060a576000848401525b50505050565b6000600282049050600182168061062857607f821691505b6020821081141561063c5761063b610673565b5b50919050565b61064b826106d1565b810181811067ffffffffffffffff8211171561066a576106696106a2565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fe4368616e67696e67206772656574696e672066726f6d202725732720746f2027257327a264697066735822122062b06e5bdee39e73f7ae7ef8606fe9f23da851629e4e297316ce7747f5074b1964736f6c634300080400334465706c6f79696e67206120477265657465722077697468206772656574696e673a",
  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061043d565b610075565b005b61005f61013c565b60405161006c91906104b7565b60405180910390f35b6101226040518060600160405280602381526020016106e3602391396000805461009e90610610565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca90610610565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b50505050508361026a565b8060009080519060200190610138929190610332565b5050565b60606000805461014b90610610565b80601f016020809104026020016040519081016040528092919081815260200182805461017790610610565b80156101c45780601f10610199576101008083540402835291602001916101c4565b820191906000526020600020905b8154815290600101906020018083116101a757829003601f168201915b5050505050905090565b61026682826040516024016101e49291906104d9565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610309565b5050565b61030483838360405160240161028293929190610510565b6040516020818303038152906040527f2ced7cef000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610309565b505050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b82805461033e90610610565b90600052602060002090601f01602090048101928261036057600085556103a7565b82601f1061037957805160ff19168380011785556103a7565b828001600101855582156103a7579182015b828111156103a657825182559160200191906001019061038b565b5b5090506103b491906103b8565b5090565b5b808211156103d15760008160009055506001016103b9565b5090565b60006103e86103e384610581565b61055c565b90508281526020810184848401111561040057600080fd5b61040b8482856105ce565b509392505050565b600082601f83011261042457600080fd5b81356104348482602086016103d5565b91505092915050565b60006020828403121561044f57600080fd5b600082013567ffffffffffffffff81111561046957600080fd5b61047584828501610413565b91505092915050565b6000610489826105b2565b61049381856105bd565b93506104a38185602086016105dd565b6104ac816106d1565b840191505092915050565b600060208201905081810360008301526104d1818461047e565b905092915050565b600060408201905081810360008301526104f3818561047e565b90508181036020830152610507818461047e565b90509392505050565b6000606082019050818103600083015261052a818661047e565b9050818103602083015261053e818561047e565b90508181036040830152610552818461047e565b9050949350505050565b6000610566610577565b90506105728282610642565b919050565b6000604051905090565b600067ffffffffffffffff82111561059c5761059b6106a2565b5b6105a5826106d1565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b838110156105fb5780820151818401526020810190506105e0565b8381111561060a576000848401525b50505050565b6000600282049050600182168061062857607f821691505b6020821081141561063c5761063b610673565b5b50919050565b61064b826106d1565b810181811067ffffffffffffffff8211171561066a576106696106a2565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fe4368616e67696e67206772656574696e672066726f6d202725732720746f2027257327a264697066735822122062b06e5bdee39e73f7ae7ef8606fe9f23da851629e4e297316ce7747f5074b1964736f6c63430008040033",
  "linkReferences": {},
  "deployedLinkReferences": {}
}

Install the dependencies

pnpm add @eth-optimism/sdk@^3.0.0 dotenv@^16.0.1 yarg@^1.0.8

Edit the env file

Edit the env file as follows:

Use Node to run the script

node gas.js --network mainnet

The command line options are:

  • --network: The network to estimate gas on:

    • mainnet: OP Mainnet
    • goerli: OP Goerli
  • --verify: Run the transaction to verify the estimate

Results

Here is an example of results from OP Mainnet:

ori@Oris-MBP sdk-estimate-gas % ./gas.js --network mainnet --verify
ori@Oris-MacBook-Pro sdk-estimate-gas % ./gas.js --network mainnet --verify
About to get estimates
About to create the transaction
Transaction created and submitted
Transaction processed
Estimates:
   Total gas cost:       58819800030256 wei
      L1 gas cost:       58787232030256 wei
      L2 gas cost:          32568000000 wei

Real values:
   Total gas cost:       58819786030272 wei
      L1 gas cost:       58787232030256 wei
      L2 gas cost:          32554000016 wei

L1 Gas:
      Estimate:       4276
          Real:       4276
    Difference:          0

L2 Gas:
      Estimate:      32568
          Real:      32554
    Difference:        -14

The L1 gas cost is over a thousand times the L2 gas cost. This is typical in Optimistic transactions, because of the cost ratio between L1 gas and L2 gas.

Conclusion

Using the Optimism SDK, you can show users how much a transaction would cost before they submit it. This is a useful feature in decentralized apps because it lets people decide if the transaction is worth doing or not.

How does it work?

In this section we go over the relevant parts of the script.

Setup

#! /usr/local/bin/node
 
// Estimate the costs of an Optimistic (L2) transaction
 
const ethers = require("ethers")
const optimismSDK = require("@eth-optimism/sdk")
const fs = require("fs")
require('dotenv').config()
const yargs = require("yargs")
const { boolean } = require("yargs")

The packages needed for the script.

 
const argv = yargs
  .option('network', {
    // mainnet - OP Mainnet, the production network
    // goerli - OP Goerli, the main test network
    choices: ["mainnet", "goerli"],
    description: 'OP network to use'
  }).
  option('verify', {
    type: boolean,
    description: 'Run the transaction, compare to the estimate'
  })
  .help()
  .alias('help', 'h').argv;

Use the yargs package (opens in a new tab) to read the command line parameters.

const greeterJSON = JSON.parse(fs.readFileSync("Greeter.json")) 

Read the Greeter.json file to know how to use the Greeter contract.

// These are the addresses of the Greeter.sol contract on the various Optimism networks:
// mainnet - OP Mainnet, the production network
// goerli - OP Goerli, the main test network
const greeterAddrs = {
  "mainnet":  "0xcf210488dad6da5fe54d260c45253afc3a9e708c",
  "goerli": "0x106941459a8768f5a92b770e280555faf817576f"
}

Addresses for the Greeter contracts:

// Utilities
const displayWei = x => x.toString().padStart(20, " ")                        
const displayGas = x => x.toString().padStart(10, " ")

Display a value (either wei or gas). To properly align these values for display, we first turn them into strings (opens in a new tab) and then add spaces to the start (opens in a new tab) until the total value is the right length (20 or 10 characters).

const sleep = ms => new Promise(resp => setTimeout(resp, ms));

Return a Promise (opens in a new tab) that gets resolved after ms milliseconds.

getSigner

const getSigner = async () => {
  let endpointUrl;
 
  if (argv.network == 'goerli')
    endpointUrl = 
      process.env.ALCHEMY_API_KEY ? 
        `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` :
        process.env.OPTIMISM_GOERLI_URL
  if (argv.network == 'mainnet')
    endpointUrl = 
      process.env.ALCHEMY_API_KEY ? 
        `https://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` :
        process.env.OPTIMISM_MAINNET_URL
 
    const l2RpcProvider = optimismSDK.asL2Provider(
      new ethers.providers.JsonRpcProvider(endpointUrl)
    )

The function optimismSDK.asL2Provider (opens in a new tab) takes a regular Ethers.js Provider (opens in a new tab) and adds a few L2 specific functions, which are explained below. Because it only adds functions, an L2Provider (opens in a new tab) can be used anywhere you use an Ethers Provider.

    const wallet = ethers.Wallet.fromMnemonic(process.env.MNEMONIC).
      connect(l2RpcProvider)
 
    return wallet
}   // getSigner

getEstimates

// Get estimates from the SDK
const getEstimates = async (provider, tx) => {
  return {
    totalCost: await provider.estimateTotalGasCost(tx),

Estimate the total cost (L1+L2) of running the transaction (opens in a new tab).

:warning: This function calls eth_estimateGas, which runs the transaction in the node (without changing the blockchain state). This means that the account in l2Provider has to have enough ETH to pay for the gas cost of the transaction.

    l1Cost: await provider.estimateL1GasCost(tx),
    l2Cost: await provider.estimateL2GasCost(tx),

Estimate the two components of the cost: L1 (opens in a new tab) and L2 (opens in a new tab).

    l1Gas: await provider.estimateL1Gas(tx)
  }
}    // getEstimates

Get the amount of gas we expect to use to store the transaction on L1 (opens in a new tab).

displayResults

 
const displayResults = (estimated, real) => {
  console.log(`Estimates:`)
  console.log(`   Total gas cost: ${displayWei(estimated.totalCost)} wei`)
  console.log(`      L1 gas cost: ${displayWei(estimated.l1Cost)} wei`)
  console.log(`      L2 gas cost: ${displayWei(estimated.l2Cost)} wei`)

Show the gas cost estimates.

  if (argv.verify) {
    console.log(`\nReal values:`)    
    console.log(`   Total gas cost: ${displayWei(real.totalCost)} wei`)
    console.log(`      L1 gas cost: ${displayWei(real.l1Cost)} wei`)
    console.log(`      L2 gas cost: ${displayWei(real.l2Cost)} wei`)

If we are verifying the estimates, show the real values.

    console.log(`\nL1 Gas:`)
    console.log(`      Estimate: ${displayGas(estimated.l1Gas)}`)
    console.log(`          Real: ${displayGas(real.l1Gas)}`)  
    console.log(`    Difference: ${displayGas(real.l1Gas-estimated.l1Gas)}`)

Compare the L1 gas estimated with the L1 gas actually required.

    console.log(`\nL2 Gas:`)
    console.log(`      Estimate: ${displayGas(estimated.l2Gas)}`)
    console.log(`          Real: ${displayGas(real.l2Gas)}`)  
    console.log(`    Difference: ${displayGas(real.l2Gas-estimated.l2Gas)}`)

Compare the L2 gas estimates with the L2 gas actually required.

  } else {   // if argv.verify
    console.log(`      L1 gas: ${displayGas(estimated.l1Gas)}`)
    console.log(`      L2 gas: ${displayGas(estimated.l2Gas)}`)
  }   // if argv.verify
 
}   // displayResults

If we aren't verifying the estimate, just display the estimated values.

main

const main = async () => {    
    
    const signer = await getSigner()
 
    if(!greeterAddrs[argv.network]) {
      console.log(`I don't know the Greeter address on chain: ${argv.network}`)
      process.exit(-1)  
    }
 
    const Greeter = new ethers.ContractFactory(greeterJSON.abi, greeterJSON.bytecode, signer)
    const greeter = Greeter.attach(greeterAddrs[argv.network])
 
    const greeting = "Hello!"
 
    let real = {}

To create a valid estimate, we need these transaction fields:

  • data
  • to
  • gasPrice
  • type
  • nonce
  • gasLimit

We need the exact values, because a zero costs only 4 gas and any other byte costs 16 bytes. For example, it is cheaper to encode gasLimit if it is 0x100000 rather than 0x10101.

    const fakeTxReq = await greeter.populateTransaction.setGreeting(greeting)

Ether's populateTransaction function (opens in a new tab) gives us three fields:

  • data
  • from
  • to
    const fakeTx = await signer.populateTransaction(fakeTxReq)

The contract cannot provide us with the nonce, chainId, gasPrice, or gasLimit. To get those fields we use signer.populateTransaction (opens in a new tab).

    console.log("About to get estimates")
    let estimated = await getEstimates(signer.provider, fakeTx)

Call getEstimates to get the L2Provider estimates.

    estimated.l2Gas = await greeter.estimateGas.setGreeting(greeting)

There is no need for a special function to estimate the amount of L2 gas, the normal estimateGas.<function> can do the same job it usually does.

    if (argv.verify) {

// If we want to run the real transaction to verify the estimate

      // If the transaction fails, error out with additional information
      let realTx, realTxResp
      const weiB4 = await signer.getBalance()

Get the balance prior to the transaction, so we'll be able to see how much it really cost.

      try {
        console.log("About to create the transaction")
        realTx = await greeter.setGreeting(greeting)
        console.log("Transaction created, submitting it")
        realTxResp = await realTx.wait()
        console.log("Transaction processed")

Create the transaction and then wait for it to be processed. This is the standard way to submit a transaction in Ethers (opens in a new tab).

      } catch (err) {        
        console.log(`Error: ${err}`)
        console.log(`Coming from address: ${await signer.getAddress()} on Optimistic ${network}`)
        console.log(`            balance: ${displayWei(await signer.getBalance())} wei`)
        process.exit(-1)
      }

If the transaction failed, it could be because the account lacks the ETH to pay for gas. The error message shows that information so the user knows about it.

      // If the balance hasn't been updated yet, wait 0.1 sec
      real.totalCost = 0
      while (real.totalCost === 0) {
          const weiAfter = await signer.getBalance()
          real.totalCost= weiB4-weiAfter
          await sleep(100)
      }

It takes a bit of time before the change in the account's balance is processed. This loop lets us wait until it is processed so we'll be able to know the full cost.

Note that this is not the only way to wait until a transaction happens. You can also use crossDomainMessenger.waitForMessageStatus (opens in a new tab).

      // Get the real information (cost, etc.) from the transaction response
      real.l1Gas = realTxResp.l1GasUsed
      real.l1Cost = realTxResp.l1Fee 

These fields are specific to OP Mainnet and OP Goerli transaction responses.

      real.l2Gas = realTxResp.gasUsed

The gas used on L2 is the gas used for processing. This field is standard in Ethers (opens in a new tab).

      real.l2Cost = real.totalCost - real.l1Cost
    }  // if argv.verified

This is one way to get the L2 cost of the transaction. Another would be to multiply gasUsed by gasPrice.

    displayResults(estimated, real)    
}  // main
 
 
main().then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })