Usage Examples
Intro
Using the Chunk oracle in a smart contract is straightforward. The examples below demonstrate how to obtain the prices of ETH and BTC on Arbitrum chain. The list of supported assets on each chain can be found on the corresponding network's page here: https://explorer.chunknet.org/.
Main Concepts
Proxies
Contract development is ongoing, and new versions of the oracle contract may be deployed. To avoid changing the oracle contract address with each deployment, consumers can use the address of a proxy contract, which shares the same interface ICoreMultidataFeedsReader as the oracle contract.
After the deployment of a new version of the oracle contract (and if the new version is backward compatible) implementation of the proxy is changed and migration for proxy users is seamless.
Base 2**112 for Prices
All prices are stored with a 2**112 multiplier, allowing for the use of fractional values. For example:
- 0.01 is stored as
0.01 * 2**112=51922968585348276285304963292200 - 100 is stored as
100 * 2**112=519229685853482762853049632922009600
Examples in Popular Languages
Solidity
To incorporate oracle prices in your smart contract, you should use the ICoreMultidataFeedsReader interface. List of the deployed oracles contracts at this page.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/ICoreMultidataFeedsReader.sol";
contract DataConsumer {
ICoreMultidataFeedsReader internal oracle;
/**
* network: Arbitrum
* oracle address: 0x11da6b6e5D62F0DC798933A926019e70Cbe5aB9a
*/
constructor() {
oracle = ICoreMultidataFeedsReader(0x11da6b6e5D62F0DC798933A926019e70Cbe5aB9a);
}
/**
* Returns prices of `ETH` and `BTC` in USD
*/
function getLatestPrices() public view returns (uint[2] memory) {
string[] memory metrics = new string[](2);
metrics[0] = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
metrics[1] = "BTC";
ICoreMultidataFeedsReader.Quote[] memory values = oracle.quoteMetrics(metrics);
return [
values[0].value / 2**112,
values[1].value / 2**112
];
}
}
JavaScript
web3
This example uses web3.js to fetch prices of ETH and BTC on the Arbitrum chain.
const Web3 = require("web3")
const ORACLE_ADDR = '0x11da6b6e5D62F0DC798933A926019e70Cbe5aB9a';
const RPC_URL = "https://arb1.arbitrum.io/rpc";
const ASSETS = [
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
'BTC',
]
const oracleReaderABI = [{"inputs":[{"internalType":"string[]","name":"names_","type":"string[]"}],"name":"quoteMetrics","outputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint32","name":"updateTS","type":"uint32"}],"internalType":"struct ICoreMultidataFeedsReader.Quote[]","name":"quotes","type":"tuple[]"}],"stateMutability":"view","type":"function"}]
const web3 = new Web3(RPC_URL)
const oracle = new web3.eth.Contract(oracleReaderABI, ORACLE_ADDR)
oracle.methods.quoteMetrics(ASSETS)
.call()
.then((prices) => {
// handle code
console.log("Latest prices", prices)
})
ethers.js
This example uses ethers.js to fetch prices of ETH and BTC on the Arbitrum chain.
const { ethers } = require("ethers")
const ORACLE_ADDR = '0x11da6b6e5D62F0DC798933A926019e70Cbe5aB9a';
const RPC_URL = "https://arb1.arbitrum.io/rpc";
const ASSETS = [
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
'BTC',
]
const oracleReaderABI = [{"inputs":[{"internalType":"string[]","name":"names_","type":"string[]"}],"name":"quoteMetrics","outputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint32","name":"updateTS","type":"uint32"}],"internalType":"struct ICoreMultidataFeedsReader.Quote[]","name":"quotes","type":"tuple[]"}],"stateMutability":"view","type":"function"}]
const provider = new ethers.providers.JsonRpcProvider(RPC_URL)
const oracle = new ethers.Contract(ORACLE_ADDR, oracleReaderABI, provider)
oracle.quoteMetrics(ASSETS)
.then((prices) => {
// handle code
console.log("Latest prices", prices)
})
Python
This example uses web3.py to fetch prices of ETH and BTC on the Arbitrum chain.
from web3 import Web3
ORACLE_ADDR = '0x11da6b6e5D62F0DC798933A926019e70Cbe5aB9a'
RPC_URL = "https://arb1.arbitrum.io/rpc"
ASSETS = [
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
'BTC',
]
ORACLE_READER_ABI = [{"inputs":[{"internalType":"string[]","name":"names_","type":"string[]"}],"name":"quoteMetrics","outputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint32","name":"updateTS","type":"uint32"}],"internalType":"struct ICoreMultidataFeedsReader.Quote[]","name":"quotes","type":"tuple[]"}],"stateMutability":"view","type":"function"}]
web3 = Web3(Web3.HTTPProvider(RPC_URL))
contract = web3.eth.contract(address=ORACLE_ADDR, abi=ORACLE_READER_ABI)
latestPrices = contract.functions.quoteMetrics(ASSETS).call()
print(latestPrices)
For Chainlink Users
We provide a Chainlink-compatible aggregator IChainlinkAggregatorV2V3Interface for each quoted asset, enabling access to the asset's latest price. The list of the deployed aggregators available at this page.
See below an example of getting prices for ETH and USDC with Chainlink compatible aggregators in the Base chain.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/IChainlinkAggregatorV2V3Interface.sol";
contract DataConsumerChainlinkCompatibility {
IChainlinkAggregatorV2V3Interface internal aggregatorETH;
IChainlinkAggregatorV2V3Interface internal aggregatorUSDC;
/**
* network: Gnosis
*/
constructor() {
aggregatorETH = IChainlinkAggregatorV2V3Interface(0x5fd8AdE1c5535a4673DE49767A31B66DC7071154);
aggregatorUSDC = IChainlinkAggregatorV2V3Interface(0xcB1287Fd3De3dc7edd5E159971C92673BB7d6b85);
}
/**
* Returns prices of `ETH` and `USDC` in USD
*/
function getLatestPrices() public view returns (uint[2] memory) {
(,int priceETH,,,) = aggregatorETH.latestRoundData();
(,int priceUSDC,,,) = aggregatorUSDC.latestRoundData();
return [
uint(priceETH) / 10**8,
uint(priceUSDC) / 10**8
];
}
}
Proof Oracle on-demand update
Here is code snippets for copying updates from main Median oracle deployed on Chunk chain to depended Proofs contract at the Arbitrum One chain. The list of the deployed proofs available at this page.
Python
import logging
import time
from functools import cached_property
from merkle_tree.standard import StandardMerkleTree # https://github.com/chunknet/python-merkle-tree
from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import Primitives, HexStr
from eth_utils import to_bytes
from web3 import Web3, HTTPProvider
from web3.contract import Contract
from web3.types import BlockData
COMMON_FEEDS_READER_ABI = [
{
"name": "quoteMetrics",
"type": "function",
"stateMutability": "view",
"inputs": [{"name": "names_", "type": "string[]"}],
"outputs": [{
"name": "quotes",
"type": "tuple[]",
"components": [
{"name": "value", "type": "uint256"},
{"name": "updateTS", "type": "uint32"},
],
}],
},
{
"name": "getMetrics",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{
"type": "tuple[]",
"components": [
{"name": "name", "type": "string"},
{"name": "description", "type": "string"},
{"name": "currency", "type": "string"},
{"name": "tags", "type": "string[]"},
],
}],
},
]
MEDIAN_ABI = [
*COMMON_FEEDS_READER_ABI,
{
"name": "getSignedMerkleTreeRoot",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{
"type": "tuple",
"components": [
{"name": "epoch", "type": "uint32"},
{"name": "v", "type": "uint8"},
{"name": "r", "type": "bytes32"},
{"name": "s", "type": "bytes32"},
{"name": "root", "type": "bytes32"},
],
}],
},
]
PROOF_ABI = [
*COMMON_FEEDS_READER_ABI,
{
"name": "setValues",
"type": "function",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "signedMerkleTreeRoot_",
"type": "tuple",
"components": [
{"name": "epoch", "type": "uint32"},
{"name": "v", "type": "uint8"},
{"name": "r", "type": "bytes32"},
{"name": "s", "type": "bytes32"},
{"name": "root", "type": "bytes32"},
],
},
{
"name": "data_",
"type": "tuple[]",
"components": [
{"name": "merkleTreeProof", "type": "bytes32[]"},
{
"name": "metricData",
"type": "tuple",
"components": [
{"name": "name", "type": "string"},
{"name": "value", "type": "uint256"},
{"name": "updateTs", "type": "uint32"},
],
},
],
},
],
"outputs": [],
},
]
def to_bytes32(primitive: Primitives = None, hexstr: HexStr = None, text: str = None):
"""
Converts a primitive, hex string or text to bytes32.
"""
bytes = to_bytes(primitive, hexstr, text)
if len(bytes) > 32:
raise ValueError('Length too long for bytes32')
return bytes.rjust(32, b'\0')
class BlockUtils:
"""
Provides methods for block operations (only needed for get_latest_block_before)
"""
class StaleChain(Exception):
pass
def __init__(self, w3: Web3):
self.w3 = w3
def _get_block(self, number: int) -> BlockData:
return self.w3.eth.get_block(number)
def _get_latest_block_number(self) -> int:
return self.w3.eth.block_number
def _get_latest_block(self) -> BlockData:
return self._get_block(self._get_latest_block_number())
@property
def avg_block_time(self) -> int:
"""
Average block time calculated over 100 blocks
"""
latest = self._get_latest_block()
if latest.number == 0:
return 1
back_number = max(latest.number - 100, 0)
return max(
(latest.timestamp - self._get_block(back_number).timestamp)
// (latest.number - back_number),
1,
)
def _get_latest_block_safe(self, timestamp: int) -> BlockData:
latest = self._get_latest_block()
if latest.timestamp < timestamp:
raise BlockUtils.StaleChain()
return latest
def get_latest_block_before(self, timestamp: int) -> BlockData:
"""
Returns the latest block which timestamp <= timestamp
:param timestamp: unix timestamp in seconds
:return: block dict
"""
# Retry loop in case of StaleChain
while True:
try:
latest = self._get_latest_block_safe(timestamp)
break
except BlockUtils.StaleChain:
continue # just re-check
if latest.timestamp == timestamp:
return latest
if latest.number == 0:
raise Exception('Block not found')
upper = latest
lower = self._get_block(0)
if lower.timestamp > timestamp:
raise Exception('Block not found')
if lower.timestamp == timestamp:
return lower
jump_from_upper = True
while True:
assert lower.timestamp < timestamp < upper.timestamp
assert upper.number > lower.number
if upper.number == lower.number + 1:
return lower
if jump_from_upper:
middle_num = upper.number - max((upper.timestamp - timestamp) // self.avg_block_time, 1)
if middle_num <= lower.number:
middle_num = lower.number + 1
else:
middle_num = lower.number + max((timestamp - lower.timestamp) // self.avg_block_time, 1)
if middle_num >= upper.number:
middle_num = upper.number - 1
middle = self._get_block(middle_num)
if middle.timestamp == timestamp:
return middle
elif middle.timestamp > timestamp:
upper = middle
jump_from_upper = True
else:
lower = middle
jump_from_upper = False
class ProofFeedsUpdater:
"""
Metric values updater for ProofFeeds contract.
"""
def __init__(
self,
epoch_duration: int,
median: Contract,
proof: Contract,
signer: LocalAccount,
update_batch_size: int = 50,
):
self._epoch_duration = epoch_duration
self._proof = proof
self._median = median
self._signer = signer
self._median_block_utils = BlockUtils(self._median.w3)
self._update_batch_size = update_batch_size
self._logger = logging.getLogger(str(self))
def _get_values(self, last_epoch_block: int) -> list[tuple[str, int, int]]:
"""
Get the metric values from the MedianFeeds contract.
"""
return [
(metric_name, quote[0], quote[1])
for metric_name, quote in zip(
self._contract_metric_names,
self._median.functions.quoteMetrics(self._contract_metric_names).call(
block_identifier=last_epoch_block
)
)
]
def _calculate_merkle_tree(self, epoch_id, last_epoch_block) -> tuple[StandardMerkleTree, list[list]]:
"""
Calculate the Merkle tree root using the metric values.
"""
values = self._get_values(last_epoch_block)
self._logger.debug(f'Calculate Merkle tree root for {len(values)} values')
leaves: list = [
[epoch_id, metric_name, metric_value, metric_update_ts]
for metric_name, metric_value, metric_update_ts in values
]
merkle_tree = StandardMerkleTree.of(leaves, ['uint32', 'string', 'uint256', 'uint32'])
return merkle_tree, leaves
@cached_property
def _contract_metric_names(self) -> list[str]:
# You can filter to update only the metrics you need.
return [m[0] for m in self._median.functions.getMetrics().call()]
def _get_current_metrics_timestamps(self) -> dict[str, int]:
"""
Returns the latest metrics updates timestamps
"""
current_metrics_names = [m[0] for m in self._proof.functions.getMetrics().call()]
current_metrics_ts = {
name: current_ts
for name, (_, current_ts) in
zip(
current_metrics_names,
self._proof.functions.quoteMetrics(current_metrics_names).call() if current_metrics_names else []
)
}
return current_metrics_ts
def make_update(self) -> int:
signed_merkle_tree_root = self._median.functions.getSignedMerkleTreeRoot().call()
epoch_id = signed_merkle_tree_root[0]
self._logger.info(f'Current median epoch id: {epoch_id} merle tree root: {signed_merkle_tree_root}')
current_metrics_ts = self._get_current_metrics_timestamps()
self._logger.info(f'Current proof timestamps: {current_metrics_ts}')
last_epoch_block = self._median_block_utils.get_latest_block_before(epoch_id + self._epoch_duration)
self._logger.info(
f'Last epoch block: {last_epoch_block.number} (timestamp: {last_epoch_block.timestamp}, epoch: {epoch_id})')
merkle_tree, leaves = self._calculate_merkle_tree(epoch_id, last_epoch_block.number)
for i in range(0, len(leaves), self._update_batch_size):
self._logger.info(f'Send batch with id: {i}')
metric_data = [
(
[to_bytes32(hexstr=p) for p in merkle_tree.get_proof(leaf)],
(
leaf[1], # name
leaf[2], # value
leaf[3] # update ts
)
)
for leaf in leaves[i:i + self._update_batch_size]
if leaf[3] > current_metrics_ts.get(leaf[1], 0) # to prevent STALE_UPDATE
]
if not metric_data:
self._logger.info(f'No updates data for {i} batch')
continue
curried_fn = self._proof.functions.setValues(signed_merkle_tree_root, metric_data)
raw_tx = curried_fn.build_transaction(
{
'from': self._signer.address,
'nonce': self._proof.w3.eth.get_transaction_count(self._signer.address),
# By default, Web3 calculates it as equal to estimated. But in fact, gas may be used up a little more.
# It depends on the order in which the transactions are executed within the epoch.
'gas': int(curried_fn.estimate_gas() * 1.1)
}
)
signed_tx = self._proof.w3.eth.account.sign_transaction(raw_tx, private_key=self._signer.key)
tx_hash = self._proof.w3.eth.send_raw_transaction(signed_tx.raw_transaction)
self._proof.w3.eth.wait_for_transaction_receipt(tx_hash)
self._logger.info(f'Values are set to the ProofFeeds contract for epoch_id: {epoch_id}')
return epoch_id
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
signer = Account.from_key('<Private Key>')
median_w3 = Web3(HTTPProvider('https://rpc.chunknet.org'))
proof_w3 = Web3(HTTPProvider('https://arb1.arbitrum.io/rpc'))
median = median_w3.eth.contract(address='0x78D02A47fA898ffF4B37A9B414Eace5eed3e7fAD', abi=MEDIAN_ABI)
proof = proof_w3.eth.contract(address='0x7796F42B46Fd2E5674f51B49CE91882315226e57', abi=PROOF_ABI)
while True:
updated_epoch_id = ProofFeedsUpdater(
epoch_duration=900,
median=median,
proof=proof,
signer=signer,
update_batch_size=50
).make_update()
logging.info(f'Waiting next epoch')
while median.functions.getSignedMerkleTreeRoot().call()[0] == updated_epoch_id:
time.sleep(60)
JavaScript
const {JsonRpcProvider, Wallet, Contract} = require("ethers");
const {StandardMerkleTree} = require("@openzeppelin/merkle-tree");
const COMMON_FEEDS_READER_ABI = [
{
name: "quoteMetrics",
type: "function",
stateMutability: "view",
inputs: [{name: "names_", type: "string[]"}],
outputs: [{
name: "quotes",
type: "tuple[]",
components: [
{name: "value", type: "uint256"},
{name: "updateTS", type: "uint32"},
],
}],
},
{
name: "getMetrics",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{
type: "tuple[]",
components: [
{name: "name", type: "string"},
{name: "description", type: "string"},
{name: "currency", type: "string"},
{name: "tags", type: "string[]"},
],
}],
},
];
const MEDIAN_ABI = [
...COMMON_FEEDS_READER_ABI,
{
name: "getSignedMerkleTreeRoot",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{
type: "tuple",
components: [
{name: "epoch", type: "uint32"},
{name: "v", type: "uint8"},
{name: "r", type: "bytes32"},
{name: "s", type: "bytes32"},
{name: "root", type: "bytes32"},
],
}],
},
];
const PROOF_ABI = [
...COMMON_FEEDS_READER_ABI,
{
name: "setValues",
type: "function",
stateMutability: "nonpayable",
inputs: [
{
name: "signedMerkleTreeRoot_",
type: "tuple",
components: [
{name: "epoch", type: "uint32"},
{name: "v", type: "uint8"},
{name: "r", type: "bytes32"},
{name: "s", type: "bytes32"},
{name: "root", type: "bytes32"},
],
},
{
name: "data_",
type: "tuple[]",
components: [
{name: "merkleTreeProof", type: "bytes32[]"},
{
name: "metricData",
type: "tuple",
components: [
{name: "name", type: "string"},
{name: "value", type: "uint256"},
{name: "updateTs", type: "uint32"},
],
},
],
},
],
outputs: [],
},
];
class BlockUtils {
constructor(provider) {
this.provider = provider;
this._avgBlockTimeCache = null;
}
async getBlock(number) {
return await this.provider.getBlock(number);
}
async getLatestBlockNumber() {
return await this.provider.getBlockNumber();
}
async getLatestBlock() {
return await this.getBlock(await this.getLatestBlockNumber());
}
async getAvgBlockTime() {
if (this._avgBlockTimeCache !== null)
return this._avgBlockTimeCache;
const latest = await this.getLatestBlock();
if (latest.number === 0) {
this._avgBlockTimeCache = 1;
return 1;
}
const backNumber = Math.max(latest.number - 100, 0);
const backBlock = await this.getBlock(backNumber);
this._avgBlockTimeCache = Math.max(
Math.floor((latest.timestamp - backBlock.timestamp) / (latest.number - backNumber)),
1
);
return this._avgBlockTimeCache;
}
async getLatestBlockSafe(timestamp) {
const latest = await this.getLatestBlock();
if (latest.timestamp < timestamp)
throw new Error('StaleChain');
return latest;
}
async getLatestBlockBefore(timestamp) {
// Retry loop for StaleChain
let latest;
while (true) {
try {
latest = await this.getLatestBlockSafe(timestamp);
break;
} catch (err) {
if (err.message === 'StaleChain') {
continue;
}
throw err;
}
}
if (latest.timestamp === timestamp) return latest;
if (latest.number === 0) throw new Error('Block not found');
let upper = latest;
let lower = await this.getBlock(0);
if (lower.timestamp > timestamp) throw new Error('Block not found');
if (lower.timestamp === timestamp) return lower;
let jumpFromUpper = true;
while (true) {
if (upper.number === lower.number + 1) return lower;
const avgBlockTime = await this.getAvgBlockTime();
let middleNum;
if (jumpFromUpper) {
middleNum = upper.number - Math.max(
Math.floor((upper.timestamp - timestamp) / avgBlockTime),
1
);
if (middleNum <= lower.number) middleNum = lower.number + 1;
} else {
middleNum = lower.number + Math.max(
Math.floor((timestamp - lower.timestamp) / avgBlockTime),
1
);
if (middleNum >= upper.number) middleNum = upper.number - 1;
}
const middle = await this.getBlock(middleNum);
if (middle.timestamp === timestamp) return middle;
else if (middle.timestamp > timestamp) {
upper = middle;
jumpFromUpper = true;
} else {
lower = middle;
jumpFromUpper = false;
}
}
}
}
class ProofFeedsUpdater {
constructor({
epochDuration,
median,
proof,
updateBatchSize = 50,
}) {
this.epochDuration = epochDuration;
this.median = median;
this.proof = proof;
this.updateBatchSize = updateBatchSize;
this.medianBlockUtils = new BlockUtils(median.runner.provider);
}
async getContractMetricNames() {
return (await this.median.getMetrics()).map(m => m.name);
}
async getValues(lastEpochBlock) {
const metricNames = await this.getContractMetricNames();
const quotes = await this.median.quoteMetrics(metricNames, {blockTag: lastEpochBlock});
return metricNames.map((name, i) => [name, quotes[i].value, Number(quotes[i].updateTS)]);
}
calculateMerkleTree(epochId, values) {
console.log(`Calculating Merkle tree root for ${values.length} values`);
const leaves = values.map((v) => [epochId, ...v]);
const merkleTree = StandardMerkleTree.of(leaves, ['uint32', 'string', 'uint256', 'uint32']);
return {merkleTree, leaves};
}
async getCurrentMetricsTimestamps() {
const currentMetricNames = (await this.proof.getMetrics()).map(m => m.name);
if (currentMetricNames.length === 0) return {}
const quotes = await this.proof.quoteMetrics(currentMetricNames);
const currentMetricsTs = {};
currentMetricNames.forEach((name, i) => {
currentMetricsTs[name] = Number(quotes[i].updateTS);
});
return currentMetricsTs;
}
async getSignedMerkleTreeRoot() {
const r = await this.median.getSignedMerkleTreeRoot();
return {
epoch: Number(r.epoch),
v: Number(r.v),
r: r.r,
s: r.s,
root: r.root
}
}
async makeUpdate() {
const signedMerkleTreeRoot = await this.getSignedMerkleTreeRoot();
const epochId = Number(signedMerkleTreeRoot.epoch);
console.log(`Current median epoch id: ${epochId}, merkle tree root:`, signedMerkleTreeRoot);
const currentMetricsTs = await this.getCurrentMetricsTimestamps();
console.log('Current proof timestamps:', currentMetricsTs);
const lastEpochBlock = await this.medianBlockUtils.getLatestBlockBefore(epochId + this.epochDuration);
console.log(`Last epoch block: ${lastEpochBlock.number} (timestamp: ${lastEpochBlock.timestamp}, epoch: ${epochId})`);
const values = await this.getValues(lastEpochBlock.number);
const {merkleTree, leaves} = this.calculateMerkleTree(epochId, values);
console.log('Calculated Merkle tree:', merkleTree.root);
for (let i = 0; i < leaves.length; i += this.updateBatchSize) {
console.log(`Send batch with id: ${i}`);
const metricData = leaves
.slice(i, i + this.updateBatchSize)
.filter(leaf => leaf[3] > (currentMetricsTs[leaf[1]] || 0))
.map(
leaf => ({
merkleTreeProof: merkleTree.getProof(leaf),
metricData: {
name: leaf[1],
value: leaf[2],
updateTs: leaf[3]
}
})
);
if (metricData.length === 0) {
console.log(`No updates data for ${i} batch`);
continue;
}
// Estimate gas
const estimatedGas = await this.proof.setValues.estimateGas(signedMerkleTreeRoot, metricData);
console.log('Estimated gas:', estimatedGas);
// Add 10% buffer for gas
const gasLimit = (estimatedGas * 110n) / 100n;
// Send transaction
const tx = await this.proof.setValues(signedMerkleTreeRoot, metricData, {gasLimit});
console.log(`Transaction sent: ${tx.hash}`);
await tx.wait();
console.log(`Transaction confirmed: ${tx.hash}`);
}
console.log(`Values are set to the ProofFeeds contract for epoch_id: ${epochId}`);
return epochId;
}
}
async function sleep(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
// Main execution
(async () => {
const EPOCH_DURATION = 900;
const signerKey = '<Private Key>'; // Add your private key here
const signer = new Wallet(signerKey);
const medianProvider = new JsonRpcProvider('https://rpc.chunknet.org');
const proofProvider = new JsonRpcProvider('https://arb1.arbitrum.io/rpc');
const median = new Contract('0x78D02A47fA898ffF4B37A9B414Eace5eed3e7fAD', MEDIAN_ABI, medianProvider);
const proof = new Contract('0x7796F42B46Fd2E5674f51B49CE91882315226e57', PROOF_ABI, signer.connect(proofProvider));
while (true) {
const updater = new ProofFeedsUpdater({
epochDuration: EPOCH_DURATION,
median: median,
proof: proof,
updateBatchSize: 50
});
const updatedEpochId = await updater.makeUpdate();
console.log('Waiting for next epoch');
// Wait until epoch changes
while (Number((await median.getSignedMerkleTreeRoot()).epoch) === updatedEpochId) {
await sleep(60);
}
}
})();