Course Content

Chapter 0: Introduction



Chapter 2: How to create a data source




Chapter 2

How to create a data source

Estimated time: 1 hr 3 mins


Writing the data source

While a data source can be any type of executable or a Python script, in this course, we'll focus on creating a data source as a Python script. As the data source will be executed in Yoda's Python runtime, the Python packages that a data source are able to use is limited to what Yoda's runtime has available. The available packages on Yoda's Python runtime can be found here.

Data source workflow

A data source runtime can be defined into three main sections:

  • Input
  • Computation
  • Output

Input

As Yoda treats each data source as an executable, a shebang line in the python script is required to help define the type and path for Yoda when executing the script. If there are any inputs for the script, it is handled as arguments following the executable call. Therefore, we can use the argv() function from Python's sys module in order to get the command line arguments that was passed to the Python script.

The example below shows an oracle script that takes two input, symbol and multiplier of type str and int respectively:

#!/usr/bin/env python3
import sys


def main(symbol: str, multiplier: int):
    pass


if __name__ == "__main__":
    symbol = sys.argv[1]  # The symbol input
    multiplier = int(sys.argv[2])  # The multiplier input
    print(main(symbol, multiplier))

Computation

During the computation flow, the requested data should be retrieved and parsed into a format we expect our oracle script to retrieve.

For example, if our oracle scripts requires the price of a particular asset and the endpoint we've queried returns a message it the following format:

{
  "bitcoin": {
    "usd": 19965.3
  }
}

We'd need to find a method to parse the data into just 19965.3.

Below is an example of a data source that queries an endpoint for the Bitcoin Price Index (BPI) and parses the given information into just the BPI rate.

#!/usr/bin/env python3
import requests

def main(quote_currency):
    # Checks if quote currency is supported.
    # If not, raises an exception.
    if quote_currency not in {"USD", "GBP", "EUR"}:
        raise Exception(f"Quote currency {quote_currency} is not supported")

    # Sends a HTTP GET method to the URL given.
    r = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
    # If response status code is not 200, raises an exception.
    r.raise_for_status()

    # Attempt to get a specific value from the response. If the response
    # format is not as expected, raises an exception.
    try:
        return r.json()["bpi"][quote_currency]["rate_float"]
    except Exception as e:
        raise e

Output

Now that we're able to get that the oracle script requires, we need a method to send the data back to BandChain. In order to do so, we will need to print our output so that Yoda is able to read the output and send the data back to BandChain.

In the case of the data source not executing as expected, we can use sys.exit(exit_code) to set the exit code to 1 as to identify that the data source was not executed successfully.

Below shows an example implementation of the data sources input, computation and execution:

#!/usr/bin/env python3

## THIS IS CODE IS STRICTLY TO BE USED AS AN EXAMPLE AND IS NOT
## MEANT FOR PRODUCTION USE

import sys
import requests

URL = "https://api.coindesk.com/v1/bpi/currentprice.json"

def main(quote_currency):
    # Checks if quote currency is supported.
    # If not, raises an exception.
    if quote_currency not in {"USD", "GBP", "EUR"}:
        raise Exception(f"Quote currency {quote_currency} is not supported")

    # Sends a HTTP GET method to the URL given.
    r = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
    # If response status code is not 200, raises an exception.
    r.raise_for_status()

    # Attempt to get a specific value from the response. If the response
    # format is not as expected, raises an exception.
    try:
        return r.json()["bpi"][quote_currency]["rate_float"]
    except Exception as e:
        raise e


if __name__ == "__main__":
    try:
        # Takes the first command line argument passed to the Python script
        # and runs uses it as the parameter for main().
        # The result of main is then printed
        print(main(sys.argv[1]))
    except Exception as e:
        # Prints out any exceptions that the script comes acrosses
        print(str(e), file=sys.stderr)
        # Exits with exit status 1
        sys.exit(1)
Challenge

Here's a challenge to help test your understanding of the knowledge acquired in the past 3 chapters:

Given the following endpoint: https://api.binance.com/api/v3/klines, with the following documentation where its endpoints parameters are as follows:

NameTypeMandatoryDescription
symbolSTRINGYesPair of asset to query. e.g: BTCUSDT
intervalENUMYesCandlestick interval. e.g: 1d
startTimeLONGNoCandlestick start time in Unix milliseconds timestamp format. Default is the latest candle. e.g. 1632398400
endTimeLONGNoCandlestick end time in Unix milliseconds timestamp format. Default is the latest candle. e.g. 1632398400
limitINTNoLimit of results returned. Default is 500. e.g: 494

Create a data source that queries the API for a daily candlestick's closing price given the candlestick's opening timestamp and the candlestick pair. The code should return an error along with exit status 1 if the pair does not exist or does not have an candle that opens at the specified timestamp.

The data source should take the following inputs:

FieldTypeDescription
pairstrThe candlestick pair to query
timestampintThe candlestick's start time in Unix timestamp in seconds

The example inputs and expected outputs for testing can be found below:

Input:

pairtimestamp
BTCUSDT1502928000
ETHUSDT1662508800
BANDUSDT1618531200

Output:

price
4285.08000000
1630.00000000
20.96470000

Previous Chapter

Data Source overview