Skip to content

Commit 1834779

Browse files
authored
Add stale price check: check current slot when parsing price data (#20)
- Checks current slot when parsing a Price account and set status to unknown if price is stale. Being stale means it is not updated in `MAX_SLOT_DIFFERENCE` slots (currently 25). - Adds status field in PriceData to access the current status (considering price getting stale) easier. - Also converts Some type/status structs to enums to be able to use them in a cleaner way. It's backward compatible.
1 parent 34aac82 commit 1834779

File tree

7 files changed

+150
-42
lines changed

7 files changed

+150
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const pythConnection = new PythConnection(solanaWeb3Connection, getPythProgramKe
3131
pythConnection.onPriceChange((product, price) => {
3232
// sample output:
3333
// SRM/USD: $8.68725 ±$0.0131
34-
console.log(`${product.symbol}: $${price.price} \xB1$${price.confidence}`)
34+
console.log(`${product.symbol}: $${price.price} \xB1$${price.confidence} Status: ${PriceStatus[price.status]}`)
3535
})
3636

3737
// Start listening for price change events.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/PythConnection.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
ProductData,
1313
Version,
1414
AccountType,
15+
MAX_SLOT_DIFFERENCE,
16+
PriceStatus,
1517
} from './index'
1618

1719
const ONES = '11111111111111111111111111111111'
@@ -44,7 +46,7 @@ export class PythConnection {
4446
}
4547
}
4648

