How to craft your own Witnet oracle queries using HTTP POST by Rokowski The Witnet Foundation Blog Oct, 2022

Recently, we went over how to craft oracle queries using HTTP GET. The Witnet oracle is capable of querying any publicly available API, and while HTTP GET is the most common way to get data, GraphQL APIs need to be called through HTTP POST. Similar to HTTP GET, you, as a developer, have the ability to control everything from data sources and aggregation parameters among other functions. This allows your data request to be perfectly tailored to your use case. If you want some background on the last article we wrote, take a look here.

As you probably already know, smart contracts aren’t able to receive information from outside of their network. This is because they are deterministic and censorship resistant. The oracle problem, something Witnet is dedicated to tackling, is finding a solution to receiving off-chain information in a way that doesn’t break the blockchain.

Witnet is a fully decentralized layer one network because it doesn’t rely on a trusted third party or KYC as many other oracles do. Witnet relies on slashing of staked collateral so that node operators are incentivized to act honestly.

Within the Witnet network, there are tens of thousands of nodes running the blockchain. These nodes are either fulfilling data requests in a trustless way or mining blocks. When your data request is fulfilled, its put into a Witnet block and thus immutable. In order to restructure the data in your smart contract, an attacker would need to reorder the Witnet blockchain.

Before we begin, its important to note that using POST versus GET is quite similar. The main difference when making HTTP GET queries is that we have the parameters we are going to send to the server: the “query” in the GraphQL case.

With that being said, we are going to run through all the steps as if we were unaware of the HTTP GET steps. That way, we can be as thorough as possible. If you would like to read the HTTP GET article directly, you can take a look here. We aren’t going to stray much from the ideas of that article up until step 4.

Step 1

We must first install Witnet dependencies if we are already using Truffle or HardHat.

yarn add witnet-solidity-bridgeyarn add witnet-requests –dev

You can take a more in-depth look at the Witnet-Solidity-Bridge here on our Github.

Step 2

This next one is quite an easy one. All we need to do is put our queries to the Witnet oracle in a directory called witnet for safe storing and organization.

Step 3

It is now time to begin by creating the actual oracle query. Let’s say we want to get the price of an asset that is difficult to find in a centralized exchange and we need to get data from a decentralized one. Let’s use VSQ/DAI.

The Witnet Foundation and OtherPlane Labs, in addition to the generally shared consensus between oracle solutions is that developers should always utilize APIs from sources with high volumes of trading, as the price is much more true. We also don’t utilize price aggregators like CoinMarketCap or CoinGecko.

When trying to strategize sources to use for your queries, we give the advice that you should use centralized or decentralized exchanges. Some of the ones we recommend are Binance, Coinbase, Gemini, Kraken, Bitfinex, SushiSwap, UniSwap, etc.

Keep in mind that this doesn’t mean you can’t query the Witnet oracle. Even if there is only one exchange that a coin you need queried is listed, you can still query it- you just need to be aware of the risks. This is the same for exchanges, you can query any one you want, but the risks are more inherent for some exchanges.

Now that we know what we want to query, we are going to choose our sources for our specific example. Let’s use the Sushiswap Polygon subgraph hosted by TheGraph. Of course, you can choose whichever ones you want.

Let’s now go back to the directory from step 2 the one called witnet. We need to create a file called VsqPrice.js.

Let’s edit the file and import the witnet-requests library using this command:

import * as Witnet from “witnet-requests”

Step 4

Let’s command some node operators to do some work, shall we? This is where things begin to stray from HTTP GET, but don’t worry, it comes back around on step 6.

Witnet internally has an easier way to consume GraphQL APIs instead of using HTTP POST. Witnet.GraphQLSource uses Witnet.HttpPostSource under the hood but is more developer friendly. We are then parsing the same special header from the result we have received from the API.

Basically, HttpPostSource and GraphQLSource are just about the same with the exception of adding query.

