github.com/ethereum-optimism/optimism@v1.7.2/packages/web3js-plugin/src/plugin.ts (about) 1 import Web3, { 2 type BlockNumberOrTag, 3 BlockTags, 4 Contract, 5 type DataFormat, 6 DEFAULT_RETURN_FORMAT, 7 FMT_BYTES, 8 FMT_NUMBER, 9 type Transaction, 10 Web3PluginBase, 11 } from 'web3' 12 import { TransactionFactory, type TxData } from 'web3-eth-accounts' 13 import { estimateGas, formatTransaction } from 'web3-eth' 14 import { 15 gasPriceOracleABI, 16 gasPriceOracleAddress, 17 } from '@eth-optimism/contracts-ts' 18 import { RLP } from '@ethereumjs/rlp' 19 20 export class OptimismPlugin extends Web3PluginBase { 21 public pluginNamespace = 'op' 22 23 private _gasPriceOracleContract: 24 | Contract<typeof gasPriceOracleABI> 25 | undefined 26 27 /** 28 * Retrieves the current L2 base fee 29 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 30 * @returns {Promise<bigint>} - The L2 base fee as a BigInt by default, but {returnFormat} determines type 31 * @example 32 * const baseFeeValue: bigint = await web3.op.getBaseFee(); 33 * @example 34 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 35 * const baseFeeValue: number = await web3.op.getBaseFee(numberFormat); 36 */ 37 public async getBaseFee< 38 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 39 >(returnFormat?: ReturnFormat) { 40 return Web3.utils.format( 41 { format: 'uint' }, 42 await this._getPriceOracleContractInstance().methods.baseFee().call(), 43 returnFormat ?? DEFAULT_RETURN_FORMAT 44 ) 45 } 46 47 /** 48 * Retrieves the decimals used in the scalar 49 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 50 * @returns {Promise<Numbers>} - The number of decimals as a BigInt by default, but {returnFormat} determines type 51 * @example 52 * const decimalsValue: bigint = await web3.op.getDecimals(); 53 * @example 54 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 55 * const decimalsValue: number = await web3.op.getDecimals(numberFormat); 56 */ 57 public async getDecimals< 58 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 59 >(returnFormat?: ReturnFormat) { 60 return Web3.utils.format( 61 { format: 'uint' }, 62 await this._getPriceOracleContractInstance().methods.decimals().call(), 63 returnFormat ?? DEFAULT_RETURN_FORMAT 64 ) 65 } 66 67 /** 68 * Retrieves the current L2 gas price (base fee) 69 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 70 * @returns {Promise<Numbers>} - The current L2 gas price as a BigInt by default, but {returnFormat} determines type 71 * @example 72 * const gasPriceValue: bigint = await web3.op.getGasPrice(); 73 * @example 74 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 75 * const gasPriceValue: number = await web3.op.getGasPrice(numberFormat); 76 */ 77 public async getGasPrice< 78 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 79 >(returnFormat?: ReturnFormat) { 80 return Web3.utils.format( 81 { format: 'uint' }, 82 await this._getPriceOracleContractInstance().methods.gasPrice().call(), 83 returnFormat ?? DEFAULT_RETURN_FORMAT 84 ) 85 } 86 87 /** 88 * Computes the L1 portion of the fee based on the size of the rlp encoded input 89 * transaction, the current L1 base fee, and the various dynamic parameters 90 * @param transaction - An unsigned web3.js {Transaction} object 91 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 92 * @returns {Promise<Numbers>} - The fee as a BigInt by default, but {returnFormat} determines type 93 * @example 94 * const l1FeeValue: bigint = await getL1Fee(transaction); 95 * @example 96 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 97 * const l1FeeValue: number = await getL1Fee(transaction, numberFormat); 98 */ 99 public async getL1Fee< 100 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 101 >(transaction: Transaction, returnFormat?: ReturnFormat) { 102 return Web3.utils.format( 103 { format: 'uint' }, 104 await this._getPriceOracleContractInstance() 105 .methods.getL1Fee(this._serializeTransaction(transaction)) 106 .call(), 107 returnFormat ?? DEFAULT_RETURN_FORMAT 108 ) 109 } 110 111 /** 112 * Computes the amount of L1 gas used for {transaction}. Adds the overhead which 113 * represents the per-transaction gas overhead of posting the {transaction} and state 114 * roots to L1. Adds 68 bytes of padding to account for the fact that the input does 115 * not have a signature. 116 * @param transaction - An unsigned web3.js {Transaction} object 117 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 118 * @returns {Promise<Numbers>} - The amount gas as a BigInt by default, but {returnFormat} determines type 119 * @example 120 * const gasUsedValue: bigint = await getL1GasUsed(transaction); 121 * @example 122 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 123 * const gasUsedValue: number = await getL1GasUsed(transaction, numberFormat); 124 */ 125 public async getL1GasUsed< 126 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 127 >(transaction: Transaction, returnFormat?: ReturnFormat) { 128 return Web3.utils.format( 129 { format: 'uint' }, 130 await this._getPriceOracleContractInstance() 131 .methods.getL1GasUsed(this._serializeTransaction(transaction)) 132 .call(), 133 returnFormat ?? DEFAULT_RETURN_FORMAT 134 ) 135 } 136 137 /** 138 * Retrieves the latest known L1 base fee 139 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 140 * @returns {Promise<Numbers>} - The L1 base fee as a BigInt by default, but {returnFormat} determines type 141 * @example 142 * const baseFeeValue: bigint = await web3.op.getL1BaseFee(); 143 * @example 144 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 145 * const baseFeeValue: number = await web3.op.getL1BaseFee(numberFormat); 146 */ 147 public async getL1BaseFee< 148 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 149 >(returnFormat?: ReturnFormat) { 150 return Web3.utils.format( 151 { format: 'uint' }, 152 await this._getPriceOracleContractInstance().methods.l1BaseFee().call(), 153 returnFormat ?? DEFAULT_RETURN_FORMAT 154 ) 155 } 156 157 /** 158 * Retrieves the current fee overhead 159 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 160 * @returns {Promise<Numbers>} - The current overhead fee as a BigInt by default, but {returnFormat} determines type 161 * @example 162 * const overheadValue: bigint = await web3.op.getOverhead(); 163 * @example 164 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 165 * const overheadValue: number = await web3.op.getOverhead(numberFormat); 166 */ 167 public async getOverhead< 168 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 169 >(returnFormat?: ReturnFormat) { 170 return Web3.utils.format( 171 { format: 'uint' }, 172 await this._getPriceOracleContractInstance().methods.overhead().call(), 173 returnFormat ?? DEFAULT_RETURN_FORMAT 174 ) 175 } 176 177 /** 178 * Retrieves the current fee scalar 179 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 180 * @returns {Promise<Numbers>} - The current scalar fee as a BigInt by default, but {returnFormat} determines type 181 * @example 182 * const scalarValue: bigint = await web3.op.getScalar(); 183 * @example 184 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 185 * const scalarValue: number = await web3.op.getScalar(numberFormat); 186 */ 187 public async getScalar< 188 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 189 >(returnFormat?: ReturnFormat) { 190 return Web3.utils.format( 191 { format: 'uint' }, 192 await this._getPriceOracleContractInstance().methods.scalar().call(), 193 returnFormat ?? DEFAULT_RETURN_FORMAT 194 ) 195 } 196 197 /** 198 * Retrieves the full semver version of GasPriceOracle 199 * @returns {Promise<string>} - The semver version 200 * @example 201 * const version = await web3.op.getVersion(); 202 */ 203 public async getVersion() { 204 return this._getPriceOracleContractInstance().methods.version().call() 205 } 206 207 /** 208 * Retrieves the amount of L2 gas estimated to execute {transaction} 209 * @param transaction - An unsigned web3.js {Transaction} object 210 * @param {{ blockNumber: BlockNumberOrTag, returnFormat: DataFormat }} [options={blockNumber: BlockTags.LATEST, returnFormat: DEFAULT_RETURN_FORMAT}] - 211 * An options object specifying what block to use for gas estimates and the web3.js format object that specifies how to format number and bytes values 212 * @returns {Promise<Numbers>} - The gas estimate as a BigInt by default, but {returnFormat} determines type 213 * @example 214 * const l2Fee: bigint = await getL2Fee(transaction); 215 * @example 216 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 217 * const l2Fee: number = await getL2Fee(transaction, numberFormat); 218 */ 219 public async getL2Fee< 220 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 221 >( 222 transaction: Transaction, 223 options?: { 224 blockNumber?: BlockNumberOrTag | undefined 225 returnFormat?: ReturnFormat 226 } 227 ) { 228 const [gasCost, gasPrice] = await Promise.all([ 229 estimateGas( 230 this, 231 transaction, 232 options?.blockNumber ?? BlockTags.LATEST, 233 DEFAULT_RETURN_FORMAT 234 ), 235 this.getGasPrice(), 236 ]) 237 238 return Web3.utils.format( 239 { format: 'uint' }, 240 gasCost * gasPrice, 241 options?.returnFormat ?? DEFAULT_RETURN_FORMAT 242 ) 243 } 244 245 /** 246 * Computes the total (L1 + L2) fee estimate to execute {transaction} 247 * @param transaction - An unsigned web3.js {Transaction} object 248 * @param {DataFormat} [returnFormat=DEFAULT_RETURN_FORMAT] - The web3.js format object that specifies how to format number and bytes values 249 * @returns {Promise<Numbers>} - The estimated total fee as a BigInt by default, but {returnFormat} determines type 250 * @example 251 * const estimatedFees: bigint = await estimateFees(transaction); 252 * @example 253 * const numberFormat = { number: FMT_NUMBER.NUMBER, bytes: FMT_BYTES.HEX } 254 * const estimatedFees: number = await estimateFees(transaction, numberFormat); 255 */ 256 public async estimateFees< 257 ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT 258 >(transaction: Transaction, returnFormat?: ReturnFormat) { 259 const [l1Fee, l2Fee] = await Promise.all([ 260 this.getL1Fee(transaction), 261 this.getL2Fee(transaction), 262 ]) 263 264 return Web3.utils.format( 265 { format: 'uint' }, 266 l1Fee + l2Fee, 267 returnFormat ?? DEFAULT_RETURN_FORMAT 268 ) 269 } 270 271 /** 272 * Used to get the web3.js contract instance for gas price oracle contract 273 * @returns {Contract<typeof gasPriceOracleABI>} - A web.js contract instance with an RPC provider inherited from root {web3} instance 274 */ 275 private _getPriceOracleContractInstance() { 276 if (this._gasPriceOracleContract === undefined) { 277 this._gasPriceOracleContract = new Contract( 278 gasPriceOracleABI, 279 gasPriceOracleAddress[420] 280 ) 281 // This plugin's Web3Context is overridden with main Web3 instance's context 282 // when the plugin is registered. This overwrites the Contract instance's context 283 this._gasPriceOracleContract.link(this) 284 } 285 286 return this._gasPriceOracleContract 287 } 288 289 /** 290 * Returns the RLP encoded hex string for {transaction} 291 * @param transaction - A web3.js {Transaction} object 292 * @returns {string} - The RLP encoded hex string 293 */ 294 private _serializeTransaction(transaction: Transaction) { 295 const ethereumjsTransaction = TransactionFactory.fromTxData( 296 formatTransaction(transaction, { 297 number: FMT_NUMBER.HEX, 298 bytes: FMT_BYTES.HEX, 299 }) as TxData 300 ) 301 302 return Web3.utils.bytesToHex( 303 Web3.utils.uint8ArrayConcat( 304 Web3.utils.hexToBytes( 305 ethereumjsTransaction.type.toString(16).padStart(2, '0') 306 ), 307 // If <transaction> doesn't include a signature, 308 // <ethereumjsTransaction.raw()> will autofill v, r, and s 309 // with empty uint8Array. Because L1 fee calculation 310 // is dependent on the number of bytes, we are removing 311 // the zero values bytes 312 RLP.encode(ethereumjsTransaction.raw().slice(0, -3)) 313 ) 314 ) 315 } 316 } 317 318 // Module Augmentation to add op namespace to root {web3} instance 319 declare module 'web3' { 320 interface Web3Context { 321 op: OptimismPlugin 322 } 323 }