47-
private handlePriceAccount(key: PublicKey, account: AccountInfo<Buffer>) {
49+
private handlePriceAccount(key: PublicKey, account: AccountInfo<Buffer>, slot: number) {
4850
const product = this.productAccountKeyToProduct[this.priceAccountKeyToProductAccountKey[key.toString()]]
4951
if (product === undefined) {
5052
// This shouldn't happen since we're subscribed to all of the program's accounts,
@@ -54,29 +56,30 @@ export class PythConnection {
5456
)
5557
}
5658

57-
const priceData = parsePriceData(account.data)
59+
const priceData = parsePriceData(account.data, slot)
60+
5861
for (const callback of this.callbacks) {
5962
callback(product, priceData)
6063
}
6164
}
6265

63-
private handleAccount(key: PublicKey, account: AccountInfo<Buffer>, productOnly: boolean) {
66+
private handleAccount(key: PublicKey, account: AccountInfo<Buffer>, productOnly: boolean, slot: number) {
6467
const base = parseBaseData(account.data)
6568
// The pyth program owns accounts that don't contain pyth data, which we can safely ignore.
6669
if (base) {
67-
switch (AccountType[base.type]) {
68-
case 'Mapping':
70+
switch (base.type) {
71+
case AccountType.Mapping:
6972
// We can skip these because we're going to get every account owned by this program anyway.
7073
break
71-
case 'Product':
74+
case AccountType.Product:
7275
this.handleProductAccount(key, account)
7376
break
74-
case 'Price':
77+
case AccountType.Price:
7578
if (!productOnly) {
76-
this.handlePriceAccount(key, account)
79+
this.handlePriceAccount(key, account, slot)
7780
}
7881
break
79-
case 'Test':
82+
case AccountType.Test:
8083
break
8184
default:
8285
throw new Error(`Unknown account type: ${base.type}. Try upgrading pyth-client.`)
@@ -98,14 +101,15 @@ export class PythConnection {
98101
*/
99102
public async start() {
100103
const accounts = await this.connection.getProgramAccounts(this.pythProgramKey, this.commitment)
104+
const currentSlot = await this.connection.getSlot(this.commitment)
101105
for (const account of accounts) {
102-
this.handleAccount(account.pubkey, account.account, true)
106+
this.handleAccount(account.pubkey, account.account, true, currentSlot)
103107
}
104108

105109
this.connection.onProgramAccountChange(
106110
this.pythProgramKey,
107111
(keyedAccountInfo, context) => {
108-
this.handleAccount(keyedAccountInfo.accountId, keyedAccountInfo.accountInfo, false)
112+
this.handleAccount(keyedAccountInfo.accountId, keyedAccountInfo.accountInfo, false, context.slot)
109113
},
110114
this.commitment,
111115
)

src/PythHttpClient.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,16 @@ export class PythHttpClient {
4343
// Popolate producs and prices
4444
const priceDataQueue = new Array<PriceData>();
4545
const productAccountKeyToProduct = new Map<string, Product>();
46+
const currentSlot = await this.connection.getSlot(this.commitment);
4647

4748
accountList.forEach(singleAccount => {
4849
const base = parseBaseData(singleAccount.account.data);
4950
if (base) {
50-
switch (AccountType[base.type]) {
51-
case 'Mapping':
51+
switch (base.type) {
52+
case AccountType.Mapping:
5253
// We can skip these because we're going to get every account owned by this program anyway.
5354
break;
54-
case 'Product':
55+
case AccountType.Product:
5556
const productData = parseProductData(singleAccount.account.data)
5657

5758
productAccountKeyToProduct.set(singleAccount.pubkey.toBase58(), productData.product)
@@ -60,11 +61,11 @@ export class PythHttpClient {
6061
products.add(productData.product);
6162
productFromSymbol.set(productData.product.symbol, productData.product);
6263
break;
63-
case 'Price':
64-
const priceData = parsePriceData(singleAccount.account.data)
64+
case AccountType.Price:
65+
const priceData = parsePriceData(singleAccount.account.data, currentSlot)
6566
priceDataQueue.push(priceData)
6667
break;
67-
case 'Test':
68+
case AccountType.Test:
6869
break;
6970
default:
7071
throw new Error(`Unknown account type: ${base.type}. Try upgrading pyth-client.`)

src/__tests__/Price.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
2-
import { Magic, parseMappingData, parsePriceData, parseProductData, Version } from '../index'
2+
import { Magic, MAX_SLOT_DIFFERENCE, parseMappingData, parsePriceData, parseProductData, PriceStatus, Version } from '../index'
33

44
test('Price', (done) => {
55
jest.setTimeout(60000)
@@ -39,3 +39,69 @@ test('Price', (done) => {
3939
done(error)
4040
})
4141
})
42+
43+
test('Handle price getting stale', (done) => {
44+
jest.setTimeout(60000)
45+
46+
const b64_data =
47+
'1MOyoQIAAAADAAAAEAsAAAEAAAD4////GwAAABUAAAC6yDcHAAAAALnINwcAAAAA4Gbo+kgAAAB/BW14AQAAAHyfF3gAAAAAEF8QBg' +
48+
'AAAAB8ZS16AAAAAHyfF3gAAAAAAQAAAAAAAAADAAAAAAAAAMZ5QL5A4Mx/+qGssI7j+rMJVaGX2h7Cl6sTPU1D2G7mAAAAAAAAAAAA' +
49+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4yDcHAAAAAKASKJtIAAAAWKPpBgAAAAAAAAAAAAAAAFADCptIAAAAYJmfBwAAAAABAAAAAA' +
50+
'AAALrINwcAAAAA45Qx5CsqvbDhOJtnIpvcng1PorJw47e2KQXX0YVPmjUAzIpLSQAAAID7mioAAAAAAQAAAAAAAAAftTYHAAAAAADM' +
51+
'iktJAAAAgPuaKgAAAAABAAAAAAAAAB+1NgcAAAAAFg+6wTr33dgF0xcKPeDGvZcSah4CwNJZ0Khu+CHW5cdABUKUSAAAAO7GFQMAAA' +
52+
'AAAQAAAAAAAAC1yDcHAAAAAEAFQpRIAAAA7sYVAwAAAAABAAAAAAAAALfINwcAAAAAefrfLNzEqEaqlX3ag5+StobUMoSgAaOeei6y' +
53+
'+250Ge1YoKVkWQAAAFjkyAgAAAAAAQAAAAAAAABAmaMGAAAAAFigpWRZAAAAWOTICAAAAAABAAAAAAAAAECZowYAAAAAibazYiCMIT' +
54+
'lc2drXqvTlt3fSCnk7W1heG3EouJogjZfz93OeSAAAAPaTSwkAAAAAAQAAAAAAAAC0yDcHAAAAAM9xPZ5IAAAA/IxLCQAAAAABAAAA' +
55+
'AAAAALfINwcAAAAAGuUCo+lCuLEcw3RJa02cXDn4DF2sH0WfNaIOwbhzCJ1AE2mbSAAAAACZDQQAAAAAAQAAAAAAAACPyDcHAAAAAC' +
56+
'BXqppIAAAAYIVhBAAAAAABAAAAAAAAAJHINwcAAAAABdIGTzMc/93Kvpb6NlUk3WT0s4bohaNNFSy+rgQs6vWuk/6fSAAAACOqFQcA' +
57+
'AAAAAQAAAAAAAAC1yDcHAAAAAK6T/p9IAAAA3dsNBgAAAAABAAAAAAAAALfINwcAAAAACdnkh8pVcvbU1GrqbrtPcukAy9tzmqwQXg' +
58+
'CzlnP/Vh4GUx+WSAAAAD2NsRYAAAAAAQAAAAAAAAC0yDcHAAAAAJ82B5NIAAAAgD0pGQAAAAABAAAAAAAAALbINwcAAAAAqerT49NL' +
59+
'zIU0uyxlDrJ6jIw/GiLWiChDKFOMQqjoI0NAm9HAPAAAAH+7Iw8AAAAAAQAAAAAAAABgMhoHAAAAAECb0cA8AAAAf7sjDwAAAAABAA' +
60+
'AAAAAAAGAyGgcAAAAA4rmPJpCE1IgFEcgRXc7wT1PH4geZLgP/0SbflE3k2wRACZWNSAAAAEBCDwAAAAAAAQAAAAAAAAC1yDcHAAAA' +
61+
'AEAJlY1IAAAAQEIPAAAAAAABAAAAAAAAALXINwcAAAAAB/LLOf2wKdxReE0o7xeRHZfBppyFcjobYlWzQlNDrXWAXrWbSAAAAAAtMQ' +
62+
'EAAAAAAQAAAAAAAACzyDcHAAAAAIBetZtIAAAAAC0xAQAAAAABAAAAAAAAALPINwcAAAAAJP62d6a8xTc1CmcY+0DmTwuAo2S1z/fu' +
63+
'ZczNVzwgzQ9jMkmaSAAAAMCuOygAAAAAAQAAAAAAAAC1yDcHAAAAAEuP0plIAAAAwK47KAAAAAABAAAAAAAAALfINwcAAAAADcO86p' +
64+
'FVaXYIsF8EpwrQmkFRcqqbtfdAhzrAeCsrGkVAZ0qJSAAAAATCjLsAAAAAAQAAAAAAAADDZzYHAAAAAEBnSolIAAAABMKMuwAAAAAB' +
65+
'AAAAAAAAAMNnNgcAAAAAnz6le9QJugDEDZKuVxNBwn48L37frOHCSlGxoVwxsrfgISuUSAAAAMB68gQAAAAAAQAAAAAAAACzyDcHAA' +
66+
'AAAOAhK5RIAAAAwHryBAAAAAABAAAAAAAAALbINwcAAAAAQ4KPo2Gdpryu1okX3h18zpIX3scrrhIwY/97590vlj4g2VOXSAAAAC9z' +
67+
'zQIAAAAAAQAAAAAAAAC1yDcHAAAAAEBxHpdIAAAASNHFAgAAAAABAAAAAAAAALfINwcAAAAAGIOxJG3aXQcXPb041WcABxWELB/Q6J' +
68+
'bnCwpt0uUaT5cgYOeaSAAAAGCOngQAAAAAAQAAAAAAAACxyDcHAAAAAMBY3JlIAAAAgC+mBAAAAAABAAAAAAAAALbINwcAAAAAn2pD' +
69+
'Hx87OIRmUmOPitxO8uWtC/ysp/PHeaLfwhx8dSf/ytiGSAAAAP/g9QUAAAAAAQAAAAAAAAB3yDcHAAAAAP/K2IZIAAAA/+D1BQAAAA' +
70+
'ABAAAAAAAAAHfINwcAAAAAQ7d4S2+FZssyg2X7zgtARhUjuuznhRezFVwx2qM4KY/AfNCaSAAAAP8foQcAAAAAAQAAAAAAAAC0yDcH' +
71+
'AAAAAKClWplIAAAAH8GoBwAAAAABAAAAAAAAALfINwcAAAAA9Z3d78wWay2JpKPM8/7Eu0uYoVG0wDf/YV67eELjPXWgGnScSAAAAO' +
72+
'BRaAYAAAAAAQAAAAAAAACwyDcHAAAAAKAadJxIAAAA4FFoBgAAAAABAAAAAAAAALDINwcAAAAA0MozHPXZ7nFryMaQowCrqEA7NxQc' +
73+
'tjsCZcCYwMWOY6zAhQ2bSAAAAAYqPgUAAAAAAQAAAAAAAAC1yDcHAAAAACBXqppIAAAATTbqBAAAAAABAAAAAAAAALfINwcAAAAAvF' +
74+
'RslRVZlbwHP1fHn9TC4H0gHT4cvadEJLsMYazqQb7g9PmSSAAAACA//woAAAAAAQAAAAAAAAC0yDcHAAAAAOD0+ZJIAAAAID//CgAA' +
75+
'AAABAAAAAAAAALfINwcAAAAAMRuB0W8DQxLQhWNGn9A26Vs6Zd9SkHugsRL/ifhdwmamhD2jSAAAAKthmBIAAAAAAQAAAAAAAAC1yD' +
76+
'cHAAAAAKaEPaNIAAAAq2GYEgAAAAABAAAAAAAAALXINwcAAAAAf4BTJ2kp9OgaB+ZMWleZBpkj76iE3CdHHzO3YVCMTh/gL1KbSAAA' +
77+
'AOBwcgAAAAAAAQAAAAAAAAC0yDcHAAAAAGBaDplIAAAAIMX7AAAAAAABAAAAAAAAALfINwcAAAAABHkihWa8qHaHujLYgFXDIwjMb1' +
78+
'piz6Z/GIGZQeOFsrLAalaaSAAAAAB6AwoAAAAAAQAAAAAAAACxyDcHAAAAAIAoR5pIAAAAgEpdBQAAAAABAAAAAAAAALfINwcAAAAA' +
79+
'fcK1rXWbYoQKtCq2nzJiCmvpYCTjfvXYuWgji0GQsGpqxReTSAAAAGQrxgkAAAAAAQAAAAAAAAC1yDcHAAAAALKht5JIAAAADJRqCQ' +
80+
'AAAAABAAAAAAAAALfINwcAAAAAQzSbO2mHWTxl9wARIruF2PbxX/03UasIR4i2h2B6He3A+iaeSAAAAACVugoAAAAAAQAAAAAAAAC0' +
81+
'yDcHAAAAAICdYJ1IAAAAAJW6CgAAAAABAAAAAAAAALfINwcAAAAAE3OB0D0ZD0KqsBVhd9DT6HJgQCrpXa9YKWoFPY/jselAjNWXSA' +
82+
'AAAD8jBQYAAAAAAQAAAAAAAAC0yDcHAAAAAECM1ZdIAAAAPyMFBgAAAAABAAAAAAAAALfINwcAAAAAsdAzM1QQJ5F9okFqcfs4Y6PP' +
83+
'cqPB7MdqSFXQjYAY9UEgV6qaSAAAAEDgBgsAAAAAAQAAAAAAAAC1yDcHAAAAAGBaDplIAAAAwObOBwAAAAABAAAAAAAAALfINwcAAA' +
84+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
85+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
86+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
87+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
88+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
89+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
90+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
91+
92+
const data = Buffer.from(b64_data, 'base64')
93+
const price = parsePriceData(data)
94+
console.log(price)
95+
expect(price.magic).toBe(Magic)
96+
expect(price.version).toBe(Version)
97+
expect(price.status).toBe(PriceStatus.Trading);
98+
99+
expect(parsePriceData(data, price.aggregate.publishSlot + MAX_SLOT_DIFFERENCE).status).toBe(PriceStatus.Trading)
100+
101+
const stalePrice = parsePriceData(data, price.aggregate.publishSlot + MAX_SLOT_DIFFERENCE + 1)
102+
expect(stalePrice.status).toBe(PriceStatus.Unknown)
103+
expect(stalePrice.price).toBeUndefined()
104+
expect(stalePrice.confidence).toBeUndefined()
105+
106+
done()
107+
})

src/example_usage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Cluster, clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
22
import { PythConnection } from './PythConnection'
33
import { getPythProgramKeyForCluster } from './cluster'
4+
import { PriceStatus } from '.'
45

56
const SOLANA_CLUSTER_NAME: Cluster = 'devnet'
67
const connection = new Connection(clusterApiUrl(SOLANA_CLUSTER_NAME))
@@ -15,7 +16,7 @@ pythConnection.onPriceChange((product, price) => {
1516
console.log(`${product.symbol}: $${price.price} \xB1$${price.confidence}`)
1617
} else {
1718
// tslint:disable-next-line:no-console
18-
console.log(`${product.symbol}: price currently unavailable`)
19+
console.log(`${product.symbol}: price currently unavailable. status is ${PriceStatus[price.status]}`)
1920
}
2021
})
2122

src/index.ts

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,46 @@ import { readBigInt64LE, readBigUInt64LE } from './readBig'
77
export const Magic = 0xa1b2c3d4
88
export const Version2 = 2
99
export const Version = Version2
10-
export const PriceStatus = ['Unknown', 'Trading', 'Halted', 'Auction']
11-
export const CorpAction = ['NoCorpAct']
12-
export const PriceType = ['Unknown', 'Price']
13-
export const DeriveType = ['Unknown', 'TWAP', 'Volatility']
14-
export const AccountType = ['Unknown', 'Mapping', 'Product', 'Price', 'Test']
15-
1610
/** Number of slots that can pass before a publisher's price is no longer included in the aggregate. */
1711
export const MAX_SLOT_DIFFERENCE = 25
1812

13+
export enum PriceStatus {
14+
Unknown,
15+
Trading,
16+
Halted,
17+
Auction
18+
}
19+
20+
export enum CorpAction {
21+
NoCorpAct
22+
}
23+
24+
export enum PriceType {
25+
Unknown,
26+
Price
27+
}
28+
29+
export enum DeriveType {
30+
Unknown,
31+
TWAP,
32+
Volatility
33+
}
34+
35+
export enum AccountType {
36+
Unknown,
37+
Mapping,
38+
Product,
39+
Price,
40+
Test
41+
}
42+
1943
const empty32Buffer = Buffer.alloc(32)
2044
const PKorNull = (data: Buffer) => (data.equals(empty32Buffer) ? null : new PublicKey(data))
2145

2246
export interface Base {
2347
magic: number
2448
version: number
25-
type: number
49+
type: AccountType
2650
size: number
2751
}
2852

@@ -49,9 +73,9 @@ export interface Price {
4973
price: number
5074
confidenceComponent: bigint
5175
confidence: number
52-
status: number
53-
corporateAction: number
54-
publishSlot: bigint
76+
status: PriceStatus
77+
corporateAction: CorpAction
78+
publishSlot: number
5579
}
5680

5781
export interface PriceComponent {
@@ -68,7 +92,7 @@ export interface Ema {
6892
}
6993

7094
export interface PriceData extends Base {
71-
priceType: number
95+
priceType: PriceType
7296
exponent: number
7397
numComponentPrices: number
7498
numQuoters: number
@@ -93,14 +117,15 @@ export interface PriceData extends Base {
93117
drv5: number
94118
priceComponents: PriceComponent[]
95119
aggregate: Price
96-
// The current price and confidence. The typical use of this interface is to consume these two fields.
120+
// The current price and confidence and status. The typical use of this interface is to consume these three fields.
97121
// If undefined, Pyth does not currently have price information for this product. This condition can
98122
// happen for various reasons (e.g., US equity market is closed, or insufficient publishers), and your
99123
// application should handle it gracefully. Note that other raw price information fields (such as
100124
// aggregate.price) may be defined even if this is undefined; you most likely should not use those fields,
101125
// as their value can be arbitrary when this is undefined.
102126
price: number | undefined
103127
confidence: number | undefined
128+
status: PriceStatus
104129
}
105130

106131
/** Parse data as a generic Pyth account. Use this method if you don't know the account type. */
@@ -115,7 +140,7 @@ export function parseBaseData(data: Buffer): Base | undefined {
115140
// program version
116141
const version = data.readUInt32LE(4)
117142
// account type
118-
const type = data.readUInt32LE(8)
143+
const type: AccountType = data.readUInt32LE(8)
119144
// account used size
120145
const size = data.readUInt32LE(12)
121146
return { magic, version, type, size }
@@ -207,11 +232,11 @@ const parsePriceInfo = (data: Buffer, exponent: number): Price => {
207232
const confidenceComponent = readBigUInt64LE(data, 8)
208233
const confidence = Number(confidenceComponent) * 10 ** exponent
209234
// aggregate status
210-
const status = data.readUInt32LE(16)
235+
const status: PriceStatus = data.readUInt32LE(16)
211236
// aggregate corporate action
212-
const corporateAction = data.readUInt32LE(20)
213-
// aggregate publish slot
214-
const publishSlot = readBigUInt64LE(data, 24)
237+
const corporateAction: CorpAction = data.readUInt32LE(20)
238+
// aggregate publish slot. It is converted to number to be consistent with Solana's library interface (Slot there is number)
239+
const publishSlot = Number(readBigUInt64LE(data, 24))
215240
return {
216241
priceComponent,
217242
price,
@@ -223,7 +248,9 @@ const parsePriceInfo = (data: Buffer, exponent: number): Price => {
223248
}
224249
}
225250

226-
export const parsePriceData = (data: Buffer): PriceData => {
251+
// Provide currentSlot when available to allow status to consider the case when price goes stale. It is optional because
252+
// it requires an extra request to get it when it is not available which is not always efficient.
253+
export const parsePriceData = (data: Buffer, currentSlot?: number): PriceData => {
227254
// pyth magic number
228255
const magic = data.readUInt32LE(0)
229256
// program version
@@ -233,7 +260,7 @@ export const parsePriceData = (data: Buffer): PriceData => {
233260
// price account size
234261
const size = data.readUInt32LE(12)
235262
// price or calculation type
236-
const priceType = data.readUInt32LE(16)
263+
const priceType: PriceType = data.readUInt32LE(16)
237264
// price exponent
238265
const exponent = data.readInt32LE(20)
239266
// number of component prices
@@ -276,9 +303,17 @@ export const parsePriceData = (data: Buffer): PriceData => {
276303
const drv5 = Number(drv5Component) * 10 ** exponent
277304
const aggregate = parsePriceInfo(data.slice(208, 240), exponent)
278305

306+
let status = aggregate.status
307+
308+
if (currentSlot && status === PriceStatus.Trading) {
309+
if(currentSlot - aggregate.publishSlot > MAX_SLOT_DIFFERENCE) {
310+
status = PriceStatus.Unknown
311+
}
312+
}
313+
279314
let price
280315
let confidence
281-
if (aggregate.status === 1) {
316+
if (status === PriceStatus.Trading) {
282317
price = aggregate.price
283318
confidence = aggregate.confidence
284319
}
@@ -333,6 +368,7 @@ export const parsePriceData = (data: Buffer): PriceData => {
333368
priceComponents,
334369
price,
335370
confidence,
371+
status,
336372
}
337373
}
338374

0 commit comments

Comments
 (0)