mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 13:05:49 +00:00
feat: add Price Oracles support (#2688)
This commit is contained in:
@@ -170,3 +170,11 @@ fixNFTokenRemint
|
||||
# 2.0.0 Amendments
|
||||
XChainBridge
|
||||
DID
|
||||
# 2.2.0-b3 Amendments
|
||||
fixNFTokenReserve
|
||||
fixInnerObjTemplate
|
||||
fixAMMOverflowOffer
|
||||
PriceOracle
|
||||
fixEmptyDID
|
||||
fixXChainRewardRounding
|
||||
fixPreviousTxnID
|
||||
|
||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -4,7 +4,7 @@
|
||||
name: Node.js CI
|
||||
|
||||
env:
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.0.0-b4
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.2.0-b3
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -90,7 +90,7 @@ This should be run from the `xrpl.js` top level folder (one above the `packages`
|
||||
```bash
|
||||
npm run build
|
||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
||||
docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.0.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg
|
||||
docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
* Support for the Price Oracles amendment (XLS-47).
|
||||
|
||||
### Fixed
|
||||
* Better error handling/error messages for serialization/deserialization errors.
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"UInt512": 23,
|
||||
"Issue": 24,
|
||||
"XChainBridge": 25,
|
||||
"Currency": 26,
|
||||
"Transaction": 10001,
|
||||
"LedgerEntry": 10002,
|
||||
"Validation": 10003,
|
||||
@@ -51,6 +52,7 @@
|
||||
"NFTokenOffer": 55,
|
||||
"AMM": 121,
|
||||
"DID": 73,
|
||||
"Oracle": 128,
|
||||
"Any": -3,
|
||||
"Child": -2,
|
||||
"Nickname": 110,
|
||||
@@ -208,6 +210,16 @@
|
||||
"type": "UInt8"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Scale",
|
||||
{
|
||||
"nth": 4,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt8"
|
||||
}
|
||||
],
|
||||
[
|
||||
"TickSize",
|
||||
{
|
||||
@@ -498,6 +510,16 @@
|
||||
"type": "UInt32"
|
||||
}
|
||||
],
|
||||
[
|
||||
"LastUpdateTime",
|
||||
{
|
||||
"nth": 15,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt32"
|
||||
}
|
||||
],
|
||||
[
|
||||
"HighQualityIn",
|
||||
{
|
||||
@@ -828,6 +850,16 @@
|
||||
"type": "UInt32"
|
||||
}
|
||||
],
|
||||
[
|
||||
"OracleDocumentID",
|
||||
{
|
||||
"nth": 51,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt32"
|
||||
}
|
||||
],
|
||||
[
|
||||
"IndexNext",
|
||||
{
|
||||
@@ -1028,6 +1060,16 @@
|
||||
"type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
"AssetPrice",
|
||||
{
|
||||
"nth": 23,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
"EmailHash",
|
||||
{
|
||||
@@ -1918,6 +1960,26 @@
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"AssetClass",
|
||||
{
|
||||
"nth": 28,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Provider",
|
||||
{
|
||||
"nth": 29,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Account",
|
||||
{
|
||||
@@ -2128,6 +2190,26 @@
|
||||
"type": "PathSet"
|
||||
}
|
||||
],
|
||||
[
|
||||
"BaseAsset",
|
||||
{
|
||||
"nth": 1,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Currency"
|
||||
}
|
||||
],
|
||||
[
|
||||
"QuoteAsset",
|
||||
{
|
||||
"nth": 2,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Currency"
|
||||
}
|
||||
],
|
||||
[
|
||||
"LockingChainIssue",
|
||||
{
|
||||
@@ -2458,6 +2540,16 @@
|
||||
"type": "STObject"
|
||||
}
|
||||
],
|
||||
[
|
||||
"PriceData",
|
||||
{
|
||||
"nth": 32,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STObject"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Signers",
|
||||
{
|
||||
@@ -2628,6 +2720,16 @@
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"PriceDataSeries",
|
||||
{
|
||||
"nth": 24,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"AuthAccounts",
|
||||
{
|
||||
@@ -2656,6 +2758,7 @@
|
||||
"telWRONG_NETWORK": -386,
|
||||
"telREQUIRES_NETWORK_ID": -385,
|
||||
"telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384,
|
||||
"telENV_RPC_FAILED": -383,
|
||||
|
||||
"temMALFORMED": -299,
|
||||
"temBAD_AMOUNT": -298,
|
||||
@@ -2703,6 +2806,8 @@
|
||||
"temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256,
|
||||
"temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
|
||||
"temEMPTY_DID": -254,
|
||||
"temARRAY_EMPTY": -253,
|
||||
"temARRAY_TOO_LARGE": -252,
|
||||
|
||||
"tefFAILURE": -199,
|
||||
"tefALREADY": -198,
|
||||
@@ -2739,7 +2844,6 @@
|
||||
"terQUEUED": -89,
|
||||
"terPRE_TICKET": -88,
|
||||
"terNO_AMM": -87,
|
||||
"terSUBMITTED": -86,
|
||||
|
||||
"tesSUCCESS": 0,
|
||||
|
||||
@@ -2815,7 +2919,11 @@
|
||||
"tecXCHAIN_SELF_COMMIT": 184,
|
||||
"tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185,
|
||||
"tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186,
|
||||
"tecEMPTY_DID": 187
|
||||
"tecEMPTY_DID": 187,
|
||||
"tecINVALID_UPDATE_TIME": 188,
|
||||
"tecTOKEN_PAIR_NOT_FOUND": 189,
|
||||
"tecARRAY_EMPTY": 190,
|
||||
"tecARRAY_TOO_LARGE": 191
|
||||
},
|
||||
"TRANSACTION_TYPES": {
|
||||
"Invalid": -1,
|
||||
@@ -2864,6 +2972,8 @@
|
||||
"XChainCreateBridge": 48,
|
||||
"DIDSet": 49,
|
||||
"DIDDelete": 50,
|
||||
"OracleSet": 51,
|
||||
"OracleDelete": 52,
|
||||
"EnableAmendment": 100,
|
||||
"SetFee": 101,
|
||||
"UNLModify": 102
|
||||
|
||||
@@ -4868,6 +4868,36 @@
|
||||
"TxnSignature": "AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD772999325667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08",
|
||||
"URI": "6469645F6578616D706C65"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binary": "1200332FFFFFFFFF2033000004D2750B6469645F6578616D706C65701C0863757272656E6379701D0870726F7669646572811401476926B590BA3245F63C829116A0A3AF7F382DF018E020301700000000000001E2041003011A0000000000000000000000000000000000000000021A0000000000000000000000005553440000000000E1F1",
|
||||
"json": {
|
||||
"TransactionType": "OracleSet",
|
||||
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
|
||||
"OracleDocumentID": 1234,
|
||||
"LastUpdateTime": 4294967295,
|
||||
"PriceDataSeries": [
|
||||
{
|
||||
"PriceData": {
|
||||
"BaseAsset": "XRP",
|
||||
"QuoteAsset": "USD",
|
||||
"AssetPrice": "00000000000001E2",
|
||||
"Scale": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"Provider": "70726F7669646572",
|
||||
"URI": "6469645F6578616D706C65",
|
||||
"AssetClass": "63757272656E6379"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binary": "1200342033000004D2811401476926B590BA3245F63C829116A0A3AF7F382D",
|
||||
"json": {
|
||||
"TransactionType": "OracleDelete",
|
||||
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
|
||||
"OracleDocumentID": 1234
|
||||
}
|
||||
}
|
||||
],
|
||||
"ledgerData": [{
|
||||
|
||||
@@ -6,6 +6,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
||||
### BREAKING CHANGES
|
||||
* Small fix in the API to use a new flag name `tfNoDirectRipple` instead of the existing flag name `tfNoRippleDirect`
|
||||
|
||||
### Added
|
||||
* Support for the Price Oracles amendment (XLS-47).
|
||||
|
||||
### Fixed
|
||||
* Typo in `Channel` type `source_tab` -> `source_tag`
|
||||
* Fix `client.requestAll` to handle filters better
|
||||
|
||||
@@ -155,3 +155,36 @@ export interface XChainBridge {
|
||||
IssuingChainDoor: string
|
||||
IssuingChainIssue: Currency
|
||||
}
|
||||
|
||||
/**
|
||||
* A PriceData object represents the price information for a token pair.
|
||||
*
|
||||
*/
|
||||
export interface PriceData {
|
||||
PriceData: {
|
||||
/**
|
||||
* The primary asset in a trading pair. Any valid identifier, such as a stock symbol, bond CUSIP, or currency code is allowed.
|
||||
* For example, in the BTC/USD pair, BTC is the base asset; in 912810RR9/BTC, 912810RR9 is the base asset.
|
||||
*/
|
||||
BaseAsset: string
|
||||
|
||||
/**
|
||||
* The quote asset in a trading pair. The quote asset denotes the price of one unit of the base asset. For example, in the
|
||||
* BTC/USD pair,BTC is the base asset; in 912810RR9/BTC, 912810RR9 is the base asset.
|
||||
*/
|
||||
QuoteAsset: string
|
||||
|
||||
/**
|
||||
* The asset price after applying the Scale precision level. It's not included if the last update transaction didn't include
|
||||
* the BaseAsset/QuoteAsset pair.
|
||||
*/
|
||||
AssetPrice?: number | string
|
||||
|
||||
/**
|
||||
* The scaling factor to apply to an asset price. For example, if Scale is 6 and original price is 0.155, then the scaled
|
||||
* price is 155000. Valid scale ranges are 0-10. It's not included if the last update transaction didn't include the
|
||||
* BaseAsset/QuoteAsset pair.
|
||||
*/
|
||||
Scale?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import FeeSettings from './FeeSettings'
|
||||
import LedgerHashes from './LedgerHashes'
|
||||
import NegativeUNL from './NegativeUNL'
|
||||
import Offer from './Offer'
|
||||
import Oracle from './Oracle'
|
||||
import PayChannel from './PayChannel'
|
||||
import RippleState from './RippleState'
|
||||
import SignerList from './SignerList'
|
||||
@@ -30,6 +31,7 @@ type LedgerEntry =
|
||||
| LedgerHashes
|
||||
| NegativeUNL
|
||||
| Offer
|
||||
| Oracle
|
||||
| PayChannel
|
||||
| RippleState
|
||||
| SignerList
|
||||
@@ -52,6 +54,7 @@ type LedgerEntryFilter =
|
||||
| 'nft_offer'
|
||||
| 'nft_page'
|
||||
| 'offer'
|
||||
| 'oracle'
|
||||
| 'payment_channel'
|
||||
| 'signer_list'
|
||||
| 'state'
|
||||
|
||||
43
packages/xrpl/src/models/ledger/Oracle.ts
Normal file
43
packages/xrpl/src/models/ledger/Oracle.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { PriceData } from '../common'
|
||||
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
/**
|
||||
* The Oracle object type describes a single Price Oracle instance.
|
||||
*
|
||||
* @category Ledger Entries
|
||||
*/
|
||||
export default interface Oracle extends BaseLedgerEntry, HasPreviousTxnID {
|
||||
LedgerEntryType: 'Oracle'
|
||||
|
||||
/**
|
||||
* The time the data was last updated, represented as a unix timestamp in seconds.
|
||||
*/
|
||||
LastUpdateTime: number
|
||||
|
||||
/**
|
||||
* The XRPL account with update and delete privileges for the oracle.
|
||||
*/
|
||||
Owner: string
|
||||
|
||||
/**
|
||||
* Describes the type of asset, such as "currency", "commodity", or "index".
|
||||
*/
|
||||
AssetClass: string
|
||||
|
||||
/**
|
||||
* The oracle provider, such as Chainlink, Band, or DIA.
|
||||
*/
|
||||
Provider: string
|
||||
|
||||
/**
|
||||
* An array of up to 10 PriceData objects.
|
||||
*/
|
||||
PriceDataSeries: PriceData[]
|
||||
|
||||
/**
|
||||
* A bit-map of boolean flags. No flags are defined for the Oracle object
|
||||
* type, so this value is always 0.
|
||||
*/
|
||||
Flags: 0
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
|
||||
import { NFTokenOffer } from './NFTokenOffer'
|
||||
import { NFToken, NFTokenPage } from './NFTokenPage'
|
||||
import Offer, { OfferFlags } from './Offer'
|
||||
import Oracle from './Oracle'
|
||||
import PayChannel from './PayChannel'
|
||||
import RippleState, { RippleStateFlags } from './RippleState'
|
||||
import SignerList, { SignerListFlags } from './SignerList'
|
||||
@@ -58,6 +59,7 @@ export {
|
||||
NFToken,
|
||||
Offer,
|
||||
OfferFlags,
|
||||
Oracle,
|
||||
PayChannel,
|
||||
RippleState,
|
||||
RippleStateFlags,
|
||||
|
||||
119
packages/xrpl/src/models/methods/getAggregatePrice.ts
Normal file
119
packages/xrpl/src/models/methods/getAggregatePrice.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { BaseRequest, BaseResponse } from './baseMethod'
|
||||
|
||||
/**
|
||||
* The `get_aggregate_price` method retrieves the aggregate price of specified Oracle objects,
|
||||
* returning three price statistics: mean, median, and trimmed mean.
|
||||
* Returns an {@link GetAggregatePriceResponse}.
|
||||
*
|
||||
* @category Requests
|
||||
*/
|
||||
export interface GetAggregatePriceRequest extends BaseRequest {
|
||||
command: 'get_aggregate_price'
|
||||
|
||||
/**
|
||||
* The currency code of the asset to be priced.
|
||||
*/
|
||||
base_asset: string
|
||||
|
||||
/**
|
||||
* The currency code of the asset to quote the price of the base asset.
|
||||
*/
|
||||
quote_asset: string
|
||||
|
||||
/**
|
||||
* The oracle identifier.
|
||||
*/
|
||||
oracles: Array<{
|
||||
/**
|
||||
* The XRPL account that controls the Oracle object.
|
||||
*/
|
||||
account: string
|
||||
|
||||
/**
|
||||
* A unique identifier of the price oracle for the Account
|
||||
*/
|
||||
oracle_document_id: string | number
|
||||
}>
|
||||
|
||||
/**
|
||||
* The percentage of outliers to trim. Valid trim range is 1-25. If included, the API returns statistics for the trimmed mean.
|
||||
*/
|
||||
trim?: number
|
||||
|
||||
/**
|
||||
* Defines a time range in seconds for filtering out older price data. Default value is 0, which doesn't filter any data.
|
||||
*/
|
||||
trim_threshold?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Response expected from an {@link GetAggregatePriceRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export interface GetAggregatePriceResponse extends BaseResponse {
|
||||
result: {
|
||||
/**
|
||||
* The statistics from the collected oracle prices.
|
||||
*/
|
||||
entire_set: {
|
||||
/**
|
||||
* The simple mean.
|
||||
*/
|
||||
mean: string
|
||||
|
||||
/**
|
||||
* The size of the data set to calculate the mean.
|
||||
*/
|
||||
size: number
|
||||
|
||||
/**
|
||||
* The standard deviation.
|
||||
*/
|
||||
standard_deviation: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The trimmed statistics from the collected oracle prices. Only appears if the trim field was specified in the request.
|
||||
*/
|
||||
trimmed_set?: {
|
||||
/**
|
||||
* The simple mean of the trimmed data.
|
||||
*/
|
||||
mean: string
|
||||
|
||||
/**
|
||||
* The size of the data to calculate the trimmed mean.
|
||||
*/
|
||||
size: number
|
||||
|
||||
/**
|
||||
* The standard deviation of the trimmed data.
|
||||
*/
|
||||
standard_deviation: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The median of the collected oracle prices.
|
||||
*/
|
||||
median: string
|
||||
|
||||
/**
|
||||
* The most recent timestamp out of all LastUpdateTime values.
|
||||
*/
|
||||
time: number
|
||||
|
||||
/**
|
||||
* The ledger index of the ledger version that was used to generate this
|
||||
* response.
|
||||
*/
|
||||
ledger_current_index: number
|
||||
|
||||
/**
|
||||
* If included and set to true, the information in this response comes from
|
||||
* a validated ledger version. Otherwise, the information is subject to
|
||||
* change.
|
||||
*/
|
||||
validated: boolean
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@ import {
|
||||
GatewayBalancesRequest,
|
||||
GatewayBalancesResponse,
|
||||
} from './gatewayBalances'
|
||||
import {
|
||||
GetAggregatePriceRequest,
|
||||
GetAggregatePriceResponse,
|
||||
} from './getAggregatePrice'
|
||||
import {
|
||||
LedgerBinary,
|
||||
LedgerModifiedOfferCreateTransaction,
|
||||
@@ -210,6 +214,8 @@ type Request =
|
||||
| NFTHistoryRequest
|
||||
// AMM methods
|
||||
| AMMInfoRequest
|
||||
// Price Oracle methods
|
||||
| GetAggregatePriceRequest
|
||||
|
||||
/**
|
||||
* @category Responses
|
||||
@@ -264,6 +270,8 @@ type Response =
|
||||
| NFTHistoryResponse
|
||||
// AMM methods
|
||||
| AMMInfoResponse
|
||||
// Price Oracle methods
|
||||
| GetAggregatePriceResponse
|
||||
|
||||
export type RequestResponseMap<T> = T extends AccountChannelsRequest
|
||||
? AccountChannelsResponse
|
||||
@@ -285,6 +293,8 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
|
||||
? AMMInfoResponse
|
||||
: T extends GatewayBalancesRequest
|
||||
? GatewayBalancesResponse
|
||||
: T extends GetAggregatePriceRequest
|
||||
? GetAggregatePriceResponse
|
||||
: T extends NoRippleCheckRequest
|
||||
? NoRippleCheckResponse
|
||||
: // NOTE: The order of these LedgerRequest types is important
|
||||
@@ -473,6 +483,8 @@ export {
|
||||
GatewayBalance,
|
||||
GatewayBalancesRequest,
|
||||
GatewayBalancesResponse,
|
||||
GetAggregatePriceRequest,
|
||||
GetAggregatePriceResponse,
|
||||
NoRippleCheckRequest,
|
||||
NoRippleCheckResponse,
|
||||
// ledger methods
|
||||
|
||||
@@ -58,6 +58,8 @@ export {
|
||||
OfferCreateFlagsInterface,
|
||||
OfferCreate,
|
||||
} from './offerCreate'
|
||||
export { OracleDelete } from './oracleDelete'
|
||||
export { OracleSet } from './oracleSet'
|
||||
export { PaymentFlags, PaymentFlagsInterface, Payment } from './payment'
|
||||
export {
|
||||
PaymentChannelClaimFlags,
|
||||
|
||||
32
packages/xrpl/src/models/transactions/oracleDelete.ts
Normal file
32
packages/xrpl/src/models/transactions/oracleDelete.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
BaseTransaction,
|
||||
isNumber,
|
||||
validateBaseTransaction,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Delete an Oracle ledger entry.
|
||||
*
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export interface OracleDelete extends BaseTransaction {
|
||||
TransactionType: 'OracleDelete'
|
||||
|
||||
/**
|
||||
* A unique identifier of the price oracle for the Account.
|
||||
*/
|
||||
OracleDocumentID: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a OracleDelete at runtime.
|
||||
*
|
||||
* @param tx - A OracleDelete Transaction.
|
||||
* @throws When the OracleDelete is malformed.
|
||||
*/
|
||||
export function validateOracleDelete(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'OracleDocumentID', isNumber)
|
||||
}
|
||||
164
packages/xrpl/src/models/transactions/oracleSet.ts
Normal file
164
packages/xrpl/src/models/transactions/oracleSet.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { PriceData } from '../common'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
isNumber,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
const PRICE_DATA_SERIES_MAX_LENGTH = 10
|
||||
const SCALE_MAX = 10
|
||||
|
||||
/**
|
||||
* Creates a new Oracle ledger entry or updates the fields of an existing one, using the Oracle ID.
|
||||
*
|
||||
* The oracle provider must complete these steps before submitting this transaction:
|
||||
* 1. Create or own the XRPL account in the Owner field and have enough XRP to meet the reserve and transaction fee requirements.
|
||||
* 2. Publish the XRPL account public key, so it can be used for verification by dApps.
|
||||
* 3. Publish a registry of available price oracles with their unique OracleDocumentID.
|
||||
*
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export interface OracleSet extends BaseTransaction {
|
||||
TransactionType: 'OracleSet'
|
||||
|
||||
/**
|
||||
* A unique identifier of the price oracle for the Account.
|
||||
*/
|
||||
OracleDocumentID: number
|
||||
|
||||
/**
|
||||
* The time the data was last updated, represented as a unix timestamp in seconds.
|
||||
*/
|
||||
LastUpdateTime: number
|
||||
|
||||
/**
|
||||
* An array of up to 10 PriceData objects, each representing the price information
|
||||
* for a token pair. More than five PriceData objects require two owner reserves.
|
||||
*/
|
||||
PriceDataSeries: PriceData[]
|
||||
|
||||
/**
|
||||
* An arbitrary value that identifies an oracle provider, such as Chainlink, Band,
|
||||
* or DIA. This field is a string, up to 256 ASCII hex encoded characters (0x20-0x7E).
|
||||
* This field is required when creating a new Oracle ledger entry, but is optional for updates.
|
||||
*/
|
||||
Provider?: string
|
||||
|
||||
/**
|
||||
* An optional Universal Resource Identifier to reference price data off-chain. This field is limited to 256 bytes.
|
||||
*/
|
||||
URI?: string
|
||||
|
||||
/**
|
||||
* Describes the type of asset, such as "currency", "commodity", or "index". This field is a string, up to 16 ASCII
|
||||
* hex encoded characters (0x20-0x7E). This field is required when creating a new Oracle ledger entry, but is optional
|
||||
* for updates.
|
||||
*/
|
||||
AssetClass?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a OracleSet at runtime.
|
||||
*
|
||||
* @param tx - A OracleSet Transaction.
|
||||
* @throws When the OracleSet is malformed.
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function -- necessary to validate many fields
|
||||
export function validateOracleSet(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'OracleDocumentID', isNumber)
|
||||
|
||||
validateRequiredField(tx, 'LastUpdateTime', isNumber)
|
||||
|
||||
validateOptionalField(tx, 'Provider', isString)
|
||||
|
||||
validateOptionalField(tx, 'URI', isString)
|
||||
|
||||
validateOptionalField(tx, 'AssetClass', isString)
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function -- necessary to validate many fields
|
||||
validateRequiredField(tx, 'PriceDataSeries', (value) => {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new ValidationError('OracleSet: PriceDataSeries must be an array')
|
||||
}
|
||||
|
||||
if (value.length > PRICE_DATA_SERIES_MAX_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`OracleSet: PriceDataSeries must have at most ${PRICE_DATA_SERIES_MAX_LENGTH} PriceData objects`,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: add support for handling inner objects easier (similar to validateRequiredField/validateOptionalField)
|
||||
for (const priceData of value) {
|
||||
if (typeof priceData !== 'object') {
|
||||
throw new ValidationError(
|
||||
'OracleSet: PriceDataSeries must be an array of objects',
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
if (priceData.PriceData == null) {
|
||||
throw new ValidationError(
|
||||
'OracleSet: PriceDataSeries must have a `PriceData` object',
|
||||
)
|
||||
}
|
||||
|
||||
// check if priceData only has PriceData
|
||||
if (Object.keys(priceData).length !== 1) {
|
||||
throw new ValidationError(
|
||||
'OracleSet: PriceDataSeries must only have a single PriceData object',
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
if (typeof priceData.PriceData.BaseAsset !== 'string') {
|
||||
throw new ValidationError(
|
||||
'OracleSet: PriceDataSeries must have a `BaseAsset` string',
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
if (typeof priceData.PriceData.QuoteAsset !== 'string') {
|
||||
throw new ValidationError(
|
||||
'OracleSet: PriceDataSeries must have a `QuoteAsset` string',
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
'AssetPrice' in priceData.PriceData &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
!isNumber(priceData.PriceData.AssetPrice)
|
||||
) {
|
||||
throw new ValidationError('OracleSet: invalid field AssetPrice')
|
||||
}
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
'Scale' in priceData.PriceData &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
!isNumber(priceData.PriceData.Scale)
|
||||
) {
|
||||
throw new ValidationError('OracleSet: invalid field Scale')
|
||||
}
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
priceData.PriceData.Scale < 0 ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
|
||||
priceData.PriceData.Scale > SCALE_MAX
|
||||
) {
|
||||
throw new ValidationError(
|
||||
`OracleSet: Scale must be in range 0-${SCALE_MAX}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -43,6 +43,8 @@ import {
|
||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
||||
import { OracleSet, validateOracleSet } from './oracleSet'
|
||||
import { Payment, validatePayment } from './payment'
|
||||
import {
|
||||
PaymentChannelClaim,
|
||||
@@ -120,6 +122,8 @@ export type SubmittableTransaction =
|
||||
| NFTokenMint
|
||||
| OfferCancel
|
||||
| OfferCreate
|
||||
| OracleDelete
|
||||
| OracleSet
|
||||
| Payment
|
||||
| PaymentChannelClaim
|
||||
| PaymentChannelCreate
|
||||
@@ -330,6 +334,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateOfferCreate(tx)
|
||||
break
|
||||
|
||||
case 'OracleDelete':
|
||||
validateOracleDelete(tx)
|
||||
break
|
||||
|
||||
case 'OracleSet':
|
||||
validateOracleSet(tx)
|
||||
break
|
||||
|
||||
case 'Payment':
|
||||
validatePayment(tx)
|
||||
break
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('fundWallet', function () {
|
||||
})
|
||||
|
||||
assert.equal(dropsToXrp(info.result.account_data.Balance), balance)
|
||||
assert.equal(balance, 10000)
|
||||
assert.equal(balance, 1000)
|
||||
|
||||
/*
|
||||
* No test for fund given wallet because the hooks v3 testnet faucet
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { OracleSet } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('get_aggregate_price', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
async () => {
|
||||
const tx: OracleSet = {
|
||||
TransactionType: 'OracleSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
OracleDocumentID: 1234,
|
||||
LastUpdateTime: Math.floor(Date.now() / 1000),
|
||||
PriceDataSeries: [
|
||||
{
|
||||
PriceData: {
|
||||
BaseAsset: 'XRP',
|
||||
QuoteAsset: 'USD',
|
||||
AssetPrice: 740,
|
||||
Scale: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
Provider: stringToHex('chainlink'),
|
||||
URI: '6469645F6578616D706C65',
|
||||
AssetClass: stringToHex('currency'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
|
||||
// confirm that the Oracle was actually created
|
||||
const getAggregatePriceResponse = await testContext.client.request({
|
||||
command: 'get_aggregate_price',
|
||||
account: testContext.wallet.classicAddress,
|
||||
base_asset: 'XRP',
|
||||
quote_asset: 'USD',
|
||||
trim: 20,
|
||||
oracles: [
|
||||
{
|
||||
account: testContext.wallet.classicAddress,
|
||||
oracle_document_id: 1234,
|
||||
},
|
||||
],
|
||||
})
|
||||
assert.deepEqual(getAggregatePriceResponse.result.entire_set, {
|
||||
mean: '0.74',
|
||||
size: 1,
|
||||
standard_deviation: '0',
|
||||
})
|
||||
assert.deepEqual(getAggregatePriceResponse.result.trimmed_set, {
|
||||
mean: '0.74',
|
||||
size: 1,
|
||||
standard_deviation: '0',
|
||||
})
|
||||
assert.equal(getAggregatePriceResponse.result.median, '0.74')
|
||||
assert.equal(getAggregatePriceResponse.result.time, tx.LastUpdateTime)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { OracleSet, OracleDelete } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('OracleDelete', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
async () => {
|
||||
const setTx: OracleSet = {
|
||||
TransactionType: 'OracleSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
OracleDocumentID: 1234,
|
||||
LastUpdateTime: Math.floor(Date.now() / 1000),
|
||||
PriceDataSeries: [
|
||||
{
|
||||
PriceData: {
|
||||
BaseAsset: 'XRP',
|
||||
QuoteAsset: 'USD',
|
||||
AssetPrice: 740,
|
||||
Scale: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
Provider: stringToHex('chainlink'),
|
||||
URI: '6469645F6578616D706C65',
|
||||
AssetClass: stringToHex('currency'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, setTx, testContext.wallet)
|
||||
|
||||
const aoResult = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'oracle',
|
||||
})
|
||||
|
||||
// confirm that the Oracle was created
|
||||
assert.equal(aoResult.result.account_objects.length, 1)
|
||||
|
||||
const deleteTx: OracleDelete = {
|
||||
TransactionType: 'OracleDelete',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
OracleDocumentID: 1234,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, deleteTx, testContext.wallet)
|
||||
|
||||
const aoResult2 = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
})
|
||||
|
||||
// confirm that the Oracle was actually deleted
|
||||
assert.equal(aoResult2.result.account_objects.length, 0)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { OracleSet } from '../../../src'
|
||||
import { Oracle } from '../../../src/models/ledger'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('OracleSet', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
async () => {
|
||||
const tx: OracleSet = {
|
||||
TransactionType: 'OracleSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
OracleDocumentID: 1234,
|
||||
LastUpdateTime: Math.floor(Date.now() / 1000),
|
||||
PriceDataSeries: [
|
||||
{
|
||||
PriceData: {
|
||||
BaseAsset: 'XRP',
|
||||
QuoteAsset: 'USD',
|
||||
AssetPrice: 740,
|
||||
Scale: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
Provider: stringToHex('chainlink'),
|
||||
URI: '6469645F6578616D706C65',
|
||||
AssetClass: stringToHex('currency'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
|
||||
const result = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'oracle',
|
||||
})
|
||||
|
||||
// confirm that the Oracle was actually created
|
||||
assert.equal(result.result.account_objects.length, 1)
|
||||
|
||||
// confirm details of Oracle ledger entry object
|
||||
const oracle = result.result.account_objects[0] as Oracle
|
||||
assert.equal(oracle.LastUpdateTime, tx.LastUpdateTime)
|
||||
assert.equal(oracle.Owner, testContext.wallet.classicAddress)
|
||||
assert.equal(oracle.AssetClass, tx.AssetClass)
|
||||
assert.equal(oracle.Provider, tx.Provider)
|
||||
assert.equal(oracle.PriceDataSeries.length, 1)
|
||||
assert.equal(oracle.PriceDataSeries[0].PriceData.BaseAsset, 'XRP')
|
||||
assert.equal(oracle.PriceDataSeries[0].PriceData.QuoteAsset, 'USD')
|
||||
assert.equal(oracle.PriceDataSeries[0].PriceData.AssetPrice, '2e4')
|
||||
assert.equal(oracle.PriceDataSeries[0].PriceData.Scale, 3)
|
||||
assert.equal(oracle.Flags, 0)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
40
packages/xrpl/test/models/oracleDelete.test.ts
Normal file
40
packages/xrpl/test/models/oracleDelete.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateOracleDelete } from '../../src/models/transactions/oracleDelete'
|
||||
|
||||
/**
|
||||
* OracleDelete Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('OracleDelete', function () {
|
||||
let tx
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
TransactionType: 'OracleDelete',
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
OracleDocumentID: 1234,
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid OracleDelete', function () {
|
||||
assert.doesNotThrow(() => validateOracleDelete(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field OracleDocumentID`, function () {
|
||||
delete tx.OracleDocumentID
|
||||
const errorMessage = 'OracleDelete: missing field OracleDocumentID'
|
||||
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid OracleDocumentID`, function () {
|
||||
tx.OracleDocumentID = '1234'
|
||||
const errorMessage = 'OracleDelete: invalid field OracleDocumentID'
|
||||
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
180
packages/xrpl/test/models/oracleSet.test.ts
Normal file
180
packages/xrpl/test/models/oracleSet.test.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateOracleSet } from '../../src/models/transactions/oracleSet'
|
||||
|
||||
/**
|
||||
* OracleSet Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('OracleSet', function () {
|
||||
let tx
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
TransactionType: 'OracleSet',
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
OracleDocumentID: 1234,
|
||||
LastUpdateTime: 768062172,
|
||||
PriceDataSeries: [
|
||||
{
|
||||
PriceData: {
|
||||
BaseAsset: 'XRP',
|
||||
QuoteAsset: 'USD',
|
||||
AssetPrice: 740,
|
||||
Scale: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
Provider: stringToHex('chainlink'),
|
||||
URI: '6469645F6578616D706C65',
|
||||
AssetClass: stringToHex('currency'),
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid OracleSet', function () {
|
||||
assert.doesNotThrow(() => validateOracleSet(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field OracleDocumentID`, function () {
|
||||
delete tx.OracleDocumentID
|
||||
const errorMessage = 'OracleSet: missing field OracleDocumentID'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid OracleDocumentID`, function () {
|
||||
tx.OracleDocumentID = '1234'
|
||||
const errorMessage = 'OracleSet: invalid field OracleDocumentID'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field LastUpdateTime`, function () {
|
||||
delete tx.LastUpdateTime
|
||||
const errorMessage = 'OracleSet: missing field LastUpdateTime'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid LastUpdateTime`, function () {
|
||||
tx.LastUpdateTime = '768062172'
|
||||
const errorMessage = 'OracleSet: invalid field LastUpdateTime'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing invalid Provider`, function () {
|
||||
tx.Provider = 1234
|
||||
const errorMessage = 'OracleSet: invalid field Provider'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing invalid URI`, function () {
|
||||
tx.URI = 1234
|
||||
const errorMessage = 'OracleSet: invalid field URI'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing invalid AssetClass`, function () {
|
||||
tx.AssetClass = 1234
|
||||
const errorMessage = 'OracleSet: invalid field AssetClass'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid PriceDataSeries must be an array`, function () {
|
||||
tx.PriceDataSeries = 1234
|
||||
const errorMessage = 'OracleSet: PriceDataSeries must be an array'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid PriceDataSeries must be an array of objects`, function () {
|
||||
tx.PriceDataSeries = [1234]
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must be an array of objects'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ PriceDataSeries must have at most 10 PriceData objects`, function () {
|
||||
tx.PriceDataSeries = new Array(11).fill({
|
||||
PriceData: {
|
||||
BaseAsset: 'XRP',
|
||||
QuoteAsset: 'USD',
|
||||
AssetPrice: 740,
|
||||
Scale: 3,
|
||||
},
|
||||
})
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must have at most 10 PriceData objects'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ PriceDataSeries must have a PriceData object`, function () {
|
||||
delete tx.PriceDataSeries[0].PriceData
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must have a `PriceData` object'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ PriceDataSeries must only have a single PriceData object`, function () {
|
||||
tx.PriceDataSeries[0].ExtraProp = 'extraprop'
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must only have a single PriceData object'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing BaseAsset of PriceDataSeries`, function () {
|
||||
delete tx.PriceDataSeries[0].PriceData.BaseAsset
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must have a `BaseAsset` string'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing QuoteAsset of PriceDataSeries`, function () {
|
||||
delete tx.PriceDataSeries[0].PriceData.QuoteAsset
|
||||
const errorMessage =
|
||||
'OracleSet: PriceDataSeries must have a `QuoteAsset` string'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid AssetPrice of PriceDataSeries`, function () {
|
||||
tx.PriceDataSeries[0].PriceData.AssetPrice = '1234'
|
||||
const errorMessage = 'OracleSet: invalid field AssetPrice'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid Scale of PriceDataSeries`, function () {
|
||||
tx.PriceDataSeries[0].PriceData.Scale = '1234'
|
||||
const errorMessage = 'OracleSet: invalid field Scale'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Scale must be in range 0-10 when above max`, function () {
|
||||
tx.PriceDataSeries[0].PriceData.Scale = 11
|
||||
const errorMessage = 'OracleSet: Scale must be in range 0-10'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Scale must be in range 0-10 when below min`, function () {
|
||||
tx.PriceDataSeries[0].PriceData.Scale = -1
|
||||
const errorMessage = 'OracleSet: Scale must be in range 0-10'
|
||||
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user