Gas Network V2 Oracle

Overview

Gasnet currently stores estimates in the form of values representing facts about chain’s gas market or estimates for future prices. The values for a specific chain and timestamp has the following structure:

interface OraclePayload {
	height: bigint
	timestamp: bigint
	systemid: number
	chainid: bigint
	payloads: Array<OracleRecord>
}
solidity

Each OraclePayload contains a specific typ and its associated value :

interface OracleRecord {
	typ: number
	value: string
}
solidity

The typ identifies the meaning of the value. It can be a fact about the chain’s gas market (e.g. base fee) or an estimate for future price to settle on that chain (e.g. 90% confidence for inclusion in next 10 seconds).

Additional estimate values will be added to accommodate chain specific mechanics, such as blob pricing. Gas estimates are available for all the chains currently support by the Blocknative Gas API. The complete list of available chains is listed in the API docs available here: https://docs.blocknative.com/gas-prediction/gas-platform

Fetch Gasnet Estimation Data

The following code will fetch a gas estimate from Gasnet using Ethers in Typescript:

const GASNET_URL=https://test.devnet.gas.network
const GASNET_CONTRACT_ADDRESS = '0x4245Cb7c690B650a38E680C9BcfB7814D42BfD32'

const gasnetProvider = new ethers.JsonRpcProvider(GASNET_URL);
const gasnetContract = new ethers.Contract(
  GASNET_CONTRACT_ADDRESS,
  abi,
  gasnetProvider
); 

const payload = await gasnetContract.getValues('2', '1') // get estimates for Ethereum (chain  id 1)
typescript

This estimate containing the supported values for the specified chain includes a signature that the Gasnet Pull Oracle on the Consumer Chain uses to verify the integrity of the estimate.

Update Consumer Chain Oracle

Once a gas estimation is retrieved from Gasnet above, the following sample code puts the data on the Consumer Chain with the Pull Oracle using Ethers in Typescript:

Currently the Pull Oracle contract is deployed to the following networks. More will be added soon.

Mainnet

// Arbitrum One
export const CONSUMER_CHAIN_URL=https://arbitrum.llamarpc.com
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x1c51B22954af03FE11183aaDF43F6415907a9287'

// Base
export const CONSUMER_CHAIN_URL=https://base.llamarpc.com
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x35FD54AcB3219C90EFC9823877f3Cb2Df2b30e3f'

// Ethereum
export const CONSUMER_CHAIN_URL=https://eth.llamarpc.com
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x0248BC1998C36087B0B0d2D17B9461827067ae04'

// Linea
export const CONSUMER_CHAIN_URL=https://rpc.linea.build
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x34816ce96F8451d21f4D61fe9543Bd78F3326048'

// Optimism
export const CONSUMER_CHAIN_URL=https://optimism.llamarpc.com
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x152bB09cE15f1149dBE46EB9E4D4e1B572513453'

// Unichain
export const CONSUMER_CHAIN_URL=https://mainnet.unichain.org
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x9d1F623d8708f950C266b422D5Bffa5E244A08e1'
typescript

Testnet

// Base Sepolia
export const CONSUMER_CHAIN_URL=https://sepolia.base.org
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0xD87f5Ea40C592DfFAe5B87922E1cdA2bb44CB67F'

// Ethereum Sepolia
export const CONSUMER_CHAIN_URL=https://endpoints.omniatech.io/v1/eth/sepolia/public
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0xCc936bE977BeDb5140C5584d8B6043C9068622A6'

// Linea Sepolia
export const CONSUMER_CHAIN_URL=https://linea-sepolia-rpc.publicnode.com
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x2c84370DaddBcD67d729689671A9Fe63DF39Cf13'

// Optimism Sepolia
export const CONSUMER_CHAIN_URL=https://sepolia.optimism.io
export const CONSUMER_CHAIN_CONTRACT_ADDRESS = '0x20A5DCE3646BD975edEE3082319bd0dB64A0e0B9'
typescript
async function publishGasEstimation(provider: any) {
    try {
        const { ethers } = await loadEthers()
        const ethersProvider = new ethers.BrowserProvider(provider, 'any')
        const signer = await ethersProvider.getSigner()

        const consumerContract = new ethers.Contract(contractAddress, abi, signer)

        let transaction = await consumerContract.storeValues(payload)

        const receipt = await transaction.wait()
        console.info('Publication success:', receipt.hash)
    } catch (error) {
        const revertErrorFromGasNetContract = (error as any)?.info?.error?.message
        console.error('Publication error:', error)
    }
}
typescript

At this point the estimates are available on the Consumer Chain for use by other contracts on that chain. You can find example code to update the estimate on the Consumer Chain using an injected provider (i.e. wallet) in the demo dapp .

Parse GasNet Estimation Data

In order to minimize gas usage and network transport when updating the oracle, the estimation data is binary encoded. The payload contains a signed header and a set of values.

The payload starts with a 32 byte header, a set of 32 byte type-value pairs or records, followed by a 65 byte signature. All the values are written in BigEndian byte order.

payload = header + (record * n) + sig( keccak256(header + (values * n)) )

This payload format is visualized using Mermaid below, where the first row indicates each byte. You can copy/paste the code into the Mermaid playground at https://www.mermaidchart.com/play to zoom into details.

