Skip to main content

How to Query Custom Data with Tellor

By June 10, 2022November 23rd, 2022Tellor Info

Any data, any chain, any speed

We all know that you can do price feeds super easily with Tellor.  In fact, we put prices from every single token on coingecko up just to prove it. All you have to do is go to our fund-a-feed tool, specify your pair, make a tip, and you’re good to go.  We can get you up with popular price feeds same day and you don’t even need approval from us.  

But let’s say you don’t want a price feed.  You want something special.  Say you want github pull requests by a certain repo, number of discord messages by user in a given channel, or even the event logs from some other chain.  Most of the time we get questions like “can you do this” or “is this possible” and the answer for Tellor is always yes.  If there’s data out there and anyone can grab it (no private pieces that would prevent others from fact checking), then we can bring it on-chain.  

The real question is just: “how do I format my solidity for it and get Tellor reporters to start putting it on chain?”. 

TL;DR: You can get any data permissionlessly ( and we’ll explain how in this article), but if you want to skip the understanding, go fill out one of these: https://github.com/tellor-io/dataSpecs/issues/25  and we’ll get in touch and help you out. 

What data do you want, exactly?

The hardest part of getting data on-chain with Tellor is picking what data you actually want.  The Tellor system is actually pretty simple: 

The part you need to ask yourself, how do I specify what the reporters should put on?  Do I have data sources for it? 

An example is the Bitcoin price.  Most people think that the Bitcoin price is a simple thing to put on-chain, but not once you get into the details:  Do you want it from a specific exchange? Do you want a median?  From what exchanges?  Do you want it to be a twap at all?  What should people do if the API for a given exchange goes down? 

And then it gets even more complicated, too…  What constitutes a bad value?  Is .1% off a bad btc price? How about 1%?  How about 10%?  

If you don’t specify which exchanges, it’s actually really hard with something as volatile as Bitcoin to enforce a low percentage.  As you can probably tell, there’s no right way to choose data.  Just be sure you think about its attack vectors and how it affects your protocol and users before defining it and using it on-chain. 

Once you have the definition for your data down, go fill out an issue form in dataSpecs: https://github.com/tellor-io/dataSpecs/issues/25 .  This is the first step toward telling reporters what data you’ll be needing. 

How to write your Solidity

To get your Solidity working, you can follow the same instructions as for any data, and we’ll make some edits:  https://docs.tellor.io/tellor/getting-data/reading-data

This is the main chunk of solidity code (found in the sampleUsingTellor repo): 

  constructor(address payable _tellorAddress) UsingTellor(_tellorAddress) public {}

  function setBtcPrice() public {

    bytes memory _b = abi.encode("SpotPrice",abi.encode("btc","usd"));
    bytes32 _queryID = keccak256(_b);
  
    uint256 _timestamp;
    bytes _value;

    (_value, _timestamp) = getDataBefore(_queryId, block.timestamp - 15 minutes);

    btcPrice = abi.decode(_value,(uint256));
  }

This is basically how to read a value from another contract (the Tellor contract), and then decode it from bytes to whatever format you need.  

The parts you’ll need to change are the first and the last line of the function.  For an example, let’s use our discord example.  We want the number of messages for a given user in a given channel (this could for instance be used for airdrops to show community engagement).  

The first line, where the “_queryData” is defined, you would first have to change the “type” (“SpotPrice” here) and the second are the encoded arguments (“BTC”,”USD”). You’ll have to tell the reporters what these arguments are in our dataSpecs repo (where you made the github issue in the first step). 

For our example, we’ll need a custom “type”, so we’ll call it “DiscordMessages” instead of SpotPrice and then for the arguments, we’ll have two: username and channel (e.g. “themandalore#6530” and “tellor” for my username on the tellor channel).  

And let’s say for funsies, we want two return values: the number of messages and the timestamp of the last message; so two uints.  So how do we get these values?  Let’s give it a go: 


function setDiscord(string _username, string _channel) public {
    bytes memory _queryData = abi.encode("DiscordMessages",abi.encode(_username,_channel));
    bytes32 _queryID = keccak256(_queryData);
    
    uint256 _timestamp;
    bytes _value;
    (_value, _timestamp) = getDataBefore(_queryId, block.timestamp - 15 minutes);
    uint256 _numMessages;
    uint256 _lastPostTimestamp;

    (_numMessages,_lastPostTimestamp) = abi.decode(_value,(uint256,uint256));
  }

As you’ll notice, we don’t put my username or the channel name in the solidity, we pass them as arguments!  This means that this function can go fetch the data of any user/channel combo on discord!

Testing your custom data function locally

This piece is easy and the same as other feeds, just have a few quick edits to the test files.  For understanding, we use the “Tellor Playground” which is the Tellor contract that you can deploy locally and has the added benefit of not making you stake or get test tokens or anything.  For ease of testing it on the testnets as well, we also have the playground deployed there too

Looking at our sampleUsingTellor test file:

const abiCoder = new ethers.utils.AbiCoder
const queryDataArgs = abiCoder.encode(['string', 'string'], ['eth', 'usd'])
const queryData = abiCoder.encode(['string', 'bytes'], ['SpotPrice', queryDataArgs])
const queryId = ethers.utils.keccak256(queryData)const mockValue = 50000;
// submit value takes 4 args : queryId, value, nonce and queryData
await tellorOracle.submitValue(queryId,mockValue,0,queryData);
let retrievedVal = await sampleUsingTellor.readEthPrice(queryId);

We just have to change those arguments again to be what we need for the queryData and the args, and then just put a new mock value and we’re good.  I highlighted the changes. 

const abiCoder = new ethers.utils.AbiCoder
const queryDataArgs = abiCoder.encode(['string', 'string'], ''themandalore#6530”',  “tellor”])
const queryData = abiCoder.encode(['string', 'bytes'], ['DiscordMessages', queryDataArgs])
const queryId = ethers.utils.keccak256(queryData)
//sample 10 messages and an epoch timestamp of when I’m writing this
const mockValue = abiCoder.encode(['uint’', 'uint'], [10,1654285930])
await tellorOracle.submitValue(queryId,mockValue,0,queryData);
let retrievedVal = await sampleUsingTellor.setDiscord(queryId);

Awesome, so now write a few assertions, test it with some different values and figure out how you get the data so you can move to the last step which is teaching the reporters how to get it and then paying them.  

Getting reporters to support it

Now that you know what data you want and you know how to get it into your own smart contract, you have to teach the reporters.  It’s a decentralized network of degens who put up money and run software to get smaller amounts of money, it’s not rocket science, but they don’t want to get slashed, so they’ll be cautious.  The usual way we teach them: 

  • Finalize a dataSpec (you should be on your way if you made an issue in github in the first step)
  • Tip it on a testnet  
    • This puts a bounty up for the next person who submits the value on-chain.  This will make it real.  Once someone submits it, be sure to check that it’s what you want and your contract works!
  • Ping the reporters on discord to get support.  

Be prepared to wait some time after the first tip.  If the reporters don’t have the software written to have your request automated, you’ll have to teach them, wait for them to get up to speed,  or report it yourself.  

But you got it down!  Go request some cool data and surprise us with some requests!!