const sushiswap = new Witnet.HttpPostSource(
“https://api.thegraph.com/subgraphs/name/sushiswap/matic-exchange”,
`{ query: { pair(id: “0x102d39bc293472dc9ac3e6a0a9261a838b3bc6d7”) { token0Price }}}`,
{“Content-Type”: “application/json”}
)
.parseJSONMap()
.getMap(“data”)
.getMap(“pair”)
.getFloat(“token0Price”)
.multiply(10 ** 6)
.round()const sushiswap = new Witnet.GraphQLSource(
“https://api.thegraph.com/subgraphs/name/sushiswap/matic-exchange”,
`{ pair(id: “0x102d39bc293472dc9ac3e6a0a9261a838b3bc6d7”) { token0Price }}`,
{“Content-Type”: “application/json”}
)
.parseJSONMap()
.getMap(“data”)
.getMap(“pair”)
.getFloat(“token0Price”)
.multiply(10 ** 6)
.round()

Above, we saw an HTTP POST source in the example. Here are the arguments for it:

The URL to query is a string.Request body, or data, is a string.Headers as a Javascript object.

The deployment or compilation flow for HTTP POST requests are identical to any other Witnet request. Because of that, we are going to jump back to what would be the same spot on the HTTP GET article.

The only difference is that this article doesn’t feature the tutorials for subsequent sources for the same data request as the other article does. In spite of that, this tutorial will be one step behind numerically.

Step 5

Now that we have defined where our nodes need to get data from in order to be rewarded, we need to define what nodes need to do with that data.

There is a two step period within Witnet that is referred to as filter and reducer. These two pieces of the puzzle define our aggregations; what nodes must do with the data they have gotten for us.

Filter refers to removing any outliers that claim wrong information or goes against consensus of information that “happened.” This is perfect for flash crashes on one exchange but not on another. The ability for developers to decide how this parameter is used is one of the reasons the Witnet oracle is so user friendly.

The reducer allows us to decide how to aggregate together the results from multiple sources. For example, calculate the mean after the outliers are filtered out.

Aggregators offer a lot of flexibility under the Witnet umbrella. Developers can choose how they want their filter and reducer to operate. However, the Witnet Foundation has come up with a generalized approach that offers a great deal of flexibility and simplicity.

That function is this:

const aggregator = Witnet.Aggregator.deviationAndMean(1.5)

This means that the difference between the average can be no greater than 1.5x. Any data that a node brings back that is greater than 1.5x is filtered out and the recomputed for nodes who passed the filter.

Step 6

This step is called the Tally. The tally function is a second layer of security; it is the aggregation of data reporters.

This also comes in multiple forms and is very malleable. A generic one that is most commonly used is:

const tally = Witnet.Tally.deviationAndMean(2.5)

Step 7

We can now begin setting these parameters that we discussed in step 6.

const tally = Witnet.Tally.deviationAndMean(2.5)
.setAggregator(aggregator) // Set the aggregator function
.setTally(tally) // Set the tally function
.setQuorum(10, 51) // Set witness count and minimum consensus percentage
.setFees(5 * 10 ** 9, 10 ** 9) // Witnessing nodes will be rewarded 5 $WIT each
.setCollateral(50 * 10 ** 9) // Require each witness node to stake 50 $WIT

Where:

.setQuorum: Allowing us to choose how many nodes should go to our sources. The more nodes we ask to fulfill our request, both the more expensive and the more secure. In the example above, you’ll see (10, 51). 10 is the minimum nodes required to complete this request and they need 51% consensus among fulfilling nodes. If there isn’t a 51% consensus, abort.

.setFees: Allowing us to state how much to pay nodes that retrieve our necessary information. Above, in the code block, we see (5 * 10 ** 9, 10 ** 9). 5 WIT for each node, and 1 WIT for the miners to include it in their block.

.setCollateral: A very important aspect of Witnet. This tells nodes they need to post a specific amount of collateral. What we see above is (50 * 10 ** 9). Stake 50 WIT to be eligible to fulfill the request.

Step 8

The actual request we just wrote is complete as far as the Witnet network is concerned. Now, we need to compile the information into smart contract language, in this case, Solidity.

Let’s run this command from the project directory:

npx rad2sol – write-Contracts

With this command, the Javascript file will be analyzed and compiled into Witnet bytecode, automatically. It will then be wrapped into a small Solidity contract. You’ll most likely have to find it in the ./contracts/requests directory for importing into your Solidity contract.

