How To Build a BTC/USD Oracle on Tezos with OrO
Recently we announced the development of OrO, a Decentralized Oracle Network on Tezos. Making smart contracts capable of extracting and pushing data, from and to off-chain resources, is necessary for making more applications possible on Tezos.
In this article we develop a BTC/USD Oracle that can be leveraged by smart contracts. The data that an oracle provides can be used to power specific business use-cases.
Make sure you have nodejs and npm installed!
Basic Terminology
Before we dive into the development, lets clarify some terms.
OrO Node
An OrO node is used to add/update data in your OrO contract(s). You can make changes to its configurations and decide how it works and what data it serves to Client Contract(s).
OrO Contract
A smart contract which serves data to Client Contract(s). For example, a Client Contract needs the live score of a Cricket match. It will query that data from an OrO contract by sending the name of the match, something like INDvsNZ, and the OrO contract will revert the data to the client’s entrypoint.
Client Contract
A smart contract which uses OrO contract data to run a business use case. A Client Contract can get data from an OrO Contract by paying a set amount of Tez.
Lets Get Started
First we need to clone the OrO repository. Once that is done extract the files and open your Powershell or terminal in that folder.
git clone https://github.com/Tezsure/OrO.git
cd OrO
npm install
This will install all the dependencies of the project.
Before configuring our OrO Node we first need to have our OrO Contract up and running on the Tezos blockchain.
What is inside an OrO Contract?
We will go through an OrO contract together, step by step.
In the OrO GitHub repository you can find a pre-configured btc/usd OrO contract. Copy this smart contract and paste it in SmartPy. Lets figure out how it works.
self.init(conversionData = sp.map(tkey = sp.TString, tvalue = sp.TRecord(buy=sp.TInt,sell=sp.TInt)), keysset = sp.set([admin]) , owner = admin)
I have initialized a map conversionData to store the buy and sell value of Bitcoin, with the fiat currency (USD) as a key. If a key exists the data will be updated, otherwise a new key value pair will be created.
The keysset is a set which contains public key hash(es) of the data contributors. A contributor can only be added by contract owners through an entry point.
An owner stores the address of the owner of the smart contract.
@sp.entry_point
def feedData(self,params):
sp.if (self.data.keysset.contains(sp.sender)):
self.data.conversionData[params.currency] = sp.record(buy = params.buy, sell = params.sell)
This entry point is for feeding data to the OrO contract. It first checks whether the sender is in the keysset.
@sp.entry_point
def addDataContributor(self,params):
sp.if sp.sender == self.data.owner:
self.data.keysset.add(params.contributor)
This entry point allows the owner to add a data contributor.
@sp.entry_point
def getDataFromOrO(self,params):
errcd = sp.record(buy = 0,sell=0)
contract = sp.contract(sp.TRecord(buy = sp.TInt, sell = sp.TInt),sp.sender,entry_point = "receiveDataFromOrO").open_some()
sp.if sp.amount == sp.mutez(5000):
sp.transfer(self.data.conversionData[params.currency],sp.mutez(0),contract)
sp.else:
sp.transfer(errcd,sp.amount,contract)
This entry point facilitates data sharing to the Client Contract. It receives the currency (USD in this example) as a key in the parameter and passes the buy and sell value in that currency, if it receives exactly 5000 Mutez. Otherwise it returns the buy and sell value as 0.
@sp.add_test(name="BTCUSDOracleTest")
def test():
scenario = sp.test_scenario()
oracle = BitcoinToCurrencyDataOracle(sp.address('tz1beX9ZDev6SVVW9yJwNYA89362ZpWuDwou'))
scenario += oracle
In this way you can initialize your public key hash as owner before deploying the smart contract.
Deploy the smart contract through SmartPy and save the contract address.
Open an explorer tab in SmartPy and place the contract address there and hit the Explore on Known Nodes and Networks button. Scroll down and select feedData from the drop down menu. Enter valid data and press Build Message. A new section will open, containing the Michelson format of the parameter. It will look like this:
(Right (Left (Pair 7096 (Pair "USD" 7095))))
Convert it to this:
(Right (Left (Pair buy (Pair 'currency' sell))))
We are using variable names instead of actual data, and converting double quotes to single quotes. This will be used in our OrO Node to feed data to the OrO Contract.
Configuring the OrO Node
Open the folder containing the OrO Node on your machine. We now need to edit the oro-config.json file.
In the tezosConfig section replace the privateKey, publicKey and keyHash with the strings you used to deploy the OrO Contract. If you added any other contributor he/she can place keys pertaining to that keyHash which is added as contributor.
Get the parameters conseilServerAddress and conseilServerAPIKEY from Nautilus Cloud. Enter carthagenet for the conseilServerNetwork parameter. Please note: the network keeps changing from time to time as per amendments on Tezos. The latest network name can also be found on Nautilus Cloud.
You can set after how many seconds the adapter has to read data again i from the endpoints and write to the OrO Contract with oracleInterval.
Once we update data to contract after how many seconds does adapter has to read data again from endpoints and write to smart contract. This interval between two jobs is called as oracleInterval. Here job means adding data from all endpoints
Now its time to set up endpoints to feed data. In this tutorial I am using Blockchain Info API to get live conversion data.
Place the converted Michelson (from a few steps back!) in entryPointMichelson. In contractAddress place the address which we got after deploying the smart contract. In endPoint place the API URL which will return the JSON containing data.
Check out the readme file of the GitHub repository if you want to know exactly what every parameter does.
After making all changes your oro-config.json file will look like this:
{
"tezosConfig": {
"nodeAddress": "https://tezos-dev.cryptonomic-infra.tech:443",
"publicKey": "edpkus4kGVS6JbuMuMaFQaNon8ehnkW3wXdD2jHw5YBRLp4jjcSnht",
"privateKey": "edskRoHVfnf2N1tp7U6LLWk6EQRNpgdBsUxTr6LCDPkCn5FZkygDdoA4zpCwfHGtCeoqYtCWZKiffYj1uPrvY956eZ5vJeNXnj",
"keyHash": "tz1XrHHchSehNudgAq1aQaoB4Bw7N4hZ1nkH",
"conseilServerAddress": "https://conseil-dev.cryptonomic-infra.tech:443",
"conseilServerAPIKEY": "4e3328e0-8008-4e70-a32e-286d9be8253c",
"conseilServerNetwork": "carthagenet"
},
"oracleInterval": 30,
"oracleConfig": [
{
"entryPointMichelson": "(Right (Left (Pair buy (Pair 'currency' sell))))",
"contractAddress": "KT1S8txAt6kpvdYuTuNdaBjj9QZWf3uuXHqS",
"endPoint": "http://blockchain.info/ticker",
"fields": [
{
"type": "fixed",
"nameInEntryPoint": "currency",
"data": "USD"
},
{
"type": "custom",
"nameInEntryPoint": "buy",
"path": "USD.buy",
"isString": false,
"doFloor": true,
"doCeil": false,
"multiplier": 1
},
{
"type": "custom",
"nameInEntryPoint": "sell",
"path": "USD.sell",
"isString": false,
"doFloor": true,
"doCeil": false,
"multiplier": 1
}
]
}
]
}
Go back to your Powershell or terminal and execute the following command:
npm run oro
This will start the OrO Node and real time data will be added to the OrO contract.
The Client Contract
Now we need to test our oracle using a Client Contract, which is present in the OrO repository.
Lets go through the different parts of the contract together.
self.init(buy =sp.int(0),sell=sp.int(0))
First we initialize two variables to store the buy and sell values.
@sp.entry_point
def requestDataFromOrO(self,params):
contract = sp.contract(sp.TRecord(currency = sp.TString),params.oracleAddress,entry_point = "getDataFromOrO").open_some()
requestRecord = sp.record(currency = params.currency)
sp.transfer(requestRecord,sp.mutez(5000),contract)
This entry point facilitates getting data from the OrO Contract. We need to pass the currency parameter (USD in this case) and the address of the Oracle contract.
@sp.entry_point
def receiveDataFromOrO(self,params):
self.data.buy = params.buy
self.data.sell = params.sell
This entry point is for getting a response from the OrO Contract.
Copy and paste the code in SmartPy and press deploy.
Make sure your Client Contract has enough Tez for payments to the OrO Contract while deploying the Client Contract. There is a separate amount field where you can input an amount of Tez you want to provide to the contract while it gets deployed.
Once deployed go to the explorer tab of SmartPy to simulate the transaction between the Client Contract and the OrO Contract.
Please note: you can place multiple endpoints in the same oro-config.json file. An example for that is provided in the test folder of the OrO repository.