Guidiaz
The Witnet Oracle Blog
28 min readJun 5, 2024

--

What is the Wit/Oracle?

The Wit/Oracle is a public, permissionless, and fully decentralized Proof-of-Stake blockchain that is powered by its own cryptocurrency, the $WIT coin. Also known as the Witnet blockchain, its primary function lies in efficiently providing public data from the Internet into all sorts of smart-contract capable blockchains and Web3 solutions in a secure, traceable and trustworthy manner. Because the Wit/Oracle blockchain nodes are cryptoeconomically incentivized to remain honest, we don’t need to trust that they are telling the truth, but rather be certain that it’s in their best interest to do so.

Data retrieved by the Wit/Oracle blockchain can be claimed to be 100% truthful to the data sources being used.

By means of the so-called Witnet Solidity Bridge (a framework of counter-factual smart contracts), the Witnet Foundation provides a reliable, decentralized and trustworthy oracle solution for a long selection of EVM-compatible chains (see full list here).

It is the Witnet Solidity Bridge that ultimately enables smart contracts to retrieve all sorts of data types from the real world: from a simple integer value to multi-typed arrays, chunks of bytes, or even mapped-values objects. Basically, any data that is publicly accessible on the Internet by means of HTTP-GET, HTTP-POST or even HTTP-HEAD requests, and that can get formally encoded into a CBOR binary buffer (see RFC-8949), can also be brought right into your smart contract, 100% truthful to the data sources being referred.

The Witnet Solidity Bridge can also be used as a source of entropy for unpredictable randomness, in the sense that it cannot be malleated nor front-run by miners of neither the Wit/Oracle or the EVM blockchains.

These are the most relevant features of the Witnet Solidity Bridge that make it stand out from other EVM-compliant oracle solutions:

  1. Data can be parametrically specified from within your smart contracts.
  2. Data can be pulled at any time from and into your smart contracts.
  3. Data traceability is possible: sources, off-chain computations and sampling timestamps about any data reported from the Wit/Oracle can be formally verified.
  4. On-demand resolution of data requests are paid with the EVM’s fee token.
  5. No need to fund your contracts with third-party ERC-20 tokens.
  6. No need to undergo off-chain subscription workflows.
  7. No need to run your own Witnet nodes.

About this article

This article will help you to better understand the value-offer of the Wit/Oracle blockchain solution. You will also learn how to build up and test parameterized Witnet-compliant data requests as to retrieve any sort of data that’s published on the Internet right into your own Solidity contracts.

You will be first introduced to:

The last part of the article contains a hands-on tutorial showing how to solve a very specific use case:

The Witnet-Solidity package

The Witnet-Solidity NPM package bundles all the resources and tooling for you to:

  • Check the chains currently bridged to the Wit/Oracle blockchain.
  • Check EVM addresses of all Witnet-related assets on a specific chain.
  • Check the number of available data sources, templates and requests.
  • Build and test your own parameterized Witnet Data Requests.
  • Deploy your own Witnet-related assets onto a specific chain.
  • Run local ETH/RPC preset gateways adapted to your target EVM chain.
  • Run local Web3 console to interact with your Witnet-based assets.
  • Run the Witnet Solidity Wizard to generate custom contract mock-ups.
  • Pull unmalleable randomness from the Wit/Oracle into your contracts.
  • Freely read from the selection of public price feeds that get periodically updated by the Witnet Foundation.
  • Force price updates under demand from your own smart contracts.
  • Introspect the actual data providers being used by the Witnet Foundation for every price feed update.

Importing this package into your project is as simple as this:

$ mkdir mydapp
$ cd mydapp
$ npm init -y
$ npm install --save-dev witnet-solidity
$ npx witnet init

Usage:

avail => List available resources from Witnet.
check => Check that all Witnet assets are properly declared.
console => Open Truffle console as to interact with Witnet deployed artifacts.
deploy => Deploy Witnet requests and templates defined within the witnet/assets/ folder.
ethrpc => Run a local ETH/RPC gateway to the specified chain.
test => Dry-run requests and templates defined within the witnet/assets/ folder.
wizard => Helps you to integrate the Witnet Oracle within your smart contracts.

Once initialized, you will see that several files were added to your project:

  • .env_witnet: Contains some optional environment variables that may be required when deploying your own Witnet-related artifacts.
  • witnet/addresses.json: Contains addresses of all deployed Witnet-related artifacts specific to your project, grouped by EVM-chain.
  • witnet/_hardhat.config.js: Example config file showing how to include Witnet-related artifacts into your Hardhat scripts.
  • witnet/_truffle.config.js: Same for Truffle migration scripts.
  • witnet/assets/sources.js: Where public REST-APIs, GraphQL endpoints, Cross-chain Procedure Calls and other sorts of public data sources can be declared. Declaration of a data source includes the Witnet Radon computation script to be run each time data is retrieved from that source. Data sources and Witnet Radon computation scripts can be parameterized by using wildcard sub-strings. Up to 10 different indexed parameters can be used within a single data source.
  • witnet/assets/requests.js: Where actual Witnet data requests can be declared as a combination of one or more non-parameterized data sources. Requests can also be declared as instances of data request templates.
  • witnet/assets/templates.js: Data request templates are declared as a combination of one or more data sources where at least one of them is parameterized. Multiple unitary tests can be specified per template.

The WSB contracts framework

The Witnet Solidity Bridge framework (aka. the WSB framework) is a set of inter-related and counter-factual contracts deployed by the Witnet Foundation that binds EVM-compatible chains to the Wit/Oracle blockchain.

