IntroductionDatum Standard

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
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_map
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:

Feed with two exchange rates
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
}