@pythnetwork/price-pusher 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/options.js ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mnemonicFile = exports.pushingFrequency = exports.pollingFrequency = exports.priceConfigFile = exports.pythContractAddress = exports.priceServiceEndpoint = void 0;
4
+ exports.priceServiceEndpoint = {
5
+ "price-service-endpoint": {
6
+ description: "Endpoint URL for the price service. e.g: https://endpoint/example",
7
+ type: "string",
8
+ required: true,
9
+ },
10
+ };
11
+ exports.pythContractAddress = {
12
+ "pyth-contract-address": {
13
+ description: "Pyth contract address. Provide the network name on which Pyth is deployed " +
14
+ "or the Pyth contract address if you use a local network.",
15
+ type: "string",
16
+ required: true,
17
+ },
18
+ };
19
+ exports.priceConfigFile = {
20
+ "price-config-file": {
21
+ description: "Path to price configuration YAML file.",
22
+ type: "string",
23
+ required: true,
24
+ },
25
+ };
26
+ exports.pollingFrequency = {
27
+ "polling-frequency": {
28
+ description: "The frequency to poll price info data from the network if the RPC is not a websocket.",
29
+ type: "number",
30
+ required: false,
31
+ default: 5,
32
+ },
33
+ };
34
+ exports.pushingFrequency = {
35
+ "pushing-frequency": {
36
+ description: "The frequency to push prices to the RPC. " +
37
+ "It is better that the value be greater than the block time of the network, so this program confirms " +
38
+ "it is updated and does not push it twice.",
39
+ type: "number",
40
+ required: false,
41
+ default: 10,
42
+ },
43
+ };
44
+ exports.mnemonicFile = {
45
+ "mnemonic-file": {
46
+ description: "Path to payer mnemonic (private key) file.",
47
+ type: "string",
48
+ required: true,
49
+ },
50
+ };
@@ -0,0 +1,18 @@
1
+ import { HexString } from "@pythnetwork/price-service-client";
2
+ import { DurationInSeconds, PctNumber } from "./utils";
3
+ import { PriceInfo } from "./interface";
4
+ export type PriceConfig = {
5
+ alias: string;
6
+ id: HexString;
7
+ timeDifference: DurationInSeconds;
8
+ priceDeviation: PctNumber;
9
+ confidenceRatio: PctNumber;
10
+ };
11
+ export declare function readPriceConfigFile(path: string): PriceConfig[];
12
+ /**
13
+ * Checks whether on-chain price needs to be updated with the latest pyth price information.
14
+ *
15
+ * @param priceConfig Config of the price feed to check
16
+ * @returns True if the on-chain price needs to be updated.
17
+ */
18
+ export declare function shouldUpdate(priceConfig: PriceConfig, sourceLatestPrice: PriceInfo | undefined, targetLatestPrice: PriceInfo | undefined): boolean;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shouldUpdate = exports.readPriceConfigFile = void 0;
7
+ const joi_1 = __importDefault(require("joi"));
8
+ const yaml_1 = __importDefault(require("yaml"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const utils_1 = require("./utils");
11
+ const PriceConfigFileSchema = joi_1.default.array()
12
+ .items(joi_1.default.object({
13
+ alias: joi_1.default.string().required(),
14
+ id: joi_1.default.string()
15
+ .regex(/^(0x)?[a-f0-9]{64}$/)
16
+ .required(),
17
+ time_difference: joi_1.default.number().required(),
18
+ price_deviation: joi_1.default.number().required(),
19
+ confidence_ratio: joi_1.default.number().required(),
20
+ }))
21
+ .unique("id")
22
+ .unique("alias")
23
+ .required();
24
+ function readPriceConfigFile(path) {
25
+ const priceConfigs = yaml_1.default.parse(fs_1.default.readFileSync(path, "utf-8"));
26
+ const validationResult = PriceConfigFileSchema.validate(priceConfigs);
27
+ if (validationResult.error !== undefined) {
28
+ throw validationResult.error;
29
+ }
30
+ return priceConfigs.map((priceConfigRaw) => {
31
+ const priceConfig = {
32
+ alias: priceConfigRaw.alias,
33
+ id: (0, utils_1.removeLeading0x)(priceConfigRaw.id),
34
+ timeDifference: priceConfigRaw.time_difference,
35
+ priceDeviation: priceConfigRaw.price_deviation,
36
+ confidenceRatio: priceConfigRaw.confidence_ratio,
37
+ };
38
+ return priceConfig;
39
+ });
40
+ }
41
+ exports.readPriceConfigFile = readPriceConfigFile;
42
+ /**
43
+ * Checks whether on-chain price needs to be updated with the latest pyth price information.
44
+ *
45
+ * @param priceConfig Config of the price feed to check
46
+ * @returns True if the on-chain price needs to be updated.
47
+ */
48
+ function shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice) {
49
+ const priceId = priceConfig.id;
50
+ // There is no price to update the target with.
51
+ if (sourceLatestPrice === undefined) {
52
+ return false;
53
+ }
54
+ // It means that price never existed there. So we should push the latest price feed.
55
+ if (targetLatestPrice === undefined) {
56
+ console.log(`${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.`);
57
+ return true;
58
+ }
59
+ // The current price is not newer than the price onchain
60
+ if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
61
+ return false;
62
+ }
63
+ const timeDifference = sourceLatestPrice.publishTime - targetLatestPrice.publishTime;
64
+ const priceDeviationPct = (Math.abs(Number(sourceLatestPrice.price) - Number(targetLatestPrice.price)) /
65
+ Number(targetLatestPrice.price)) *
66
+ 100;
67
+ const confidenceRatioPct = Math.abs((Number(sourceLatestPrice.conf) / Number(sourceLatestPrice.price)) * 100);
68
+ console.log(`Analyzing price ${priceConfig.alias} (${priceId})`);
69
+ console.log("Source latest price: ", sourceLatestPrice);
70
+ console.log("Target latest price: ", targetLatestPrice);
71
+ console.log(`Time difference: ${timeDifference} (< ${priceConfig.timeDifference}?) OR ` +
72
+ `Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${priceConfig.priceDeviation}%?) OR ` +
73
+ `Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${priceConfig.confidenceRatio}%?)`);
74
+ const result = timeDifference >= priceConfig.timeDifference ||
75
+ priceDeviationPct >= priceConfig.priceDeviation ||
76
+ confidenceRatioPct >= priceConfig.confidenceRatio;
77
+ return result;
78
+ }
79
+ exports.shouldUpdate = shouldUpdate;
@@ -0,0 +1,12 @@
1
+ import { PriceServiceConnection } from "@pythnetwork/price-service-client";
2
+ import { PriceInfo, IPriceListener, PriceItem } from "./interface";
3
+ export declare class PythPriceListener implements IPriceListener {
4
+ private connection;
5
+ private priceIds;
6
+ private priceIdToAlias;
7
+ private latestPriceInfo;
8
+ constructor(connection: PriceServiceConnection, priceItems: PriceItem[]);
9
+ start(): Promise<void>;
10
+ private onNewPriceFeed;
11
+ getLatestPriceInfo(priceId: string): PriceInfo | undefined;
12
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PythPriceListener = void 0;
4
+ class PythPriceListener {
5
+ connection;
6
+ priceIds;
7
+ priceIdToAlias;
8
+ latestPriceInfo;
9
+ constructor(connection, priceItems) {
10
+ this.connection = connection;
11
+ this.priceIds = priceItems.map((priceItem) => priceItem.id);
12
+ this.priceIdToAlias = new Map(priceItems.map((priceItem) => [priceItem.id, priceItem.alias]));
13
+ this.latestPriceInfo = new Map();
14
+ }
15
+ // This method should be awaited on and once it finishes it has the latest value
16
+ // for the given price feeds (if they exist).
17
+ async start() {
18
+ this.connection.subscribePriceFeedUpdates(this.priceIds, this.onNewPriceFeed.bind(this));
19
+ const priceFeeds = await this.connection.getLatestPriceFeeds(this.priceIds);
20
+ priceFeeds?.forEach((priceFeed) => {
21
+ // Getting unchecked because although it might be old
22
+ // but might not be there on the target chain.
23
+ const latestAvailablePrice = priceFeed.getPriceUnchecked();
24
+ this.latestPriceInfo.set(priceFeed.id, {
25
+ price: latestAvailablePrice.price,
26
+ conf: latestAvailablePrice.conf,
27
+ publishTime: latestAvailablePrice.publishTime,
28
+ });
29
+ });
30
+ }
31
+ onNewPriceFeed(priceFeed) {
32
+ console.log(`Received new price feed update from Pyth price service: ${this.priceIdToAlias.get(priceFeed.id)} ${priceFeed.id}`);
33
+ // Consider price to be currently available if it is not older than 60s
34
+ const currentPrice = priceFeed.getPriceNoOlderThan(60);
35
+ if (currentPrice === undefined) {
36
+ return;
37
+ }
38
+ const priceInfo = {
39
+ conf: currentPrice.conf,
40
+ price: currentPrice.price,
41
+ publishTime: currentPrice.publishTime,
42
+ };
43
+ this.latestPriceInfo.set(priceFeed.id, priceInfo);
44
+ }
45
+ getLatestPriceInfo(priceId) {
46
+ return this.latestPriceInfo.get(priceId);
47
+ }
48
+ }
49
+ exports.PythPriceListener = PythPriceListener;
package/lib/utils.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { HexString } from "@pythnetwork/price-service-client";
2
+ export type PctNumber = number;
3
+ export type DurationInSeconds = number;
4
+ export declare const txSpeeds: readonly ["slow", "standard", "fast"];
5
+ export type TxSpeed = typeof txSpeeds[number];
6
+ export declare const customGasChainIds: readonly [137];
7
+ export type CustomGasChainId = typeof customGasChainIds[number];
8
+ export declare function sleep(ms: number): Promise<void>;
9
+ export declare function removeLeading0x(id: HexString): HexString;
10
+ export declare function addLeading0x(id: HexString): HexString;
11
+ export declare function isWsEndpoint(endpoint: string): boolean;
12
+ export declare function verifyValidOption<options extends Readonly<Array<any>>, validOption extends options[number]>(option: any, validOptions: options): validOption;
package/lib/utils.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyValidOption = exports.isWsEndpoint = exports.addLeading0x = exports.removeLeading0x = exports.sleep = exports.customGasChainIds = exports.txSpeeds = void 0;
4
+ exports.txSpeeds = ["slow", "standard", "fast"];
5
+ exports.customGasChainIds = [137];
6
+ async function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ exports.sleep = sleep;
10
+ function removeLeading0x(id) {
11
+ if (id.startsWith("0x")) {
12
+ return id.substring(2);
13
+ }
14
+ return id;
15
+ }
16
+ exports.removeLeading0x = removeLeading0x;
17
+ function addLeading0x(id) {
18
+ if (id.startsWith("0x")) {
19
+ return id;
20
+ }
21
+ return "0x" + id;
22
+ }
23
+ exports.addLeading0x = addLeading0x;
24
+ function isWsEndpoint(endpoint) {
25
+ const url = new URL(endpoint);
26
+ const protocol = url.protocol;
27
+ if (protocol === "ws:" || protocol == "wss:") {
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ exports.isWsEndpoint = isWsEndpoint;
33
+ function verifyValidOption(option, validOptions) {
34
+ if (validOptions.includes(option)) {
35
+ return option;
36
+ }
37
+ const errorString = option + " is not a valid option. Please choose between " + validOptions;
38
+ throw new Error(errorString);
39
+ }
40
+ exports.verifyValidOption = verifyValidOption;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@pythnetwork/price-pusher",
3
+ "version": "4.1.1",
4
+ "description": "Pyth Price Pusher",
5
+ "homepage": "https://pyth.network",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "files": [
9
+ "lib/**/*"
10
+ ],
11
+ "bin": {
12
+ "pyth-price-pusher": "./lib/index.js"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/pyth-network/pyth-crosschain",
17
+ "directory": "price_pusher"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "test": "jest src/ --passWithNoTests",
24
+ "build": "tsc",
25
+ "format": "prettier --write \"src/**/*.ts\"",
26
+ "lint": "eslint src/",
27
+ "start": "node lib/index.js",
28
+ "dev": "ts-node src/index.ts",
29
+ "prepublishOnly": "npm run build && npm test && npm run lint",
30
+ "preversion": "npm run lint",
31
+ "version": "npm run format && git add -A src"
32
+ },
33
+ "keywords": [
34
+ "pyth",
35
+ "oracle",
36
+ "evm",
37
+ "ethereum",
38
+ "injective"
39
+ ],
40
+ "license": "Apache-2.0",
41
+ "devDependencies": {
42
+ "@types/ethereum-protocol": "^1.0.2",
43
+ "@types/jest": "^27.4.1",
44
+ "@types/yargs": "^17.0.10",
45
+ "@typescript-eslint/eslint-plugin": "^5.20.0",
46
+ "@typescript-eslint/parser": "^5.20.0",
47
+ "eslint": "^8.13.0",
48
+ "jest": "^27.5.1",
49
+ "prettier": "^2.6.2",
50
+ "ts-jest": "^27.1.4",
51
+ "typescript": "^4.6.3"
52
+ },
53
+ "dependencies": {
54
+ "@injectivelabs/sdk-ts": "^1.0.484",
55
+ "@pythnetwork/price-service-client": "*",
56
+ "@pythnetwork/pyth-sdk-solidity": "*",
57
+ "@truffle/hdwallet-provider": "^2.1.3",
58
+ "joi": "^17.6.0",
59
+ "web3": "^1.8.1",
60
+ "web3-eth-contract": "^1.8.1",
61
+ "yaml": "^2.1.1",
62
+ "yargs": "^17.5.1"
63
+ },
64
+ "gitHead": "94f38fdd745ba4211e4aee0b361b2f6d82522f8c"
65
+ }