From a smart contract developer’s point of view, these are the main artifacts to be aware of:

  • WitnetOracle: From where you can pull data updates (i.e. data queries), estimate required EVM fees and check for data results as resolved by the Wit/Oracle blockchain.
  • WitnetRequestBytecodes: This contract contains a registry of validated data sources, request templates and data requests built up so far by any dapp based on the WSB framework, up to the present time. A unique identifier (i.e. the RAD hash) is generated for every data request that gets validated with this contract. These identifiers are counter-factual to the data sources and off-chain computations they refer to, meaning that data requests in the registry only need to get validated once in their lifetime. Once a data request gets validated into the registry, all the related metadata, like the actual data sources and off-chain computations being specified, or the returning data type and maximum size, can be formally verified on-chain. The RAD hash also allows to track all the times upon which some data request got solved on the Wit/Oracle blockchain.
  • WitnetRequestFactory: Bound to the WitnetRequestBytecodes registry, this contract enables smart contracts to programmatically build WitnetRequestTemplate instances, based on a provided list of parameterized data sources, attestation filters and computation scripts that transform and aggregate the data as extracted from those sources.
  • WitnetRequestTemplate(s) : Built from the WitnetRequestFactory, contracts of this kind contain reference(s) to one or more parameterized data sources. Concrete parameters can be provided on-the-fly from a smart contract, resulting in a fully Witnet-compliant data request upon every usage.
  • WitnetPriceFeeds: Relying on the WitnetOracle, this contract acts as a third-party oracle solution for the provisioning of regularly updated price feeds, where price feeds get identified by a descriptive string tag (see ERC-2362). Unlike most other price feed oracles, price updates can be pulled at anytime by any consuming contract. Moreover, all provided price updates (both values and timestamps) can be proven to be 100% truthful to the public sources being used, with no human-driven workflows involved at all, while providing a 15-minute finalization period in worst-case scenarios.
  • WitnetRandomnessV2: Bound also to the WitnetOracle, this contract provides unpredictable and unmalleable randomness to smart contracts. How to use it is out of the scope of this article.

While you may of course interact on-chain with any of these artifacts, a more convenient way to exploit the capabilities of the Wit/Oracle, in most use cases, would be by extending the mock-up contracts created by the Witnet Solidity Wizard tool which is bundled together with the Witnet-Solidity package.

Tutorial: Build and pull your own custom data feeds

The following tutorial will guide you on how to generally (1) build a custom data feed out from your own selection of public REST APIs, (2) pull a data update from a smart contract, and (3) handle the data as soon as it gets reported from the Wit/Oracle blockchain:

Unlike other blockchain oracle solutions, the Wit/Oracle-based approach shown on this tutorial will set you free to:

a) Decide the actual data providers where to compute data from.

b) Use as many independent data providers as you deem appropriate.

c) Outsource computation complexity to the Wit/Oracle, while keeping very low fees at the EVM level.

Although the Wit/Oracle can cover a wide range of use cases other than “price feeds”, for the sake of simplicity and conciseness, this tutorial will focus on building a Satoshi/Wei price feed: the market price of one Bitcoin “satoshi” quoted in terms of Ethereum “weis”.

Should you be all about price feeds, before reinventing your own, please have a look into the WitnetPriceFeeds available instances, as it might be already supported (and therefore, periodically updated) by the Witnet Foundation. If so, you could then make your contract to either read the last updated price at no cost from the WitnetPriceFeeds contract, or even force a fresh new update upon your contract’s command.

Declaring parameterized data sources

First and rule #1 when building up your own data feeds: «Choose wisely your data sources»

Whenever possible, select your data sources to be:

  • Public. The great strength and versatility of the WSB framework is that it relies on a totally independent, public and permissionless blockchain, which is the Wit/Oracle blockchain. Meaning, that all data requests solved by the Wit/Oracle blockchain can be audited and verified publicly by anyone, anytime. In consequence, be aware that private sources that require a secret to be provided (e.g. an API key), would be publicly exposed at both the EVM and the Witnet Oracle blockchains.

Other oracle solutions claiming to support private APIs actually manage to do so by introducing a considerable level of centralization into your dapp, by forcing you to either: (a) share secrets with a centralized third-party, (b) run your own off-chain infrastructure (upon which the reliability and trustworthiness of your dapp would ultimately rely), or both.

  • Reliable. Because of the permissionless and decentralized nature of the underlying Wit/Oracle blockchain, the reliability and trustworthiness of your dapp will be only limited by those of the data sources being used.
  • Independent. If the selected data sources all depended from a single entity (e.g. CoinMarketCap), it would be easy for that entity to either tamper the provided data or deny data availability when most needed.
  • Numerous. Use cases on the Wit/Oracle can be grouped in two major sets: a) those that leverage multiple sources for data redundancy (all sources are expected to provide exactly the same value), and b), those that leverage multiple sources for data composability. An odd number of sources should be selected for (a) use cases, as a Witnet.Reducers.Mode() reducer operator might possibly apply. For (b) use cases, if results are numbers to be averaged in some way, it normally applies the rule of “the higher the number, the better” (as long as they remain reliable and independent, of course). Otherwise, the number of sources might be ultimately determined by the nature, singularity or complexity of the data to be composed (off-chain) before getting reported into the EVM storage.

As for the goal of building a Satoshi/Wei price feed, let’s follow these recommendations by searching into CoinMarketCap for either BTC/ETH or ETH/BTC markets. Although up to 10 different exchanges supporting the ETH/BTC market can be easily found, we will stick to those that (a) provide a public REST/API, and (b) trade the equivalent to at least 1 M$ per day. As for the day when this tutorial was written:

  • Binance.com → $50,231,720
  • OKX.com → $21,571,539
  • Coinbase.com → $6,779,639
  • Kraken.com → $2,910,010
  • KuCoin.com → $1,129,471

Only when an acceptable number of reliable, public and independent data sources have been selected, you can then start coding.

Knowing the JSON schema returned by every chosen data source is paramount for building an appropriate data retrieval script (i.e. Witnet Radon scripts). The Witnet-Solidity package bundles a Witnet Radon Typescript library supporting code auto-completion, making it really easy and straight-forward to build all sorts of Witnet-related artifacts, including data retrieval scripts.

We must also have in mind:

  • In most of above sources, we’d actually be getting data from ETH/BTC markets, so we’ll need to call the power(-1) Radon operator at some point.
  • We’re willing to get the rounded count of “weis” (1/10¹⁸ ETH) to be paid for getting one single “satoshi” (1/10⁸ BTC), so we actually need to scale BTC/ETH values by 10¹⁸ and then divide by 10⁸, or simply multiply BTC/ETH values by 10¹⁰, and then apply a round() Radon operator at the end.

On all these exchange APIs, both the base (e.g. ETH) and quotation (e.g. BTC) can be expressed some way or another within either the URL, or somewhere within the returned JSON object. As any of these data providers could eventually be used for building other price feeds, we will now declare 5 reusable and parameterized data sources, and their corresponding retrieval scripts within your project’s data sources resource file:

/* File: witnet/assets/sources.js */

const Witnet = require("witnet-toolkit")

