Datum Standard
The format of the Oracle Feed UTXO’s datum, as shown in the previous section’s example, consists of an Integer
and two POSIXTime
types. While this provides a simple asset rate, additional information could benefit both data providers and consumers. For instance, consumers may want to know the data feed’s decimal precision, asset names, or the involved data providers.
Charli3’s oracle can handle multiple data formats, which comply with the CBOR (Concise Binary Object Representation) data format standard. This standard organizes datum types and efficiently scales more information.
Charli3’s datum feed standard is located in the oracle-datum-lib repository. This repository assists in creating custom datums and standardizing the conversion of Haskell and CBOR code in both directions. It’s worth noting that other programming languages can be used to achieve the same functionality.
Technical specification
The Oracle Datum consists of three types of data: Generic
, Shared
, and Extended
. Generic Data
includes multiple feeds of Price Data
, which represent exchange rates.
The ?
symbol in CDDL denotes an optional value, meaning that the preceding element may or may not be present in the data. The 1*
syntax before the generic_data
field indicates that it must occur at least once in the list but can also occur multiple times. Therefore, an oracle feed can solely include the generic data type.
oracle_datum = #6.121(oracle_datum_list)
oracle_datum_list =
[ ? shared_data
, 1* generic_data
, ? extended_data
]
shared_data = #6.121([shared_map])
generic_data = price_data
extended_data = #6.122([extended_map])
price_data = #6.123([price_map])
Generic Data
The price_data
entry typically specifies the currency pair being priced. A currency pair refers to the two currencies involved in a trade, where the first currency listed (base currency) is purchased and the second currency (quote currency) is sold. Each currency is individually defined in the entry and is referred to as the base currency and quote currency, respectively.
The current list of exchange rates is predefined, and all fields are optional. Additionally, the list can be expanded to include additional fields as needed.
price_data = #6.123([price_map])
price_map =
{ ? 0 : price ; how many quote currency is received per base currency spent
, ? 1 : posixtime ; unix timestamp related to when the price data was created
, ? 2 : posixtime ; unix timestamp related to when the price data will expire
, ? 3 : precision ; decimal precision
, ? 4 : asset_id ; id of base
, ? 5 : asset_id ; id of quote
, ? 6 : asset_symbol ; symbol of base
, ? 7 : asset_symbol ; symbol of quote
, ? 8 : asset_name ; name of base
, ? 9 : asset_name ; name of quote
}
precision = uint
price = uint
asset_id = scripthash
asset_symbol = bytes .size (0..32)
asset_name = bytes .size (0..32)
posixtime = uint
For example, an oracle feed that provides three exchange rates, USD/BTC, ETH/USD and ADA/USD, from different sources can be represented as shown below:
121([123([
{ 0 : 1.234
, 1 : 1644939296
, 2 : 1644942896
, 3 : 3
, 4 : "USD"
, 5 : "BTC"
, 6 : "US Dollar"
, 7 : "Bitcoin"
, 8 : "United States Dollar"
, 9 : "Bitcoin"
},
{ 0 : 1573.10
, 1 : 1644939296
, 2 : 1644942896
, 4 : "ETH"
, 5 : "USD"
},
{ 0 : 0.033
, 1 : 1644939296
, 2 : 1644942896
, 3 : 3
}
])])
Since fields are optional, only necessary ones can be used.
It is possible to offer multiple exchange rates in a single datum, and the number of exchange rates can be theoretically N
(subject to size limits on transactions) in each oracle feed.
Extended Data
The Extended Data
section contains information that is specific to the oracle provider and may be customized by them to include data that they consider relevant. While the specification does not entirely define this section, indices below 100 are reserved by the specification.
extended_data = #6.122([extended_map])
extended_map =
{ ? 0: oracle_provider_id
, ? 1: data_source_count
, ? 2: data_signatories_count
, ? 3: oracle_provider_signature
}
oracle_provider_id = uint
data_source_count = uint
data_signatories_count = uint
oracle_provider_signature = tstr
Shared Data
The shared_data
structure stores common information among all generic_data
entries, reducing redundancy by factoring out repeated information. This includes the creation and expiration timestamps for all generic data entries
shared_data = #6.121([shared_map])
shared_map =
{ ? 0: price_data
, ? 1: kyc_data
}
Setters and getters helper functions
The oracle-datum-lib library’s objective is to provide an abstraction layer over the BuiltinData
type. It is implemented with the PlutusTx
library as well as using the INLINABLE
pragma to allow for its use in On-Chain Plutus code. It implements getters and setters for each Generic data
, Extended Data
and Shared Data
. The cddl
can be found on the spec.cddl
file in the root of the repository.
Blockchain’s data representation
For the live ADA/USD oracle feed on the main-net, the structural representation in the blockchain is identical to the one analyzed in this article. An example of the oracle feed inline datum displayed at some point is provided below:
{
"fields": [
{
"fields": [
{
"map": [
{
"k": {
"int": 0
},
"v": {
"int": 334136
}
},
{
"k": {
"int": 1
},
"v": {
"int": 1678127888323
}
},
{
"k": {
"int": 2
},
"v": {
"int": 1678129688323
}
}
]
}
],
"constructor": 2
}
],
"constructor": 0
}