diff --git a/src/__tests__/PriceFeed.test.ts b/src/__tests__/PriceFeed.test.ts index a241080..536412a 100644 --- a/src/__tests__/PriceFeed.test.ts +++ b/src/__tests__/PriceFeed.test.ts @@ -1,4 +1,4 @@ -import { Price, PriceFeed, PriceStatus } from "../index"; +import { Price, PriceFeed, PriceFeedMetadata, PriceStatus } from "../index"; beforeAll(() => { jest.useFakeTimers(); @@ -42,8 +42,7 @@ test("Parsing Price Feed works as expected", () => { new Price("1", 4, "10") ); expect(priceFeed.getLatestAvailablePriceWithinDuration(5)).toBeUndefined(); - - expect(priceFeed.toJson()).toStrictEqual(data); + expect(priceFeed.toJson()).toEqual(data); }); test("getCurrentPrice returns undefined if status is not Trading", () => { @@ -99,3 +98,37 @@ test("getLatestAvailablePrice returns prevPrice when status is not Trading", () ); expect(priceFeed.getLatestAvailablePriceWithinDuration(10)).toBeUndefined(); }); + +test("getMetadata returns PriceFeedMetadata as expected", () => { + const data = { + conf: "1", + ema_conf: "2", + ema_price: "3", + expo: 4, + id: "abcdef0123456789", + max_num_publishers: 6, + metadata: new PriceFeedMetadata({ + attestation_time: 7, + emitter_chain: 8, + sequence_number: 9, + }), + num_publishers: 10, + prev_conf: "11", + prev_price: "12", + prev_publish_time: 13, + price: "14", + product_id: "0123456789abcdef", + publish_time: 16, + status: PriceStatus.Unknown, + }; + + const priceFeed = PriceFeed.fromJson(data); + + expect(priceFeed.getMetadata()).toStrictEqual( + new PriceFeedMetadata({ + attestation_time: 7, + emitter_chain: 8, + sequence_number: 9, + }) + ); +}); diff --git a/src/index.ts b/src/index.ts index c635734..ecf0334 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,6 +53,36 @@ export enum PriceStatus { Unknown = "Unknown", } +/** + * Metadata about the price + * + * Represents metadata of a price feed. + */ +export class PriceFeedMetadata { + /** + * Attestation time of the price + */ + attestation_time: number; + /** + * Chain of the emitter + */ + emitter_chain: number; + /** + * Sequence number of the price + */ + sequence_number: number; + + constructor(metadata: { + attestation_time: number; + emitter_chain: number; + sequence_number: number; + }) { + this.attestation_time = metadata.attestation_time; + this.emitter_chain = metadata.emitter_chain; + this.sequence_number = metadata.sequence_number; + } +} + /** * Pyth Price Feed * @@ -84,6 +114,10 @@ export class PriceFeed { * Maximum number of allowed publishers that can contribute to a price. */ maxNumPublishers: number; + /** + * Metadata about the price + */ + metadata?: PriceFeedMetadata; /** * Number of publishers that made up current aggregate. */ @@ -124,6 +158,7 @@ export class PriceFeed { expo: number; id: HexString; maxNumPublishers: number; + metadata?: PriceFeedMetadata; numPublishers: number; prevConf: string; prevPrice: string; @@ -139,6 +174,7 @@ export class PriceFeed { this.expo = rawValues.expo; this.id = rawValues.id; this.maxNumPublishers = rawValues.maxNumPublishers; + this.metadata = rawValues.metadata; this.numPublishers = rawValues.numPublishers; this.prevConf = rawValues.prevConf; this.prevPrice = rawValues.prevPrice; @@ -158,6 +194,7 @@ export class PriceFeed { expo: jsonFeed.expo, id: jsonFeed.id, maxNumPublishers: jsonFeed.max_num_publishers, + metadata: jsonFeed.metadata, numPublishers: jsonFeed.num_publishers, prevConf: jsonFeed.prev_conf, prevPrice: jsonFeed.prev_price, @@ -177,6 +214,7 @@ export class PriceFeed { expo: this.expo, id: this.id, max_num_publishers: this.maxNumPublishers, + metadata: this.metadata, num_publishers: this.numPublishers, prev_conf: this.prevConf, prev_price: this.prevPrice, @@ -186,6 +224,7 @@ export class PriceFeed { publish_time: this.publishTime, status: this.status, }; + // this is done to avoid sending undefined values to the server return Convert.priceFeedToJson(jsonFeed); } @@ -259,18 +298,33 @@ export class PriceFeed { * @returns a struct containing the latest available price, confidence interval and the exponent for * both numbers, or `undefined` if no price update occurred within `duration` seconds of the current time. */ - getLatestAvailablePriceWithinDuration(duration: DurationInSeconds): Price | undefined { + getLatestAvailablePriceWithinDuration( + duration: DurationInSeconds + ): Price | undefined { const [price, timestamp] = this.getLatestAvailablePriceUnchecked(); const currentTime: UnixTimestamp = Math.floor(Date.now() / 1000); - + // This checks the absolute difference as a sanity check // for the cases that the system time is behind or price - // feed timestamp happen to be in the future (a bug). + // feed timestamp happen to be in the future (a bug). if (Math.abs(currentTime - timestamp) > duration) { return undefined; } return price; } + + /** + * Get the price feed metadata. + * + * @returns a struct containing the attestation time, emitter chain, and the sequence number. + * Returns `undefined` if metadata is currently unavailable. + */ + getMetadata(): PriceFeedMetadata | undefined { + if (this.metadata === undefined) { + return undefined; + } + return new PriceFeedMetadata(this.metadata); + } } diff --git a/src/schemas/PriceFeed.ts b/src/schemas/PriceFeed.ts index 10741b6..32085cc 100644 --- a/src/schemas/PriceFeed.ts +++ b/src/schemas/PriceFeed.ts @@ -35,6 +35,10 @@ export interface PriceFeed { * Maximum number of allowed publishers that can contribute to a price. */ max_num_publishers: number; + /** + * Metadata about the price + */ + metadata?: PriceFeedMetadata; /** * Number of publishers that made up current aggregate. */ @@ -69,6 +73,26 @@ export interface PriceFeed { status: PriceStatus; } +/** + * Metadata about the price + * + * Represents metadata of a price feed. + */ +export interface PriceFeedMetadata { + /** + * Attestation time of the price + */ + attestation_time: number; + /** + * Chain of the emitter + */ + emitter_chain: number; + /** + * Sequence number of the price + */ + sequence_number: number; +} + /** * Status of price (Trading is valid). * @@ -249,6 +273,11 @@ const typeMap: any = { { json: "expo", js: "expo", typ: 0 }, { json: "id", js: "id", typ: "" }, { json: "max_num_publishers", js: "max_num_publishers", typ: 0 }, + { + json: "metadata", + js: "metadata", + typ: u(undefined, r("PriceFeedMetadata")), + }, { json: "num_publishers", js: "num_publishers", typ: 0 }, { json: "prev_conf", js: "prev_conf", typ: "" }, { json: "prev_price", js: "prev_price", typ: "" }, @@ -260,5 +289,13 @@ const typeMap: any = { ], "any" ), + PriceFeedMetadata: o( + [ + { json: "attestation_time", js: "attestation_time", typ: 0 }, + { json: "emitter_chain", js: "emitter_chain", typ: 0 }, + { json: "sequence_number", js: "sequence_number", typ: 0 }, + ], + "any" + ), PriceStatus: ["Auction", "Halted", "Trading", "Unknown"], }; diff --git a/src/schemas/price_feed.json b/src/schemas/price_feed.json index c73baea..87576ba 100644 --- a/src/schemas/price_feed.json +++ b/src/schemas/price_feed.json @@ -94,6 +94,14 @@ "$ref": "#/definitions/PriceStatus" } ] + }, + "metadata": { + "description": "Metadata about the price", + "allOf": [ + { + "$ref": "#/definitions/PriceFeedMetadata" + } + ] } }, "definitions": { @@ -103,12 +111,29 @@ "PriceStatus": { "description": "Represents availability status of a price feed.", "type": "string", - "enum": [ - "Unknown", - "Trading", - "Halted", - "Auction" - ] + "enum": ["Unknown", "Trading", "Halted", "Auction"] + }, + "PriceFeedMetadata": { + "description": "Represents metadata of a price feed.", + "type": "object", + "required": ["attestation_time", "emitter_chain", "sequence_number"], + "properties": { + "attestation_time": { + "description": "Attestation time of the price", + "type": "integer", + "format": "int64" + }, + "emitter_chain": { + "description": "Chain of the emitter", + "type": "integer", + "format": "int16" + }, + "sequence_number": { + "description": "Sequence number of the price", + "type": "integer", + "format": "int64" + } + } } } }