module.exports = {
exchanges: {
"binance.com/inversed-ticker#10": Witnet.Sources.HttpGet({
url: "https://api.binance.com/api/v3/ticker/price?symbol=\\0\\\\1\\",
script: Witnet.Script()
.parseJSONMap()
.getFloat("price")
.power(-1)
.multiply(1e10)
.round(),
}),
"coinbase.com/ticker#10": Witnet.Sources.HttpGet({
url: "https://api.coinbase.com/v2/exchange-rates?currency=\\0\\",
script: Witnet.Script()
.parseJSONMap()
.getMap("data")
.getMap("rates")
.getFloat("\\1\\")
.multiply(1e10)
.round(),
}),
"kraken.com/inversed-ticker#10": Witnet.Sources.HttpGet({
url: "https://api.kraken.com/0/public/Ticker?pair=\\0\\\\1\\",
script: Witnet.Script()
.parseJSONMap()
.getMap("result")
.values()
.getMap(0)
.getArray("a")
.getFloat(0)
.power(-1)
.multiply(1e10)
.round(),
}),
"kucoin.com/inversed-ticker#10": Witnet.Sources.HttpGet({
url: "https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=\\0\\-\\1\\",
script: Witnet.Script()
.parseJSONMap()
.getMap("data")
.getFloat("price")
.power(-1)
.multiply(1e10)
.round(),
}),
"okx.com/ticker#10": Witnet.Sources.HttpGet({
url: "https://www.okx.com/api/v5/market/ticker?instId=\\0\\-\\1\\",
script: Witnet.Script()
.parseJSONMap()
.getArray("data")
.getMap(0)
.getFloat("last")
.multiply(1e10)
.round(),
}),
}
};

You’re advised to type the above lines by hand (instead of just copy-pasting it), or at least a part of them, so you can see in action the auto-completion feature of the bundled Witnet-Toolkit typescript module. This way you can play around and learn about all Radon operators at hand depending on the retrieved data type being processed on each step, as well as other pre-built factory methods for declaring other sort of data sources, like GraphQL endpoints, HTTP-POST or HTTP-HEAD requests, or even Cross Chain Data Requests to external EVM chains (or even the Wit/Oracle blockchain itself ;-).

As you can see on the example above, indexed parameters within Witnet Data Sources are referred by using the \#\ wildcard string, where # can be any ASCII character between 0 (first parameter) and 9.

Wildcard strings can be potentially used on either URLs, HTTP request bodies, HTTP request headers, and Radon operator’s string parameters.

Once all data sources get added to the assets file, let’s run the npx witnet check command just to validate that we incurred in no syntax typos. You would see a description message and stack trace in case you typed something wrong. Otherwise, you should get:

$ npx witnet check

WITNET ASSETS
-------------
> Data sources: 5
> Data requests: 0
> Request templates: 0

All assets checked successfully!

Building up parameterized data requests

You first need to have a general overview on the multi-stage and multi-phase workflow followed by the witnessing nodes on the Wit/Oracle blockchain that contribute to resolve the Witnet data requests that get eventually posted to a WitnetOracle contract:

  • RETRIEVE STAGE: every eligible witnessing node (randomly selected) will retrieve data for every single source referred within the data request, and will then run the corresponding Radon retrieval script.
  • AGGREGATE STAGE: every witnessing node will then compute the aggregate reductor function (and potentially one or more aggregate filters, as well) by using as input each piece of data as computed from all referred data sources. If an Aggregate Filter is specified, all computed values not passing such filter will be excluded before actually computing the aggregate reductor function.

Aggregate filters might therefore be used to filter out data sources that unexpectedly provide some value that deviates too much from the average of values provided by the rest of data sources. So, in the case of price feeds use case, using multiple public data sources not only provides an extra of trustworthiness on the final value, but also an extra on reliability in case one or even more data providers misbehaved (or malfunctioned) in any way.

  • COMMIT-PHASE TXS: Once data gets aggregated by witnessing nodes, the witnessing nodes will then hash their values before broadcasting DR COMMIT transacitons to the Wit/Oracle blockchain, plus a collateral.
  • REVEAL-PHASE TXS: Once witnessing nodes get awareness of all required DR COMMIT transactions got successfully mined, they will then broadcast the actually computed value at the AGGREGATE stage.
  • TALLY STAGE: Once all DR REVEAL transactions get eventually mined, the rules of the Wit/Oracle blockchain will force mining nodes to include a DR TALLY transaction that contains the result of applying the tally reducer function (and potentially one or more tally filters) to the values actually revealed by every contributing witnessing node.

If at least one Tally filter is specified, witnesses whose revealed values get filtered out will be considered to be outliers and therefore get slashed by losing a 100x times the reward they’d earn by keeping true (or at least close enough, depending on the use case) to what a majority of other witnesses revealed.

Deciding on Aggregate reducers and filters

When building data requests for price feeds, where some currency pair price (either a float or an integer) is fetched from multiple exchanges, it is wise to use a Witnet.Reducers.Mean() aggregate reducer operator. A Witnet.Reducers.Median() would also be okay.

But then, when trying to reduce numeric values from multiple sources, you’re strongly encouraged to also set some aggregate filter. As it’s pretty common that different exchanges provide different prices for the same market, it is wise to include a parameterized standard deviation filter: Witnet.Filters.Stdev(_stdev). This way we make sure that if any exchange eventually provides a price value that deviates way too much from the mean average of the other exchanges, it would then just get ignored.

A good enough aggregate deviation threshold on price feeds with at least three independent data sources has been proven to be √2, or just 1.4 for the short.

Deciding on Tally reducers and filters

You may be wondering: if all witnesses rely on the same set of data sources and retrieval scripts, why bother to reduce or filter revealed values at all?

Well, it indeed happens that revealed values can differ among each other, for multiple reasons:

  • Price markets can be highly volatile on certain occasions, so witnessing nodes retrieving data a few hundred milliseconds after others may actually lead to different aggregated results.
  • If a data request is set to require a large witnessing committee (up to 127), it may happen that some data providers may refrain from attending all witnesses at the same time, and again, provoking different witnesses to produce different aggregated results.
  • And even though revealers can get highly penalized if they are proven to be false with respect a majority of other revealers, there is still a chance for eventual malicious actors to take the risk.

In the context of numeric data feeds (where numeric values can potentially be interpolated), we basically have three options depending on how harsh we decide to be with eventual outliers:

  • Mild: just calculate the statistical mode out from all revealed values (no body gets slashed, but yet you get the most frequently revealed value). E.g.: Witnet.Reducers.Mode()
  • Just: slash revealers providing values that deviate “too much” from the average and then calculate some mean average on all passing values. E.g.: Witnet.Reducers.Mean(Witnet.Filters.Stdev(2.5))
  • Harsh: slash revealers providing values not matching the statistical mode, and then calculate the mode (unfair to witnesses revealing valid aggregated values that slightly differ from the general mode). E.g.: Witnet.Reducers.Mode(Witnet.Filters.Mode())

