Title: Price Oracles on XRP Ledger Revision: 3 (2023-10-18)
Author: Gregory Tsipenyuk Affiliation: Ripple
This proposal adds an on-chain PriceOracle
object to the XRPL ledger. A blockchain oracle is a system or service that acts as a bridge between a blockchain network and the external world, providing off-chain data or information to decentralized applications (dApps) on the blockchain. Oracles are used to bring real-world data, for instance market prices, exchange rates, interest rates, or weather conditions onto the blockchain, enabling dApps to access and utilize information that resides outside the blockchain. This document outlines a new protocol for price oracles the on XRPL ledger, and provides guidelines for developers and system architects to implement and utilize this solution effectively. This proposal introduces a new on-ledger PriceOracle
object and the transactions to create, delete, and update the PriceOracle
. It also adds the get_aggregate_price
API, to retrieve an aggregate mean
, trimmed mean
, and median
for the provided price oracles. This feature requires an amendment.
- Oracle Provider: A service or technology that enables the integration of external data and real-world events into a blockchain network.
- dApp (Decentralized Application): An application that is built on a blockchain network and operates using smart contracts or other mechanisms or protocols for their functionality.
The PriceOracle
ledger entry represents the PriceOracle
object on XRPL ledger and contains the following fields:
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
LedgerEntryType |
✔️ | string |
UINT16 |
Owner |
✔️ | string |
ACCOUNTID |
Provider |
✔️ | string |
BLOB |
PriceDataSeries |
✔️ | array |
ARRAY |
LastUpdateTime |
✔️ | number |
UINT32 |
URI |
string |
BLOB |
|
AssetClass |
✔️ | string |
BLOB |
PreviousTxnID |
✔️ | string |
HASH256 |
PreviousTxnLgrSeq |
✔️ | number |
UINT32 |
-
LedgerEntryType
identifies the type of ledger object. The proposal recommends the value 0x0080 as the reserved entry type. -
Owner
is the account that owns this object and has the update and delete privileges. It is recommended that this account has an associatedsigner list
. -
Provider
identifies an Oracle Provider. It can be URI or any data, for instancechainlink
. It is a string of up to 256 ASCII hex encoded characters (0x20-0x7E). -
PriceDataSeries
is an array of up to tenPriceData
objects, wherePriceData
represents the price information for a token pair. AnyPriceOracle
with more than fivePriceData
objects requires two owner reserves.PriceData
includes the following fields:FieldName Required? JSON Type Internal Type BaseAsset
✔️ string
CURRENCY
QuoteAsset
✔️ string
CURRENCY
AssetPrice
number
UINT64
Scale
number
UINT8
-
BaseAsset
refers to the primary asset within a trading pair. It is the asset against which the price of the quote asset is quoted. The base asset is usually considered the 'primary' asset and forms the basis for trading. Any valid identifier, such as a stock symbol, bond CUSIP, or currency code, should be allowed and interpreted exactly like other asset identifiers in the ledger. For example, in the pair BTC/USD, BTC is the base asset; in 912810RR9/BTC, 912810RR9 is the base asset. A new type,STI_CURRENCY
, is introduced to support theCURRENCY
field (see Appendix for details). -
QuoteAsset
represents the secondary or quote asset in a trading pair. It denotes the price of one unit of the base asset. The quote asset's value is expressed in terms of the base asset. Any valid identifier such as a currency or a crypto-currency code, should be allowed and interpreted exactly like other asset identifiers in the ledger. For example, in the pair BTC/USD, USD is the quote asset; in 912810RR9/BTC, BTC is the quote asset. A new enum value STI_CURRENCY is introduced to support theCURRENCY
field (see Appendix for details). TheBaseAsset
andQuoteAsset
together form a trading pair, and their relationship determines the price at which one asset can be exchanged for another. -
AssetPrice
is the scaled asset price, which is the price value after applying the scaling factor. This is an optional field. It is not included if the last update transaction didn't include theBaseAsset
/QuoteAsset
pair. -
Scale
is the price's scaling factor. It represents the price's precision level. For instance, ifScale
is6
and the original price is0.155
then the scaled price is155000
. Formally,$scaledPrice = originalPrice*{10}^{scale}$ . ValidScale
range is {0-10}. This is an optional field. It is not included if the last update transaction didn't include theBaseAsset
/QuoteAsset
pair.
-
-
URI
is an optional URI field to reference price data off-chain. It is limited to 256 bytes. -
AssetClass
describes a type of the assets, for instance "currency", "commodity", "index". It is a string of up to sixteen ASCII hex encoded characters (0x20-0x7E). -
LastUpdateTime
is the specific point in time when the data was last updated. TheLastUpdateTime
is the ripple epoch time. -
PreviousTxnID
is the hash of the previous transaction to modify this entry (same as on other objects with this field). -
PreviousTxnLgrSeq
is the ledger index of the ledger when this object was most recently updated/created (same as other objects with this field).
We compute the PriceOracle
object ID, a.k.a., OracleID
, as the SHA-512Half of the following values, concatenated in order:
- The Oracle space key (0x52)
- The Owner Account ID
- The Oracle Sequence. This field must be passed to the transactions and it describes a unique Price Oracle sequence for the given account.
{
"LedgerEntryType": "PriceOracle",
"OracleID": "00070C4495F14B0E44F78A264E41713C64B5F89242540EE255534400000000000000",
"Owner": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"Provider": "70726F7669646572",
"AssetClass": "63757272656E6379",
"PriceDataSeries": [
{
"PriceData": {
"BaseAsset": "XRP",
"QuoteAsset": "USD",
"AssetPrice": 74,
"Scale": 2,
}
},
],
"LastUpdateTime": 743609414,
"PreviousTxnID": "C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9",
"PreviousTxnLgrSeq": 56865244
}
This proposal introduces several new transactions to allow for the creation, update, and deletion of the PriceOracle
object.
We define a new transaction OracleSet for creating or updating a PriceOracle
instance. Before the transaction can be submitted to create a new PriceOracle
instance, the Oracle Provider has to do the following:
- Create or own the
Account
on the XRPL with sufficient XRP balance to meet the XRP reserve and the transaction fee requirements. - The Oracle Provider has to publish the
Account
account public key so that it can be used for verification by dApp’s. - The Oracle Provider has to publish a registry of available Price Oracles with their unique Oracle Sequence. The hash of the
Account
and theOracleSequence
uniquely identifies the Price Oracle on-ledger object.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
TransactionType |
✔️ | string |
UINT16 |
Indicates a new transaction type OracleSet
. The integer value is TBD.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
Account |
✔️ | string |
ACCOUNTID |
Account
is the XRPL account that has update and delete privileges on the Oracle being set.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
OracleSequence |
✔️ | string |
UINT32 |
OracleSequence
is a unique identifier of the Price Oracle for the given Account.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
Provider |
string |
BLOB |
Provider
identifies an Oracle Provider. Provider
must be included when creating a new instance of PriceOracle
.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
URI |
string |
BLOB |
URI
is an optional field to reference the price data off-chain.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
AssetClass |
string |
BLOB |
AssetClass
describes the asset's type.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
LastUpdateTime |
✔️ | number |
UINT32 |
LastUpdateTime
is the specific point in time when the data was last updated.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
PriceDataSeries |
✔️ | array |
ARRAY |
PriceDataSeries
is an array of up to ten PriceData
objects, where PriceData
represents the price information for a token pair. PriceData
includes the following fields:
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
BaseAsset |
✔️ | string |
CURRENCY |
BaseAsset
is the asset to be priced.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
QuoteAsset |
✔️ | string |
CURRENCY |
QuoteAsset
is the denomination in which the prices are expressed.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
AssetPrice |
✔️ | number |
UINT64 |
AssetPrice
is the scaled asset price, which is the price value after applying the scaling factor.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
Scale |
✔️ | number |
UINT8 |
Scale
is the price's scaling factor.
The transaction fails if:
- A required field is missing.
- XRP reserve is insufficient. If the Oracle instance has less or equal to five token pairs then the XRP reserve requirements is one, otherwise the XRP reserve requirements is two.
- Transaction's
PriceDataSeries
array size is empty or exceeds ten when creating a new Oracle instance or Oracle's instancePriceDataSeries
array size exceeds ten after updating the Oracle instance. PriceDataSeries
has duplicate token pairs.PriceDataSeries
has array elements with missingAssetPrice
.- The
Account
account doesn't exist or theAccount
is not equal to theOwner
field when updating the Oracle instance. - The transaction is not signed by the
Account
account or the account's multi signers. - The
URI
field length exceeds 64 bytes. - The
Provider
field length exceeds 64 bytes. - The
AssetClass
field length exceeds 16 bytes. - The
LastUpdateTime
field is less than the previousLastUpdateTime
or is greater than the last close time plus 30 seconds.
If an object with the OracleID
Object ID already exists then the new token pairs are added to the Oracle instance PriceDataSeries
array. Note that the order of the token pairs in the PriceDataSeries
array is not important since the token pair uniquely identifies location in the PriceDataSeries
array of the PriceOracle
object. Also note that not every token pair price has to be updated. I.e., even though the PriceOracle
may define ten token pairs, OracleSet
transaction may contain only one token pair price update. In this case the missing token pair will not include AssetPrice
and Scale
fields. PreviousTxnID
can be used to find the last updated Price Data for this token pair.
On success the transaction creates a new or updates existing PriceOracle
object. If a new object is created then the owner reserve requirement is incremented by one or two depending on the PriceDataSeries
array size.
{
"TransactionType": "OracleSet",
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"OracleSequence": 34,
"Provider": "70726F7669646572",
"LastUpdateTime": 743609014,
"AssetClass": "63757272656E6379",
"PriceDataSeries": [
{
"PriceData": {
"BaseAsset": "XRP",
"QuoteAsset": "USD",
"AssetPrice": 740,
"Scale": 3
}
}
]
}
We define a new transaction OracleDelete for deleting an Oracle instance.
FieldName | Required? | JSON Type |
---|---|---|
TransactionType |
✔️ | string |
Indicates a new transaction type OracleDelete
. The integer value is TBD.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
Account |
✔️ | string |
ACCOUNTID |
Account
is the account that has the Oracle update and delete privileges.
FieldName | Required? | JSON Type | Internal Type |
---|---|---|---|
OracleSequence |
✔️ | string |
UINT32 |
OracleSequence
is a unique identifier of the Price Oracle for the given Account.
OracleDelete transaction deletes the Oracle
object from the ledger.
The transaction fails if:
- Object with the
OracleID
Object ID doesn't exist. - The transaction is not signed by the
Account
account or the account's multi signers.
On success the transaction deletes the Oracle
object and the owner’s reserve requirement is reduced by one or two depending on the PriceDataSeries
array size.
{
"TransactionType": "OracleDelete",
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"OracleSequence": 34
}
An Oracle object can be retrieved with the ledger_entry
API call by specifying the account
and oracle_sequence
.
{
"method ": "ledger_entry ",
"params" : [
"account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"oracle_sequence": 34,
"ledger_index ": "validated "
]
}
{
"index" : "CF2C20122022DE908C4F521A96DC2C1E5EFFD1EFD47AA244E9EE9A442451162E",
"ledger_current_index" : 23,
"node" : {
"Flags" : 0,
"LastUpdateTime" : 743609014,
"LedgerEntryType" : "Oracle",
"Owner" : "rp847ow9WcPmnNpVHMQV5A4BF6vaL9Abm6",
"AssetClass" : "63757272656E6379",
"Provider": "70726F7669646572",
"PreviousTxnID" : "6F120537D0D212FEA6E11A0DCC5410AFCA95BD98D451D046832E6C4C4398164D",
"PreviousTxnLgrSeq" : 22,
"PriceDataSeries": [
{
"PriceData: {
"QuoteAsset" : {
"currency" : "USD"
},
"BaseAsset" : {
"currency" : "XRP"
},
"Scale" : 1,
"AssetPrice" : "740",
}
}
],
"index" : "CF2C20122022DE908C4F521A96DC2C1E5EFFD1EFD47AA244E9EE9A442451162E"
},
"status" : "success",
"validated" : true
}
get_aggregate_price
RPC calculates the aggregate price of the specified PriceOracle
, and returns three types of price statistics - average, median, and trimmed median if trim
parameter is included in the request.
FieldName | Required? | JSON Type |
---|---|---|
ledger_index |
string or number (positive integer) |
The ledger index of the max ledger to use, or a shortcut string to choose a ledger automatically.
FieldName | Required? | JSON Type |
---|---|---|
ledger_hash |
string |
A 20-byte hex string for the max ledger version to use.
FieldName | Required? | JSON Type |
---|---|---|
base_asset |
✔️ | string |
base_asset
is the asset to be priced.
FieldName | Required? | JSON Type |
---|---|---|
quote_asset |
✔️ | string |
quote_asset
is the denomination in which the prices are expressed.
FieldName | Required? | JSON Type |
---|---|---|
oracles |
✔️ | array |
oracles
is an array of oracle
objects to aggregate over. oracle
object has two fields:
-
FieldName Required? JSON Type account
✔️ string
account
is the Oracle's account. -
FieldName Required? JSON Type oracle_sequence
✔️ number oracle_sequence
is a unique identifier of the Price Oracle for the given Account.
FieldName | Required? | JSON Type |
---|---|---|
trim |
number |
trim
is the percentage of outliers to trim. Valid trim range is 1-25. If this parameter is included then the API returns trimmed median in addition to the average and median.
FieldName | Required? | JSON Type |
---|---|---|
time_threshold |
number |
The time_threshold
is used to define a time range in seconds for filtering out older price data. It's an optional parameter and is 0 by default; i.e. there is no filtering in this case.
The price data to aggregate is selected based on specific criteria. The most recent Price Oracle object is obtained for the specified oracles. The most recent LastUpdateTime
among all objects is chosen as the upper time threshold. A Price Oracle object is included in the aggregation dataset if it satisfies the conditions of containing the specified base_asset
/quote_asset
pair, including the AssetPrice
field, and its LastUpdateTime
is within the time range of (upper threshold - time threshold) to the upper threshold. If a Price Oracle object doesn't contain the AssetPrice
for the specified token pair, then up to three previous Price Oracle objects are examined and include the first one that fulfills the criteria.
The get_aggregate_price
fails if:
- The oracles array size is either 0 or greater than 200.
- The oracles array's object doesn't include
account
ororacle_sequence
or those fields have invalid value. base_asset
orquote_asset
are missing.trim
ortime_threshold
contain invalid uint value.- If the resulting data set is empty.
{
"method": "get_aggregate_price",
"params": [
{
"ledger_index": "current",
"base_asset": "XRP",
"quote_asset": "USD",
"flags": 7,
"trim": 20,
"oracles": [
{
"account": "rp047ow9WcPmnNpVHMQV5A4BF6vaL9Abm6,
"oracle_sequence": 34
},
{
"account": "rp147ow9WcPmnNpVHMQV5A4BF6vaL9Abm7,
"oracle_sequence": 56
},
{
"account": "rp247ow9WcPmnNpVHMQV5A4BF6vaL9Abm8,
"oracle_sequence": 2
},
{
"account": "rp347ow9WcPmnNpVHMQV5A4BF6vaL9Abm9,
"oracle_sequence": 7
},
{
"account": "rp447ow9WcPmnNpVHMQV5A4BF6vaL9Abm0,
"oracle_sequence": 109
}
]
}
]
}
On success, the response data contains the following fields:
entire_set
is an object of the following fields:size
is the size of the data set used to calculate the statistics.average
is the simple average.standard_deviation
is the standard deviation.
trimmed_set
is an object, which is included in the response iftrim
fields is set. The object has the following fields:size
is the size of the data set used to calculate the statistics.average
is the simple average.standard_deviation
is the standard deviation.
median
is the median.time
is the most recent time stamp out of allLastUpdateTime
values.
{
"entire_set" : {
"average" : "74.75",
"size" : 10,
"standard_deviation" : "0.1290994448735806"
},
"ledger_current_index" : 25,
"median" : "74.75",
"status" : "success",
"trimmed_set" : {
"average" : "74.75",
"size" : 6,
"standard_deviation" : "0.1290994448735806"
},
"validated" : false
"time" : 78937648
}
A new type, STI_CURRENCY
, is introduced to support BaseAsset
and QuoteAsset
fields' type CURRENCY
. This type can represent a standard currency code or an arbitrary asset as a 160-bit (40 character) hexadecimal string. This type is conformant to the XRPL Currency Codes. Below is a JSON example with the BaseAsset
representing a CUSIP code 912810RR9
as a 160-bit hexadecimal string and a QuoteAsset
representing a standard USD
currency code:
{
"PriceData" : {
"BaseAsset" : "3931323831305252390000000000000000000000",
"QuoteAsset" : "USD",
"Scale" : 1,
"SymbolPrice" : 740
}
}