Let’s begin testing

The actual request is done so let’s try a few tests.

We are going to run the query locally in order to see the result and then move on to the Solidity part. We do this to ensure valid data sources and our aggregation methods are working.

The witnet-requests library provides a command to try this test:

npx witnet-toolkit try-query – from-solidity

Step 1

// Import the UsingWitnet library that enables interacting with Witnetimport “witnet-ethereum-bridge/contracts/UsingWitnet.sol”;// Import the VsqPrice request that you compiled beforeimport “./requests/VsqPrice.sol”;

Step 2

Let’s now make our contract inherit from UsingWitnet:

contract PriceFeed is UsingWitnet {

This requires the constructor to pass the address of the WitnetRequestBoard that is specific to the network you are building on. It will most likely be a different address between different layers and chains, so bear that in mind.

To deploy with convenience on different EVM chains, and in order to not change the code you can make the constructor receive the WitnetRequestBoard as an argument and provide the address in our migration scripts when deploying.

constructor (WitnetRequestBoard _wrb) UsingWitnet(_wrb) {

Aside from this, the only change needing to be made in our contract is to define the query as a property of the contract and to instantiate it from the constructor. See below:

contract PriceFeed is UsingWitnet {
Request public query;
uint256 requestId;
uint64 latestPrice; constructor (WitnetRequestBoard _wrb) UsingWitnet(_wrb) {
query = new VsqPriceRequest();
}
}

There are a few properties to focus on; requestId and latestPrice. The former will store the identifier of the last updated price, the latter will keep the latest result.

Step 3

We have finally made it to the point where we can submit the query to the actual Witnet network. The payable function must call the _witnetPostRequest. This is going to look like this:

function requestUpdate() public payable {
requestId = _witnetPostRequest(query);
}

Witnet has a special contract enabling the communication between EVM chains and the Witnet sidechain, this is referred to as the WitnetRequestBoard. Calling _witnetPostRequest will get the bytecode posted there.

Step 4

When Witnet is working on fulfilling a data request, it is an asynchronous process. This means it takes up to 10 minutes for the query to be fulfilled. While this seems like a long time, it has proven effective in security and decentralization; there hasn’t been a use case we’ve seen yet that has suffered due to the timing.

The result will be reported back to the WitnetRequestBoard, which is a storage that your contract can read from. Other contracts are also able to read this feed after its fulfilled, and so is your contract! Let’s use this block below:

function completeUpdate() public witnetRequestResolved(requestId) {
Witnet.Result memory result = _witnetReadResult (requestId); if
witnet.isOk(result)) {
lastPrice = witnet.asUint64(result);
} else {
// You can decide here what to do if the query failed
}
}

Now we have the result with Witnet.Result, we now need to decode the right Solidity type, like .asUint64.

Keep this in mind: the WitnetRequestResolved(requestId) modifier prevents calling the function before the resolution of our query. We are going to use witnet.isOk(result) to see the query status.

Step 5: deploy!

This is an important piece. Deployment instructions tend to be very specific to the Solidity toolkit.

Our contract needs to get the address of the WitnetRequestBoard passed as an argument. Use this page here to find the contract address.

rad2sol can auto-generate migrations for the Witnet libraries and in turn, your contracts if you use it this way:

npx rad2sol – write-contracts – write-witnet-migrations – write- user-migrations

If you look at the migrations folder, you will find two files; 1_witnet_core.js and 2_user_contracts.js. The former deploys all the Witnet related contracts for deploying on a local or private network. It can also dynamically link them for those on a public network. The latter contains autogenerated migrations scripts for consumer contracts.

The compiler has also created default values for the additional constructor arguments in your contract.

Please make sure to double check the default argument that the compiler has inserted for you because they may not make sense for your use.

If you had any trouble, or you want to give us some feedback join our community. If you want to take your contract to the next level with a Witnet Foundation grant for marketing, resources, gas fees, etc, check the grant program.

Let us know what you think, our Discord is full of both answers to previous questions and people who will help you.

Website | GitHub | Twitter | Discord | Telegram | Reddit | YouTube