Let’s follow the “mild” approach for now, so we can keep it simple, and keep on coding as well.

Declaring Witnet data requests

There are multiple ways to declare your own custom data requests:

  • Witnet.StaticRequest(..): only non-parameterized data sources can be referred.
  • Witnet.RequestFromTemplate(..): a Witnet request template definition has to be referred, together with all required parameters for all involved data sources.
  • Witnet.RequestFromDictionary(..): data sources are referred by name and specific parameter values are set for those sources that require them.

As we have declared all data sources within the corresponding sources asset file, let’s declare the WitnetRequestPriceSatoshiWei artifact by adding these lines to the data requests asset file within your project:

/* File: witnet/assets/requests.js */

const Witnet = require("witnet-toolkit")
const sources = Witnet.Dictionary(
Witnet.Sources.Class,
require("./sources")
)
module.exports = {
WitnetRequestPriceSatoshiWei: Witnet.RequestFromDictionary({
retrieve: {
dict: sources,
tags: {
"binance.com/inversed-ticker#10": [ "ETH", "BTC" ],
"coinbase.com/ticker#10": [ "BTC", "ETH" ],
"kraken.com/inversed-ticker#10": [ "ETH", "BTC" ],
"kucoin.com/inversed-ticker#10": [ "ETH", "BTC" ],
"okx.com/ticker#10": [ "BTC", "ETH" ],
}
},
aggregate: Witnet.Reducers.Median(Witnet.Filters.Stdev(1.4)),
tally: Witnet.Reducers.Mode(),
}),
};

Just like we did after declaring data sources, let’s again make sure that we typed everything correctly. If so, you should now see:

$ npx witnet check

WITNET ASSETS
-------------
> Data sources: 5
> Data requests: 1
> Request templates: 0

All assets checked successfully!

Dry-running data requests

Now that we have finally declared what we believe to be a sound data request, let’s test it!

By using the bundled tester in the Witnet-Solidity package, we’ll actually be testing:

  • The process by which all referred data sources, Radon scripts, aggregate and tally reducers and filters will get validated and registered into the WitnetRequestBytecodes registry contract.
  • Reproduce the workflow that any eventual witnessing node in the Wit/Oracle blockchain would follow when solving this data request.

After running a dry-run, we’ll always get a summary report, as well a list of failing issues, if any. In the source code above there’s a bug that was introduced on purpose. Let’s see how to debug it, and fix it.

$ npx witnet test
...
====================================================================================================================================================
> REQUESTS DRY-RUNS:
┌─────────┬────────────────────────────────┬────────────────────────────────────────────────────────────────────┬────────┬───────────┬───────────┬─────────────┬──────────────────────────────────┐
│ (index) │ Artifact │ RAD hash │ Status │ ✓ Sources │ ∑ Sources │ Time (secs) │ Result │
├─────────┼────────────────────────────────┼────────────────────────────────────────────────────────────────────┼────────┼───────────┼───────────┼─────────────┼──────────────────────────────────┤
│ 0 │ 'WitnetRequestPriceSatoshiWei' │ '1338d36e3fef235c1b88f180b9f765956c4c7b21511601234b328386a8954a3c' │ 'WARN' │ 4 │ 5 │ 0.001 │ { RadonInteger: '204855067191' } │
└─────────┴────────────────────────────────┴────────────────────────────────────────────────────────────────────┴────────┴───────────┴───────────┴─────────────┴──────────────────────────────────┘
1 passing (2s)
1 failing
1) Contract: witnet-solidity/requests
My Witnet Requests...
WitnetRequestPriceSatoshiWei
request dryruns successfully:
Error: Source #5: Failed to get item at index `0` from RadonArray
at Context.<anonymous> (node_modules\witnet-solidity\witnet\tests\truffle\witnet.requests.spec.js:59:23)
at processTicksAndRejections (node:internal/process/task_queues:95:5)

So indeed, there are multiple pieces of interesting info that we can infer from this experiment:

  • The unique RAD hash that identifies the request on the Wit/Oracle.
  • Something went wrong when trying to retrieve data from the fifth data source (i.e. OKX).
  • Even though one out of five data sources failed, the data request was still resolved with an integer value: 204855067191.

By introducing some verbosity to the test, we’ll be able to debug what’s actually going on with the OKX data source:

$ npx witnet test --verbose
...
│ └─[ Source #5 (www.okx.com) ]
│ Method: HTTP-GET
│ Complete URL: https://www.okx.com/api/v5/market/ticker?instId=BTC-ETH
│ Number of executed operators: 3
│ Execution time: 0.0269 ms
│ Execution trace:
│ [0] HTTP-GET -> String: "{\"code\":\"51001\",\"msg\":\"Instrument ID does not exist\",\"data\":[]}"
│ [1] .parseJSONMap() -> Map: {"code":{"RadonString":"51001"},"data":{"RadonArray":[]},"msg":{"RadonString":"Instrument ID does not exist"}}
│ [2] .getArray(data) -> Array: []
│ [3] .getMap(0) -> Error: "Failed to get item at index `0` from RadonArray"
│ Execution result: Error: "Failed to get item at index `0` from RadonArray"
...

Oops! We wrongly presumed that OKX supported the BTC/ETH market, but it does not. It does support the ETH/BTC market, though.

We will need to change both the Radon script, and possibly the way parameters are passed to the OKX data source when declaring WitnetRequestPriceSatoshiWei artifact. However, let’s just change the later and see what we’d get:

/* File: witnet/assets/requests.js */
...
"okx.com/ticker#10": [ "ETH", "BTC" ],
...
$ npx witnet test --verbose
...
│ └─[ Source #5 (www.okx.com) ]
│ Method: HTTP-GET
│ Complete URL: https://www.okx.com/api/v5/market/ticker?instId=ETH-BTC
│ Number of executed operators: 6
│ Execution time: 0.0734 ms
│ Execution trace:
│ [0] HTTP-GET -> String: "{\"code\":\"0\",\"msg\":\"\",\"data\":[{\"instType\":\"SPOT\",\"instId\":\"ETH-BTC\",\"last\":\"0.04886\",\"lastSz\":\"2.500374\",\"askPx\":\"0.04887\",\"askSz\":\"18.14519\",\"bidPx\":\"0.04886\",\"bidSz\":\"6.41579\",\"open24h\":\"0.0479\",\"high24h\":\"0.04887\",\"low24h\":\"0.04781\",\"volCcy24h\":\"355.2290508263\",\"vol24h\":\"7335.683697\",\"ts\":\"1713948961807\",\"sodUtc0\":\"0.04848\",\"sodUtc8\":\"0.04856\"}]}"
│ [1] .parseJSONMap() -> Map: {"code":{"RadonString":"0"},"data":{"RadonArray":[{"RadonMap":{"askPx":{"RadonString":"0.04887"},"askSz":{"RadonString":"18.14519"},"bidPx":{"RadonString":"0.04886"},"bidSz":{"RadonString":"6.41579"},"high24h":{"RadonString":"0.04887"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.04886"},"lastSz":{"RadonString":"2.500374"},"low24h":{"RadonString":"0.04781"},"open24h":{"RadonString":"0.0479"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713948961807"},"vol24h":{"RadonString":"7335.683697"},"volCcy24h":{"RadonString":"355.2290508263"}}}]},"msg":{"RadonString":""}}
│ [2] .getArray(data) -> Array: [{"RadonMap":{"askPx":{"RadonString":"0.04887"},"askSz":{"RadonString":"18.14519"},"bidPx":{"RadonString":"0.04886"},"bidSz":{"RadonString":"6.41579"},"high24h":{"RadonString":"0.04887"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.04886"},"lastSz":{"RadonString":"2.500374"},"low24h":{"RadonString":"0.04781"},"open24h":{"RadonString":"0.0479"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713948961807"},"vol24h":{"RadonString":"7335.683697"},"volCcy24h":{"RadonString":"355.2290508263"}}}]
│ [3] .getMap(0) -> Map: {"askPx":{"RadonString":"0.04887"},"askSz":{"RadonString":"18.14519"},"bidPx":{"RadonString":"0.04886"},"bidSz":{"RadonString":"6.41579"},"high24h":{"RadonString":"0.04887"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.04886"},"lastSz":{"RadonString":"2.500374"},"low24h":{"RadonString":"0.04781"},"open24h":{"RadonString":"0.0479"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713948961807"},"vol24h":{"RadonString":"7335.683697"},"volCcy24h":{"RadonString":"355.2290508263"}}
│ [4] .getFloat(last) -> Float: 0.04886
│ [5] .multiply(10000000000) -> Float: 488600000
│ [6] .round() -> Integer: 488600000
│ Execution result: Integer: 488600000

│ ┌────────────────────────────────────────────────┐
├──┤ Aggregate stage │
│ ├────────────────────────────────────────────────┤
│ │ Execution time: 0.0541 ms │
│ │ Result is: Integer: 204666393778 │
│ └────────────────────────────────────────────────┘
...

This we can see:

  • The five sources seem to work.
  • But the OKX source is producing a value that actually deviates quite much from the others.
  • Because we’re using a Witnet.Filters.Stdev(1.4) filter at the Aggregate stage, the OKX value is being ignored, so it does not affect the aggregated average, at all.

We can still do better, right? So let’s refactor the source name and add the missing power(-1) operator on the Radon script for the OKX data source:

/* File: witnet/assets/sources.js */
...
"okx.com/inversed-ticker#10": Witnet.Sources.HttpGet({
url: "https://www.okx.com/api/v5/market/ticker?instId=\\0\\-\\1\\",
script: Witnet.Script()
.parseJSONMap()
.getArray("data")
.getMap(0)
.getFloat("last")
.power(-1)
.multiply(1e10)
.round(),
}),
...
/* File: witnet/assets/requests.js */
...
"okx.com/inversed-ticker#10": [ "ETH", "BTC" ],
...
$ npx witnet test --verbose
...
│ └─[ Source #5 (www.okx.com) ]
│ Method: HTTP-GET
│ Complete URL: https://www.okx.com/api/v5/market/ticker?instId=ETH-BTC
│ Number of executed operators: 7
│ Execution time: 0.0626 ms
│ Execution trace:
│ [0] HTTP-GET -> String: "{\"code\":\"0\",\"msg\":\"\",\"data\":[{\"instType\":\"SPOT\",\"instId\":\"ETH-BTC\",\"last\":\"0.0489\",\"lastSz\":\"0.322699\",\"askPx\":\"0.0489\",\"askSz\":\"6.280634\",\"bidPx\":\"0.04889\",\"bidSz\":\"13.717913\",\"open24h\":\"0.04783\",\"high24h\":\"0.0489\",\"low24h\":\"0.04783\",\"volCcy24h\":\"342.95522962302\",\"vol24h\":\"7077.231565\",\"ts\":\"1713950099207\",\"sodUtc0\":\"0.04848\",\"sodUtc8\":\"0.04856\"}]}"
│ [1] .parseJSONMap() -> Map: {"code":{"RadonString":"0"},"data":{"RadonArray":[{"RadonMap":{"askPx":{"RadonString":"0.0489"},"askSz":{"RadonString":"6.280634"},"bidPx":{"RadonString":"0.04889"},"bidSz":{"RadonString":"13.717913"},"high24h":{"RadonString":"0.0489"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.0489"},"lastSz":{"RadonString":"0.322699"},"low24h":{"RadonString":"0.04783"},"open24h":{"RadonString":"0.04783"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713950099207"},"vol24h":{"RadonString":"7077.231565"},"volCcy24h":{"RadonString":"342.95522962302"}}}]},"msg":{"RadonString":""}}
│ [2] .getArray(data) -> Array: [{"RadonMap":{"askPx":{"RadonString":"0.0489"},"askSz":{"RadonString":"6.280634"},"bidPx":{"RadonString":"0.04889"},"bidSz":{"RadonString":"13.717913"},"high24h":{"RadonString":"0.0489"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.0489"},"lastSz":{"RadonString":"0.322699"},"low24h":{"RadonString":"0.04783"},"open24h":{"RadonString":"0.04783"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713950099207"},"vol24h":{"RadonString":"7077.231565"},"volCcy24h":{"RadonString":"342.95522962302"}}}]
│ [3] .getMap(0) -> Map: {"askPx":{"RadonString":"0.0489"},"askSz":{"RadonString":"6.280634"},"bidPx":{"RadonString":"0.04889"},"bidSz":{"RadonString":"13.717913"},"high24h":{"RadonString":"0.0489"},"instId":{"RadonString":"ETH-BTC"},"instType":{"RadonString":"SPOT"},"last":{"RadonString":"0.0489"},"lastSz":{"RadonString":"0.322699"},"low24h":{"RadonString":"0.04783"},"open24h":{"RadonString":"0.04783"},"sodUtc0":{"RadonString":"0.04848"},"sodUtc8":{"RadonString":"0.04856"},"ts":{"RadonString":"1713950099207"},"vol24h":{"RadonString":"7077.231565"},"volCcy24h":{"RadonString":"342.95522962302"}}
│ [4] .getFloat(last) -> Float: 0.0489
│ [5] .power(-1) -> Float: 20.449897750511248
│ [6] .multiply(10000000000) -> Float: 204498977505.1125
│ [7] .round() -> Integer: 204498977505
│ Execution result: Integer: 204498977505

│ ┌────────────────────────────────────────────────┐
├──┤ Aggregate stage │
│ ├────────────────────────────────────────────────┤
│ │ Execution time: 0.0294 ms │
│ │ Result is: Integer: 204540814448 │
│ └────────────────────────────────────────────────┘
...

Awesome! We got it!!

Deploying data requests

The Witnet-Solidity package bundles two very handy tools for the purpose of deploying data request artifacts and also data request templates:

  • An ETH/RPC local gateway, preset with all EVM chains (both testnets and mainnets) currently bridged to the Witnet Oracle blockchain.
  • A deploying command-line script that automates the validation and registration of all the data sources, scripts, reducers and filters required for the artifacts getting deployed.

You can explore the actual EVM ecosystems and networks being supported by using this command:

$ npx witnet avail --chains

SUPPORTED ECOSYSTEMS
--------------------
ARBITRUM
AVALANCHE
BASE
...
$ npx witnet avail --chains optimism

OPTIMISM ECOSYSTEM
------------------
optimism:mainnet
optimism:sepolia

Let’s now follow the steps on how to deploy the WitnetRequestPriceSatoshiWei artifact into the Optimism Sepolia testnet:

  1. Rename the .env_witnet file at your project’s root folder to .env. Or just copy the content of .env_witnet into .env if you already had one.
  2. Set up the W3GW_PRIVATE_KEYS variable to contain an array of one or more private keys from which EVM accounts will be derived.
  3. Make sure you properly fund the first EVM account in the array.
  4. Run the local ETH/RPC gateway: npx witnet ethrpc optimism:sepolia
  5. On a different terminal, run the following command:
$ npx witnet deploy optimism:sepolia --artifacts WitnetRequestPriceSatoshiWei
...
Building 'WitnetRequestPriceSatoshiWei'...
-----------------------------------------------------
> Template address: 0x1Ee13946FfA56350C75996b930879a6185dB4568
> Instance parameters:
Source #0: ["ETH","BTC"] => https://api.binance.com/api/v3/ticker/price?symbol=\0\\1\ ...
Source #1: ["BTC","ETH"] => https://api.coinbase.com/v2/exchange-rates?currency=\0\ ...
Source #2: ["ETH","BTC"] => https://api.kraken.com/0/public/Ticker?pair=\0\\1\ ...
Source #3: ["ETH","BTC"] => https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=\0\-\1\ ...
Source #4: ["ETH","BTC"] => https://www.okx.com/api/v5/market/ticker?instId=\0\-\1\ ...
> Template settlement hash: 0x7fbfc48a8a1f647f469dedd31c5495a8f16981fb4fca1a26525ec077694d936a
> Template settlement gas: 2111899
> Request address: 0x8712674330815BB8C635FF029235d97B9340C8D6
-------------------------------------
...

Done!

The just deployed WitnetRequest artifact was actually forked as a minimal proxy from the WitnetRequestFactory contract (part of the WSB framework), meaning that the contract got automatically verified on Optimism Sepolia, just right out of the box!

One of the most powerful capabilities of the WSB framework, is that data requests can get formally verified on-chain, and so, even other contracts can check the actual data sources, scripts, and so on, being resolved on the Wit/Oracle blockchain every time the data request gets pulled from the WitnetOracle instance. So if certain Witnet data request is verified on-chain, all results gotten from it can be assured to be 100% true to the data sources it formally refers to.

For instance, take your time to play around on the Optimism Sepolia block explorer and check how all metadata related to the just deployed artifact got properly and fully registered. Pay special attention to these methods:

- getRadonAggregator()
- getRadonRetrievalsCount()
- getRadonRetrievalByIndex(_index)
- getRadonTally()
- parameterized()
- radHash()
- resultDataMaxSize()
- resultDataType()

Pulling data updates from a Web3 console

Before going deeper into how to integrate Witnet within your own smart contracts, let’s perform a real test from a Web3 console tool (like the one bundled in the Witnet-Solidity package), and ask the WitnetOracle contract on Optimism Sepolia to resolve our just-deployed data request:

$ npx witnet console optimism:sepolia

OPTIMISM:SEPOLIA
----------------
0x77703aE126B971c9946d562F41Dd47071dA00777 <= WitnetOracle
0x1111AbA2164AcdC6D291b08DfB374280035E1111 <= WitnetPriceFeeds
0xC0FFEE98AD1434aCbDB894BbB752e138c1006fAB <= WitnetRandomnessV2
0x8712674330815BB8C635FF029235d97B9340C8D6 <= WitnetRequestPriceSatoshiWei

truffle(optimism:sepolia)> var wrb = await WitnetOracle.deployed()
truffle(optimism:sepolia)> var req = await WitnetRequest.at("0x8712674330815BB8C635FF029235d97B9340C8D6")
truffle(optimism:sepolia)> var radHash = await req.radHash()
truffle(optimism:sepolia)> var gasPrice = await web3.eth.getGasPrice()
truffle(optimism:sepolia)> var baseFee = await wrb.estimateBaseFee(gasPrice, radHash)
truffle(optimism:sepolia)> var witnessingCommitteeSize = 11
truffle(optimism:sepolia)> var witnessingRewardNanowit = 2 * 10 ** 8
truffle(optimism:sepolia)> await wrb.methods["postRequest(bytes32,(uint8,uint64))"](radHash, [ witnessingCommitteeSize, witnessingRewardNanowit ], { gasPrice, value: baseFee * 1.25 })
truffle(optimism:sepolia)> radHash
'0x4f43576e32f8425d3b71d786d537faff8e17ef60b3de5a8de78c78b83405f89f'

Don’t forget to keep running a local ETH/RPC gateway connected to Optimism Sepolia.

Once the data request result gets eventually posted into the WitnetOracle contract, we can really check that a new data request transaction gets mined and eventually solved on the Wit/Oracle blockchain by searching for the request’s unique RAD hash in a Witnet Block Explorer.

Pulling data updates from a Solidity contract

We will now learn how to create a mock-up contract prepared for interacting with the Wit/Oracle in order to pull a fresh update of the Satoshi/Wei market price.

Just run the Witnet Solidity Wizard from the command line, and answer the questions as shown below:

Once all questions get answered, a new file will be added to your project’s contracts/ folder, containing the source code of a Solidity contract that we will rename to MyWitnetPriceFeed:

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;
pragma experimental ABIEncoderV2;

import "witnet-solidity-bridge/contracts/apps/WitnetRequestConsumer.sol";

contract MyWitnetPriceFeed
is
WitnetRequestConsumer
{
using Witnet for Witnet.Result;
using WitnetCBOR for WitnetCBOR.CBOR;
using WitnetCBOR for WitnetCBOR.CBOR[];
using WitnetV2 for WitnetV2.RadonSLA;

constructor (WitnetRequest _witnetRequest)
WitnetRequestConsumer(
/* _witnetRequest */ _witnetRequest,
/* _witnetBaseFeeOverheadPercentage */ uint16(30),
/* _witnetCallbackGasLimit */ uint24(250000)
)
{
// ...
}

/// ... Introspect the `WitnetRequestConsumer` abstract contract to learn
/// ... about the internal helper methods at your disposal for pulling
/// ... data updates from the Witnet Oracle.

/// ===============================================================================================================
/// --- Implementing `IWitnetConsumer` interface ------------------------------------------------------------------
/// @notice Method to be called from the WitnetOracle contract as soon as the given `_witnetQueryId`
/// @notice gets reported, if solved with no errors.
/// @param _witnetQueryId The unique identifier of the Witnet query being reported.
/// @param _witnetResultTallyHash Hash of the commit/reveal witnessing act that took place in the Witnet blockahin.
/// @param _witnetResultTimestamp Timestamp at which the reported value was captured by the Witnet blockchain.
/// @param _witnetEvmFinalityBlock EVM block at which the provided data can be considered to be final.
/// @param _witnetResultCborValue The CBOR-encoded resulting value of the Witnet query being reported.
function reportWitnetQueryResult(
uint256 _witnetQueryId,
uint64 _witnetResultTimestamp,
bytes32 _witnetResultTallyHash,
uint256 _witnetEvmFinalityBlock,
WitnetCBOR.CBOR calldata _witnetResultCborValue
)
override external
onlyFromWitnet
{
// TODO: this method MUST be implemented
// TODO: gas consumed by this method SHOULD NEVER be greater than 250000 units.
}

/// @notice Method to be called from the WitnetOracle contract as soon as the given `_witnetQueryId`
/// @notice gets reported, if solved WITH errors.
/// @param _witnetQueryId The unique identifier of the Witnet query being reported.
/// @param _witnetResultTallyHash Hash of the commit/reveal witnessing act that took place in the Witnet blockahin.
/// @param _witnetResultTimestamp Timestamp at which the reported value was captured by the Witnet blockchain.
/// @param _witnetEvmFinalityBlock EVM block at which the provided data can be considered to be final.
/// @param _errorCode The error code enum identifying the error produced during resolution on the Witnet blockchain.
/// @param _errorArgs Error arguments, if any. An empty buffer is to be passed if no error arguments apply.
function reportWitnetQueryError(
uint256 _witnetQueryId,
uint64 _witnetResultTimestamp,
bytes32 _witnetResultTallyHash,
uint256 _witnetEvmFinalityBlock,
Witnet.ResultErrorCodes _errorCode,
WitnetCBOR.CBOR calldata _errorArgs
)
override external
onlyFromWitnet
{
// TODO: this method MUST be implemented
// TODO: gas consumed by this method SHOULD NEVER be greater than 250000 gas units.
}

// ...

As you can see in this mock-up contract:

  • Only the address of the some WitnetRequest artifact is required on the constructor.
  • As we told the Witnet Solidity Wizard that we want our contract to synchronously process results as soon as it gets reported from the Wit/Oracle, the mock-up contract inherits from WitnetRequestConsumer, forcing you to implement both the reportWitnetQueryResult(..) and the reportWitnetQueryError(..) virtual methods.

Querying the Wit/Oracle to resolve a data request

But before learning how to process data request results, we need our contract to pull the trigger and ask for a fresh data update. This of course can be done by directly calling onto the WitnetOracle counter-factual address, or using the internal helper methods already implemented on the underlying WitnetRequestConsumer and UsingWitnetRequest base contracts.

Aside from specifying a previously validated data request RAD hash, there are two more things that you need to account for when querying the Wit/Oracle to resolve such data request:

  • How much fee to pay when requesting some data:
    — The base fee ultimately depends on the transaction gas price (i.e. maxFeePerGas on EIP-1559 chains) and how large we can expect the result to be.
    — The actual fee, though, depends also on the query’s SLA parameters (explained below). It’s highly recommended to pay an extra overhead over the base fee. If not enough extra fee gets paid, data requests could ultimately get solved with an error instead of a value.
  • The Service Level Agreement parameters that we want the Wit/Oracle to commit to when solving the requested data:
    — The number of witnessing nodes required for solving the data request.
    — The amount of collateral (in nanoWIT) that every witnessing nodes will have to stake and put at risk before contributing to solve the data request.

Depending on how critical the result to the data request can be for your dapp (e.g. the amount of funds to be distributed depending on the returned value), you could require up to 127 witnesses. Of course, the higher the number of required witnesses, the higher the extra fee needs to be. On most regular use cases, something like 15 or 31 witnesses should suffice.

So now, let’s add a few public methods to our MyWitnetPriceFeed contract:

    function estimateUpdateFee() external view returns (uint256) {
return _witnetEstimateEvmReward();
}

function requestUpdate()
external payable
returns (uint256 _witnetQueryId)
{
return requestUpdate(__witnetDefaultSLA);
}

function requestUpdate(WitnetV2.RadonSLA memory _witnetQuerySLA)
public payable
returns (uint256 _witnetQueryId)
{
uint256 _witnetEvmReward = _witnetEstimateEvmReward();
_witnetQueryId = __witnetRequestData(_witnetEvmReward, _witnetQuerySLA);
if (_witnetEvmReward < msg.value) {
payable(msg.sender).transfer(msg.value - _witnetEvmReward);
}
}

Deserializing results from data requests

Data request results on the Wit/Oracle blockchain get encoded into CBOR buffers (see RFC-8949). So indeed, any data structure that can be encoded into a CBOR buffer can be potentially reported to your smart contract via the reportWitnetQueryResult(..) callback method: from a simple integer value, to float numbers, strings, chunks of bytes, multi-typed arrays, or even mapped-values objects.

All these data structures can get fully parsed by using the internal helper functions of the WitnetCBOR library, like:
- WitnetCBOR.readUint(_cbor)
- WitnetCBOR.readFloat64(_cbor)
- WitnetCBOR.readString(_cbor)
- WitnetCBOR.readArray(_cbor)
- WitnetCBOR.readMap(_cbor)

See how simple it gets in practice:

    /// ===============================================================================================================
/// --- Implementing `IWitnetConsumer` interface ------------------------------------------------------------------

event LastPrice(uint64 price, uint64 priceTs, bytes32 priceTallyHash);
event LastError(string error, uint64 errorTs, bytes32 errorTallyHash, bool retriable);

uint64 public lastPrice;
uint64 public lastPriceTs;
bytes32 public lastPriceTallyHash;

/// @notice Method to be called from the WitnetOracle contract as soon as the given `_witnetQueryId`
/// @notice gets reported, if solved with no errors.
/// @param _witnetResultTallyHash Hash of the commit/reveal witnessing act that took place in the Witnet blockahin.
/// @param _witnetResultTimestamp Timestamp at which the reported value was captured by the Witnet blockchain.
/// @param _witnetResultCborValue The CBOR-encoded resulting value of the Witnet query being reported.
function reportWitnetQueryResult(
uint256,
uint64 _witnetResultTimestamp,
bytes32 _witnetResultTallyHash,
uint256,
WitnetCBOR.CBOR calldata _witnetResultCborValue
)
override external
onlyFromWitnet
{
if (_witnetResultTimestamp > lastPriceTs) {
lastPrice = uint64(_witnetResultCborValue.readUint());
lastPriceTs = _witnetResultTimestamp;
lastPriceTallyHash = _witnetResultTallyHash;
emit LastPrice(
lastPrice,
lastPriceTs,
lastPriceTallyHash
);
}
}

Trying to parse some expected data type from a CBOR buffer that contains another will certain provoke a revert situation. So as to avoid the _witnetResultCborValue.readUint() sentence above from ever failing, we can add some clever condition on the constructor, just to make sure that any WitnetRequestartifact that may eventually be passed on construction, will always be one that returns an integer value:

    constructor (WitnetRequest _witnetRequest) 
WitnetRequestConsumer(
/* _witnetRequest */ _witnetRequest,
/* _witnetBaseFeeOverheadPercentage */ uint16(30),
/* _witnetCallbackGasLimit */ uint24(250000)
)
{
require(
_witnetRequest.resultDataType() == Witnet.RadonDataTypes.Integer,
"MyWitnetPriceFeed: uncompliant data request"
);
}

There’s one last important thing to be aware of, and that is that resolution of Witnet data requests can potentially fail, for multiple and different reasons.

It’s quite an unlikely thing to happen, specially when data sources, filters, and rewards were conveniently devised, but still possible. While describing all possible error situations is out of the scope of this article, it’s yet important to know that there are basically two main category of errors:

  • Those that can be considered “critical and permanent”, produced because there’s some malformation on either the data request itself, or the retrieved data doesn’t actually comply with the some expected schema, or perhaps one or more inconsistent data sources are being referred. In these cases, no matter how much incentives, or how many times you retry the data request, you shall always get an error result.

Inconsistent data sources are detected to be those that return different values depending on the IP address of witnessing nodes, or “User-agent” HTTP header values, for instance.

  • Those that can be considered “temporary or circumstantial”, meaning that the data request can eventually get solved if queried again. For instance, if a majority of data providers were simultaneously unavailable for a short period of time, or perhaps the SLA parameters were too strict and not enough witnessing nodes accepted the challenge of solving the data request. This sort of errors can be easily identified using the Witnet.isRetriable(_errorCode) internal method.

Let’s finish up the implementation of the MyWitnetPriceFeed contract by implementing the reportWitnetQueryError(..) virtual method:

    /// @notice Method to be called from the WitnetOracle contract as soon as the given `_witnetQueryId`
/// @notice gets reported, if solved WITH errors.
/// @param _witnetQueryId The unique identifier of the Witnet query being reported.
/// @param _witnetResultTallyHash Hash of the commit/reveal witnessing act that took place in the Witnet blockahin.
/// @param _witnetResultTimestamp Timestamp at which the reported value was captured by the Witnet blockchain.
/// @param _errorCode The error code enum identifying the error produced during resolution on the Witnet blockchain.
function reportWitnetQueryError(
uint256 _witnetQueryId,
uint64 _witnetResultTimestamp,
bytes32 _witnetResultTallyHash,
uint256,
Witnet.ResultErrorCodes _errorCode,
WitnetCBOR.CBOR calldata
)
override external
onlyFromWitnet
{
Witnet.ResultError memory _witnetError = witnet().getQueryResultError(_witnetQueryId);
emit LastError(
_witnetError.reason,
_witnetResultTimestamp,
_witnetResultTallyHash,
Witnet.isRetriable(_errorCode)
);
}

For your own convenience, you can find a fully verified version of this contract on Optimism Sepolia. Please, feel free to play around with it, request fresh data updates, and observe how data updates get logged in the “contract events” tab as soon as they get eventully reported from the Wit/Oracle blockchain.

In case you’d rather prefer to compile and deploy your own version, please have a look into the witnet/_*.config.js files to figure out the proper way to import Witnet-related artifacts within your migration scripts.

Data traceability on the Wit/Oracle blockchain

Tracing and proving data sources of all values provided by Witnet is pretty simple and straight-forward. Two general ways to do so:

  • By using the RAD hash of the underlying data request, you can check all the times that such data request got solved on the Wit/Oracle blockchain.

On the previously deployed WitnetRequestPriceSatoshiWei artifact, you can get the RAD hash by calling to radHash() method. You can then use it to track all historical updates of this price feed on the Wit/Oracle blockchain.

  • By using the Tally transaction hash as reported from the Wit/Oracle blockchain, you can get to track a lot of info:
    1. The actual data request that produced the reported value.
    2. The timestamp at which the reported value was captured.
    3. The witnessing committee members that got randomly selected on the Wit/Oracle blockchain.
    4. The actual revealed values by each one of them.
    5. The committee members that got punished (if any), and how much penalty they perceived.

On the above MyWitnetPriceFeed instance, you can read the Tally hash of the last valid update by calling to the lastPriceTallyHash()method. You can then use it on the Wit/Oracle explorer and access all details about such reported value.

That’s all folks by now!!! Thank you if you read through out this guide till the end. If you have any doubt or questions, don’t hesitate to raise them up on Witnet’s Discord channel. Stay tuned for more tutorials to come on how to leverage the Wit/Oracle on many other use cases.

--

--