D3lphi Oracle
On-chain oracles on the Bitcoin Cash network.
The D3lphi oracle is being developed as part of the Moria project, to serve as an on-chain oracle for the lending protocol.
The D3lphi oracle contracts allows for getting price information using transaction introspection. By taking a oracle utxo as input, transaction introspection can be used to fetch price information from the oracles NFT commitment.
flowchart LR
D3lphiInput --> DeFiTransaction--> D3lphiOutput
DefiContractIn --> DeFiTransaction --> DefiContractOut
Benefits of using an on-chain oracle in this manner include:
- No need to pass oracle and message as input and verify it.
- You do not need to query external sources for oracle messages.
- You can get the median price of multiple oracles as one input (see aggregated.cash).
- The oracle contract owner can swap out the underlying oracle if it goes offline.
Format
The oracle contains a 36 byte commitment at all times that is as follows:
Bytes | Purpose |
---|---|
20 bytes | Hash of the oracle public key |
16 bytes | Oracle message as defined by the oracles.cash protocol |
Note: Users should always verify that the price is not 0
. This occurs when
a contract owner swaps the oracle public key.
Notable trade-offs
You can optionally verify that the oracle used in the contract has not been changed by checking the first 20 bytes of the commitment. These bytes should match the hash of the oracle public key (hash160).
If you decide to not verify enforce the oracle public key hash, you put additional trust to the contract owner in addition to the oracle, essentially allowing the owner to swap out the oracle public key.
Ideally, you should deploy your own oracle contract.
Deployed contracts
Category / Token ID | Contract Address | Contract Owner | Oracle owner | Contract owner pubkey | Oracle pubkey | Notes |
---|---|---|---|---|---|---|
b0b6fc3d5cda81f4bb3fe464767dcc33e80b6356e4838f4dda40a1871a625950 | bitcoincash:pdm0hsy0twjt6zv0psx6z2sn6keznd5vd4lre0ge0jgwcqdwz94tjqaeveg6z | Riften Labs AS | General Protocols | 03341a6fb68e883fb2c5ce0d0d186e9e09792839479bfb14adda2f498fc2dfaacf | 02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818 | Updated at 10 minute intervals. Runs until May 2024. |
Fetch latest on-chain price with oracletools.js price <category> <contractAddress>
Usage, development and testing
This repository contains tests on regtest for all oracle calls.
These tests also serve as examples on how to use the oracle.
Components
Tool: oracletool.js
A tool for deploying and updating a contract. It can also be used to fetch price information from the blockchain.
Contract: oracle.cash
This is the main contract that holds the price information from a single oracle.
Calls:
update(oraclePubKey, oracleSig, oracleMessage)
Called to update the contract commitment to a newer oracle message.
use()
Called to take the contract as an input into a transaction, allowing another contract to accesse price information via transaction introspection.
The contract using price information as input must verify that the category of the contract is correct. The contract can optionally verify that the oracle has not been swapped.
This call requires that the utxo be re-created at the same output index as input index. There is a 1000 satoshi fee to use the contract.
withdrawBCH(ownerSignature)
Allows owner to withdraw BCH to contract, without changing the commitment.
swapOracle(ownerSignature)
Allows the owner to swap the underlying oracle used in the contract.
An oracle swap requires that the oracle message is set to blank
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Contract: aggregated.cash
This is a contract to aggregate the price from multiple oracles.
It takes 3 oracles and inputs and produces the median price of the 3.
This contract is barely within the OP code limit of Bitcoin Cash consensus, so adding more seems unfeasable, however, we can add more oracle entities by chaining aggregated oracles.
flowchart LR
Oracle1 --> AggregatedMedian1
Oracle2 --> AggregatedMedian1
Oracle3 --> AggregatedMedian1
AggregatedMedian1 --> MedianOfMedian
Oracle4 --> AggregatedMedian2
Oracle5 --> AggregatedMedian2
Oracle6 --> AggregatedMedian2
AggregatedMedian2 --> MedianOfMedian
Oracle7 --> AggregatedMedian3
Oracle8 --> AggregatedMedian3
Oracle9 --> AggregatedMedian3
AggregatedMedian3 --> MedianOfMedian
In this image we take the "median of three from the median of three", with total of 9 different oracle entities. This can be aggregated further if needed to add more entities.
update()
Takes 3 oracle inputs at this contracts input offset +1, +2 and +3. Parses the median price and updates its own commitment to match.
use()
Called to take the contract as an input into a transaction, allowing another contract to accesse price information via transaction introspection.
withdrawBCH(ownerSignature)
Allows contract owner to withdraw fees.
Format
Bytes | Purpose |
---|---|
20 bytes | Hash of all oracle public keys |
16 bytes | Oracle message with median price of 3 |
Deployment
It is suggested to deploy many threads. You do not need to update them all, but this allows you to scale if the utxos being updated get congested.
Deployer must not create a minting NFT, as this will allow him to publish messages without using the oracle.
Future work
Contract owners may want to deploy their own proxy oracle, allowing them to swap out underlying aggregated oracles.
flowchart LR
Oracle1 --> AggregatedOracle
Oracle2 --> AggregatedOracle
Oracle3 --> AggregatedOracle
AggregatedOracle --> ProxyOracleIn
ProxyOracleIn --> TxThatNeedsPrice
TxThatNeedsPrice --> ProxyOracleOut
OtherInputs --> TxThatNeedsPrice --> OtherOutputs
Adding a proxy would allow them to swap out the aggregated oracle. Proxy contract is not written yet.