-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathindex.ts
415 lines (381 loc) · 11.9 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
import { PublicKey } from '@solana/web3.js'
import { Buffer } from 'buffer'
import { readBigInt64LE, readBigUInt64LE } from './readBig'
/** Constants. This section must be kept in sync with the on-chain program. */
export const Magic = 0xa1b2c3d4
export const Version2 = 2
export const Version = Version2
/** Number of slots that can pass before a publisher's price is no longer included in the aggregate. */
export const MAX_SLOT_DIFFERENCE = 25
export enum PriceStatus {
Unknown,
Trading,
Halted,
Auction,
Ignored,
}
export enum CorpAction {
NoCorpAct,
}
export enum PriceType {
Unknown,
Price,
}
export enum DeriveType {
Unknown,
Volatility,
}
export enum AccountType {
Unknown,
Mapping,
Product,
Price,
Test,
Permission,
}
export type Flags = {
accumulatorV2: boolean
messageBufferCleared: boolean
}
const empty32Buffer = Buffer.alloc(32)
const PKorNull = (data: Buffer) => (data.equals(empty32Buffer) ? null : new PublicKey(data))
export interface Base {
magic: number
version: number
type: AccountType
size: number
}
export interface MappingData extends Base {
nextMappingAccount: PublicKey | null
productAccountKeys: PublicKey[]
}
export interface Product {
[index: string]: string
}
export interface ProductData extends Base {
priceAccountKey: PublicKey | null
product: Product
}
export interface Price {
priceComponent: bigint
price: number
confidenceComponent: bigint
confidence: number
status: PriceStatus
corporateAction: CorpAction
publishSlot: number
}
export interface PriceComponent {
publisher: PublicKey
aggregate: Price
latest: Price
}
/**
* valueComponent = numerator / denominator
* value = valueComponent * 10 ^ exponent (from PriceData)
*/
export interface Ema {
valueComponent: bigint
value: number
numerator: bigint
denominator: bigint
}
export interface PriceData extends Base {
priceType: PriceType
exponent: number
numComponentPrices: number
numQuoters: number
lastSlot: bigint
validSlot: bigint
emaPrice: Ema
emaConfidence: Ema
timestamp: bigint
minPublishers: number
messageSent: number
maxLatency: number
flags: Flags
feedIndex: number
productAccountKey: PublicKey
nextPriceAccountKey: PublicKey | null
previousSlot: bigint
previousPriceComponent: bigint
previousPrice: number
previousConfidenceComponent: bigint
previousConfidence: number
previousTimestamp: bigint
priceComponents: PriceComponent[]
aggregate: Price
// The current price and confidence and status. The typical use of this interface is to consume these three fields.
// If undefined, Pyth does not currently have price information for this product. This condition can
// happen for various reasons (e.g., US equity market is closed, or insufficient publishers), and your
// application should handle it gracefully. Note that other raw price information fields (such as
// aggregate.price) may be defined even if this is undefined; you most likely should not use those fields,
// as their value can be arbitrary when this is undefined.
price: number | undefined
confidence: number | undefined
status: PriceStatus
}
export interface PermissionData extends Base {
masterAuthority: PublicKey
dataCurationAuthority: PublicKey
securityAuthority: PublicKey
}
/** Parse data as a generic Pyth account. Use this method if you don't know the account type. */
export function parseBaseData(data: Buffer): Base | undefined {
// data is too short to have the magic number.
if (data.byteLength < 4) {
return undefined
}
const magic = data.readUInt32LE(0)
if (magic === Magic) {
// program version
const version = data.readUInt32LE(4)
// account type
const type: AccountType = data.readUInt32LE(8)
// account used size
const size = data.readUInt32LE(12)
return { magic, version, type, size }
} else {
return undefined
}
}
export const parseMappingData = (data: Buffer): MappingData => {
// pyth magic number
const magic = data.readUInt32LE(0)
// program version
const version = data.readUInt32LE(4)
// account type
const type = data.readUInt32LE(8)
// account used size
const size = data.readUInt32LE(12)
// number of product accounts
const numProducts = data.readUInt32LE(16)
// unused
// const unused = accountInfo.data.readUInt32LE(20)
// next mapping account (if any)
const nextMappingAccount = PKorNull(data.slice(24, 56))
// read each symbol account
let offset = 56
const productAccountKeys: PublicKey[] = []
for (let i = 0; i < numProducts; i++) {
const productAccountBytes = data.slice(offset, offset + 32)
const productAccountKey = new PublicKey(productAccountBytes)
offset += 32
productAccountKeys.push(productAccountKey)
}
return {
magic,
version,
type,
size,
nextMappingAccount,
productAccountKeys,
}
}
export const parseProductData = (data: Buffer): ProductData => {
// pyth magic number
const magic = data.readUInt32LE(0)
// program version
const version = data.readUInt32LE(4)
// account type
const type = data.readUInt32LE(8)
// price account size
const size = data.readUInt32LE(12)
// first price account in list
const priceAccountBytes = data.slice(16, 48)
const priceAccountKey = PKorNull(priceAccountBytes)
const product = {} as Product
if (priceAccountKey) product.price_account = priceAccountKey.toBase58()
let idx = 48
while (idx < size) {
const keyLength = data[idx]
idx++
if (keyLength) {
const key = data.slice(idx, idx + keyLength).toString()
idx += keyLength
const valueLength = data[idx]
idx++
const value = data.slice(idx, idx + valueLength).toString()
idx += valueLength
product[key] = value
}
}
return { magic, version, type, size, priceAccountKey, product }
}
const parseEma = (data: Buffer, exponent: number): Ema => {
// current value of ema
const valueComponent = readBigInt64LE(data, 0)
const value = Number(valueComponent) * 10 ** exponent
// numerator state for next update
const numerator = readBigInt64LE(data, 8)
// denominator state for next update
const denominator = readBigInt64LE(data, 16)
return { valueComponent, value, numerator, denominator }
}
const parsePriceInfo = (data: Buffer, exponent: number): Price => {
// aggregate price
const priceComponent = readBigInt64LE(data, 0)
const price = Number(priceComponent) * 10 ** exponent
// aggregate confidence
const confidenceComponent = readBigUInt64LE(data, 8)
const confidence = Number(confidenceComponent) * 10 ** exponent
// aggregate status
const status: PriceStatus = data.readUInt32LE(16)
// aggregate corporate action
const corporateAction: CorpAction = data.readUInt32LE(20)
// aggregate publish slot. It is converted to number to be consistent with Solana's library interface (Slot there is number)
const publishSlot = Number(readBigUInt64LE(data, 24))
return {
priceComponent,
price,
confidenceComponent,
confidence,
status,
corporateAction,
publishSlot,
}
}
// Provide currentSlot when available to allow status to consider the case when price goes stale. It is optional because
// it requires an extra request to get it when it is not available which is not always efficient.
export const parsePriceData = (data: Buffer, currentSlot?: number): PriceData => {
// pyth magic number
const magic = data.readUInt32LE(0)
// program version
const version = data.readUInt32LE(4)
// account type
const type = data.readUInt32LE(8)
// price account size
const size = data.readUInt32LE(12)
// price or calculation type
const priceType: PriceType = data.readUInt32LE(16)
// price exponent
const exponent = data.readInt32LE(20)
// number of component prices
const numComponentPrices = data.readUInt32LE(24)
// number of quoters that make up aggregate
const numQuoters = data.readUInt32LE(28)
// slot of last valid (not unknown) aggregate price
const lastSlot = readBigUInt64LE(data, 32)
// valid on-chain slot of aggregate price
const validSlot = readBigUInt64LE(data, 40)
// exponential moving average price
const emaPrice = parseEma(data.slice(48, 72), exponent)
// exponential moving average confidence interval
const emaConfidence = parseEma(data.slice(72, 96), exponent)
// timestamp of the current price
const timestamp = readBigInt64LE(data, 96)
// minimum number of publishers for status to be TRADING
const minPublishers = data.readUInt8(104)
// message sent flag
const messageSent = data.readUInt8(105)
// configurable max latency in slots between send and receive
const maxLatency = data.readUInt8(106)
// Various flags (used for operations)
const flagBits = data.readInt8(107)
/* tslint:disable:no-bitwise */
const flags = {
accumulatorV2: (flagBits & (1 << 0)) !== 0,
messageBufferCleared: (flagBits & (1 << 1)) !== 0,
}
/* tslint:enable:no-bitwise */
// Globally immutable unique price feed index used for publishing.
const feedIndex = data.readInt32LE(108)
// product id / reference account
const productAccountKey = new PublicKey(data.slice(112, 144))
// next price account in list
const nextPriceAccountKey = PKorNull(data.slice(144, 176))
// valid slot of previous update
const previousSlot = readBigUInt64LE(data, 176)
// aggregate price of previous update
const previousPriceComponent = readBigInt64LE(data, 184)
const previousPrice = Number(previousPriceComponent) * 10 ** exponent
// confidence interval of previous update
const previousConfidenceComponent = readBigUInt64LE(data, 192)
const previousConfidence = Number(previousConfidenceComponent) * 10 ** exponent
// space for future derived values
const previousTimestamp = readBigInt64LE(data, 200)
const aggregate = parsePriceInfo(data.slice(208, 240), exponent)
let status = aggregate.status
if (currentSlot && status === PriceStatus.Trading) {
if (currentSlot - aggregate.publishSlot > MAX_SLOT_DIFFERENCE) {
status = PriceStatus.Unknown
}
}
let price
let confidence
if (status === PriceStatus.Trading) {
price = aggregate.price
confidence = aggregate.confidence
}
// price components - up to 32
const priceComponents: PriceComponent[] = []
let offset = 240
while (priceComponents.length < numComponentPrices) {
const publisher = new PublicKey(data.slice(offset, offset + 32))
offset += 32
const componentAggregate = parsePriceInfo(data.slice(offset, offset + 32), exponent)
offset += 32
const latest = parsePriceInfo(data.slice(offset, offset + 32), exponent)
offset += 32
priceComponents.push({ publisher, aggregate: componentAggregate, latest })
}
return {
magic,
version,
type,
size,
priceType,
exponent,
numComponentPrices,
numQuoters,
lastSlot,
validSlot,
emaPrice,
emaConfidence,
timestamp,
minPublishers,
messageSent,
maxLatency,
flags,
feedIndex,
productAccountKey,
nextPriceAccountKey,
previousSlot,
previousPriceComponent,
previousPrice,
previousConfidenceComponent,
previousConfidence,
previousTimestamp,
aggregate,
priceComponents,
price,
confidence,
status,
}
}
export const parsePermissionData = (data: Buffer): PermissionData => {
// pyth magic number
const magic = data.readUInt32LE(0)
// program version
const version = data.readUInt32LE(4)
// account type
const type = data.readUInt32LE(8)
// price account size
const size = data.readUInt32LE(12)
const masterAuthority = new PublicKey(data.slice(16, 48))
const dataCurationAuthority = new PublicKey(data.slice(48, 80))
const securityAuthority = new PublicKey(data.slice(80, 112))
return {
magic,
version,
type,
size,
masterAuthority,
dataCurationAuthority,
securityAuthority,
}
}
export { PythConnection } from './PythConnection'
export { PythHttpClient } from './PythHttpClient'
export { pythIdl, pythOracleCoder, pythOracleProgram } from './anchor'
export { PythCluster, getPythClusterApiUrl, getPythProgramKeyForCluster } from './cluster'