block-beta
  columns 32
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
  23 24 25 26 27 28 29 30 31 32
	
	a[" Empty "]:6
	length["Length: uint16"]:2
	timestamp["Timestamp: uint48"]:6
  sid["System ID: uint8"]:1
  chid["Chain ID: uint64"]:8
  Height["Height: uint64"]:8
  Version["Version: uint8"]:1
  
  rec1typ["Record 1 Type"]:2
  rec1val["Record 1 Value"]:30
  rec2typ["Record 2 Type"]:2
  rec2val["Record 2 Value"]:30
  rec3typ["Record 3 Type"]:2
  rec3val["Record 3 Value"]:30
  
  sigr["Signature - part R"]:32
  sigs["Signature - part S"]:32
  sigv["Signature - part V"]:1
mermaid

Header

Each payload begins with a 32 byte header with the following structure specified right to left byte order (See “starting position).

NameTypeSizeStarting PositionDescription
1Versionuint8132The version of the entire payload. This document describes the second version of payload
2Heightuint64831The height, or blocknumber, of the estimated chain at time of estimate.
3ChainIDuint64823The chain id of the estimated chain. For EVM chains, this is the EIP-155 id.
4SystemIDuint8115The system id of the estiimate chain. For EVM chains, this value is 2 in decimal. Non-EVM chains will have other system ids (e.g. BTC is 1).
5Timestampuint48614The unix millisecond timestamp when the estimate was determined.
6Lengthuint1628The count of records, or type/value pairs, in the payload
7Empty6Not used but reserved for future expansion.

Record(s)

Each payload contains 1 or more 32 bytes records containing the values of the estimate

NameTypeSizeStarting PositionDescription
1Valueuint240300The value is the value of the record indicated by type number
2Typeuint16231The type describing what the value represents. It is specific to chain + system mixture. Some currently deployed types are listed in the next table.

Currently Used Types

The following types are currently supported. This list will be updated as more types are defined and supported on GasNet.

TypeIDNameTypeSystemsChains
107base_fee_per_gasNumber21
112blob_base_fee_per_gasNumber21
322pred_max_priority_fee_per_gas_p90Number21

The following sample code will unpack and parse the above format into its constituent parts and return as an OraclePayload with one or more OracleRecord items defined above.

function parsePayload(payload: string): OraclePayload {
		let pV = {} as OraclePayload
		const buf = Buffer.from(payload.replace('0x', ''), 'hex')
		pV.height = buf.readBigUInt64BE(0x17)
		pV.chainid = buf.readBigUInt64BE(0xf)
		pV.systemid = buf.readUint8(0xe)

		let timeBuff = Buffer.alloc(8)
		buf.copy(timeBuff, 2, 0x8, 0xe)
		pV.timestamp = timeBuff.readBigUInt64BE(0)
		let pLen = buf.readUint16BE(0x6)

		pV.payloads = new Array()
		let value = Buffer.alloc(0x20)
		let pos = 0

		for (let i = 0; i < pLen; i++) {
			pos += 0x20 // also, skip header
			buf.copy(value, 2, pos + 0x2)
			pV.payloads.push({
				typ: buf.readUint16BE(pos),
				value: '0x' + value.toString('hex')
			} as OracleRecord)
		}

		// ... and signature
		return pV
	
typescript

Read Consumer Chain Oracle

Sample code calling the Oracle to read a gas estimate using ethers in Typescript:

async function handleOracleValues(gasNetContract: Contract) {
    try {
        // get base fee (107), blob base fee (112), and p90 max priority fee (322)
        const valuesObject = await ([107, 112, 322]
        ).reduce(async (accPromise, typ) => {
            const acc = await accPromise
            const contractRespPerType = await gasNetContract.getInTime(
                '2', // evm architecture
                '1', // Ethereum mainnet
                typ,
                3600000 // recency in ms
            )

            const [value, blockNumber, estTimestamp] = contractRespPerType
            
            // A value of 0 inidicates no data available
            if (value === 0n && height === 0n && timestamp === 0n) {
                throw (`Estimate not available at selected recency`
            }

            return {
                chainId,
                estTimestamp,
                blockNumber,
                value: (Number(value) / 1e9).toPrecision(4)
            }
        }, Promise.resolve({}))
        return (valuesObject)
    } catch (error) {
        console.error('Error Reading V2 Published Data:', error)
        const revertErrorFromContract = (error as any)?.info?.error?.message || (error as any)?.reason
        readFromTargetNetErrorMessage = revertErrorFromContract || (error as string)
        return null
    }
}

async function readFromOracle(provider: any) {
		try {
			const { ethers } = await loadEthers()
			const ethersProvider = new ethers.BrowserProvider(provider, 'any')
			const signer = await ethersProvider.getSigner()

			const gasNetContract = new ethers.Contract(
				CONSUMER_CHAIN_CONTRACT_ADDRESS, abi, signer
			)

			publishedGasData = handleOracleValues(gasNetContract)
			
		} catch (error) {
			console.error('Gas data read published data error:', error)
			const revertErrorFromContract = (error as any)?.info?.error?.message || (error as any)?.reason
		}
	}
typescript