diff --git a/Dockerfile.ethereum b/Dockerfile.ethereum index c6225eb247..d62bf1b0f1 100644 --- a/Dockerfile.ethereum +++ b/Dockerfile.ethereum @@ -23,7 +23,6 @@ WORKDIR /home/node/ethereum # Only invalidate the npm install step if package.json changed ADD --chown=node:node ethereum/package.json . ADD --chown=node:node ethereum/package-lock.json . -ADD --chown=node:node ethereum/.env.test .env # We want to cache node_modules *and* incorporate it into the final image. RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \ @@ -37,4 +36,4 @@ RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \ RUN rm -rf node_modules && mv node_modules_cache node_modules ADD --chown=node:node ethereum/ . - +ADD --chown=node:node ethereum/.env.test .env diff --git a/third_party/pyth/p2w-relay/package-lock.json b/third_party/pyth/p2w-relay/package-lock.json index 9303a06cf9..3d0c3b7a15 100644 --- a/third_party/pyth/p2w-relay/package-lock.json +++ b/third_party/pyth/p2w-relay/package-lock.json @@ -9,9 +9,9 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@certusone/p2w-sdk": "file:../p2w-sdk/js", "@certusone/wormhole-sdk": "^0.1.4", "@certusone/wormhole-spydk": "^0.0.1", + "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js", "@solana/spl-token": "^0.1.8", "@solana/web3.js": "^1.24.0", "@terra-money/terra.js": "^3.1.3", @@ -44,13 +44,13 @@ } }, "../p2w-sdk/js": { - "name": "@certusone/p2w-sdk", - "version": "0.1.0", + "name": "@pythnetwork/p2w-sdk-js", + "version": "1.0.0", "license": "MIT", "dependencies": { "@certusone/wormhole-sdk": "0.2.1", "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@pythnetwork/pyth-sdk-js": "^0.1.0" + "@pythnetwork/pyth-sdk-js": "^1.0.0" }, "devDependencies": { "@openzeppelin/contracts": "^4.2.0", @@ -699,10 +699,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@certusone/p2w-sdk": { - "resolved": "../p2w-sdk/js", - "link": true - }, "node_modules/@certusone/wormhole-sdk": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.4.tgz", @@ -2060,6 +2056,10 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "node_modules/@pythnetwork/p2w-sdk-js": { + "resolved": "../p2w-sdk/js", + "link": true + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -8387,24 +8387,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@certusone/p2w-sdk": { - "version": "file:../p2w-sdk/js", - "requires": { - "@certusone/wormhole-sdk": "0.2.1", - "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@openzeppelin/contracts": "^4.2.0", - "@pythnetwork/pyth-sdk-js": "^0.1.0", - "@typechain/ethers-v5": "^7.1.2", - "@types/long": "^4.0.1", - "@types/node": "^16.6.1", - "copy-dir": "^1.3.0", - "find": "^0.3.0", - "prettier": "^2.3.2", - "tslint": "^6.1.3", - "tslint-config-prettier": "^1.18.0", - "typescript": "^4.3.5" - } - }, "@certusone/wormhole-sdk": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.4.tgz", @@ -9317,6 +9299,24 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@pythnetwork/p2w-sdk-js": { + "version": "file:../p2w-sdk/js", + "requires": { + "@certusone/wormhole-sdk": "0.2.1", + "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", + "@openzeppelin/contracts": "^4.2.0", + "@pythnetwork/pyth-sdk-js": "^1.0.0", + "@typechain/ethers-v5": "^7.1.2", + "@types/long": "^4.0.1", + "@types/node": "^16.6.1", + "copy-dir": "^1.3.0", + "find": "^0.3.0", + "prettier": "^2.3.2", + "tslint": "^6.1.3", + "tslint-config-prettier": "^1.18.0", + "typescript": "^4.3.5" + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", diff --git a/third_party/pyth/p2w-relay/package.json b/third_party/pyth/p2w-relay/package.json index 8cc3f447f3..22677b56f9 100644 --- a/third_party/pyth/p2w-relay/package.json +++ b/third_party/pyth/p2w-relay/package.json @@ -30,7 +30,7 @@ "typescript": "^4.3.5" }, "dependencies": { - "@certusone/p2w-sdk": "file:../p2w-sdk/js", + "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js", "@certusone/wormhole-sdk": "^0.1.4", "@certusone/wormhole-spydk": "^0.0.1", "@solana/spl-token": "^0.1.8", diff --git a/third_party/pyth/p2w-relay/src/listen.ts b/third_party/pyth/p2w-relay/src/listen.ts index aa9efb100d..bfaa88d4aa 100644 --- a/third_party/pyth/p2w-relay/src/listen.ts +++ b/third_party/pyth/p2w-relay/src/listen.ts @@ -14,7 +14,7 @@ import { subscribeSignedVAA, } from "@certusone/wormhole-spydk"; -import { parseBatchPriceAttestation, getBatchSummary } from "@certusone/p2w-sdk"; +import { parseBatchPriceAttestation, getBatchSummary } from "@pythnetwork/p2w-sdk-js"; import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; diff --git a/third_party/pyth/p2w-relay/src/relay/evm.ts b/third_party/pyth/p2w-relay/src/relay/evm.ts index ed0d70099a..54d98c8ab5 100644 --- a/third_party/pyth/p2w-relay/src/relay/evm.ts +++ b/third_party/pyth/p2w-relay/src/relay/evm.ts @@ -5,7 +5,7 @@ import { hexToUint8Array } from "@certusone/wormhole-sdk"; import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; import { PythUpgradable__factory, PythUpgradable } from "../evm/bindings/"; -import { parseBatchPriceAttestation } from "@certusone/p2w-sdk"; +import { parseBatchPriceAttestation } from "@pythnetwork/p2w-sdk-js"; let WH_WASM: any = null; diff --git a/third_party/pyth/p2w-relay/src/worker.ts b/third_party/pyth/p2w-relay/src/worker.ts index 88392d7990..7de2c5b1b9 100644 --- a/third_party/pyth/p2w-relay/src/worker.ts +++ b/third_party/pyth/p2w-relay/src/worker.ts @@ -8,7 +8,7 @@ import { Relay, RelayResult, RelayRetcode } from "./relay/iface"; import * as helpers from "./helpers"; import { logger } from "./helpers"; import { PromHelper } from "./promHelpers"; -import { BatchPriceAttestation, getBatchAttestationHashKey, getBatchSummary } from "@certusone/p2w-sdk"; +import { BatchPriceAttestation, getBatchAttestationHashKey, getBatchSummary } from "@pythnetwork/p2w-sdk-js"; const mutex = new Mutex(); let condition = new CondVar(); diff --git a/third_party/pyth/p2w-sdk/js/package-lock.json b/third_party/pyth/p2w-sdk/js/package-lock.json index 586d0be07d..c060421205 100644 --- a/third_party/pyth/p2w-sdk/js/package-lock.json +++ b/third_party/pyth/p2w-sdk/js/package-lock.json @@ -1,17 +1,17 @@ { - "name": "@certusone/p2w-sdk", - "version": "0.1.0", + "name": "@pythnetwork/p2w-sdk-js", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@certusone/p2w-sdk", - "version": "0.1.0", + "name": "@pythnetwork/p2w-sdk-js", + "version": "1.0.0", "license": "MIT", "dependencies": { "@certusone/wormhole-sdk": "0.2.1", "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@pythnetwork/pyth-sdk-js": "^0.3.0" + "@pythnetwork/pyth-sdk-js": "^1.0.0" }, "devDependencies": { "@openzeppelin/contracts": "^4.2.0", @@ -913,9 +913,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "node_modules/@pythnetwork/pyth-sdk-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.3.0.tgz", - "integrity": "sha512-7xsSM5PWD8+ez8lB5R0ofpaP1J1bRrtVkp9zm7Ry8QtKq5dOFfQqSqOjh9tLTX2h8i2xD93//0EnXXw35pzCkg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-1.0.0.tgz", + "integrity": "sha512-nZ3tmn5EhR7Y6177cAE7p7iQJK40bipMUI4ZBwRhgTONOcg35jG0fsvlETZYgij0baQ1PMJQE6dIqZ50EMZpJw==" }, "node_modules/@solana/buffer-layout": { "version": "4.0.0", @@ -3236,9 +3236,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@pythnetwork/pyth-sdk-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.3.0.tgz", - "integrity": "sha512-7xsSM5PWD8+ez8lB5R0ofpaP1J1bRrtVkp9zm7Ry8QtKq5dOFfQqSqOjh9tLTX2h8i2xD93//0EnXXw35pzCkg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-1.0.0.tgz", + "integrity": "sha512-nZ3tmn5EhR7Y6177cAE7p7iQJK40bipMUI4ZBwRhgTONOcg35jG0fsvlETZYgij0baQ1PMJQE6dIqZ50EMZpJw==" }, "@solana/buffer-layout": { "version": "4.0.0", diff --git a/third_party/pyth/p2w-sdk/js/package.json b/third_party/pyth/p2w-sdk/js/package.json index eb0f566a7a..6a129855df 100644 --- a/third_party/pyth/p2w-sdk/js/package.json +++ b/third_party/pyth/p2w-sdk/js/package.json @@ -1,6 +1,6 @@ { - "name": "@certusone/p2w-sdk", - "version": "0.1.0", + "name": "@pythnetwork/p2w-sdk-js", + "version": "1.0.0", "description": "TypeScript library for interacting with Pyth2Wormhole", "types": "lib/index.d.ts", "main": "lib/index.js", @@ -11,6 +11,7 @@ "build": "npm run build-lib", "build-lib": "npm run copy-artifacts && tsc", "build-watch": "npm run copy-artifacts && tsc --watch", + "format": "prettier --write \"src/**/*.ts\"", "copy-artifacts": "node scripts/copyWasm.cjs", "lint": "tslint -p tsconfig.json", "postversion": "git push && git push --tags", @@ -41,7 +42,7 @@ "dependencies": { "@certusone/wormhole-sdk": "0.2.1", "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@pythnetwork/pyth-sdk-js": "^0.3.0" + "@pythnetwork/pyth-sdk-js": "^1.0.0" }, "bugs": { "url": "https://github.com/pyth-network/pyth-crosschain/issues" diff --git a/third_party/pyth/p2w-sdk/js/src/index.ts b/third_party/pyth/p2w-sdk/js/src/index.ts index 923a4eec85..108a2c4d80 100644 --- a/third_party/pyth/p2w-sdk/js/src/index.ts +++ b/third_party/pyth/p2w-sdk/js/src/index.ts @@ -1,116 +1,141 @@ import { getSignedVAA, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import { zeroPad } from "ethers/lib/utils"; import { PublicKey } from "@solana/web3.js"; -import { PriceFeed, PriceStatus, UnixTimestamp } from "@pythnetwork/pyth-sdk-js"; - -let _P2W_WASM: any = undefined; +import { PriceFeed, Price, UnixTimestamp } from "@pythnetwork/pyth-sdk-js"; +let _P2W_WASM: any; async function importWasm() { - if (!_P2W_WASM) { - if (typeof window === 'undefined') { - _P2W_WASM = await import("./solana/p2w-core/nodejs/p2w_sdk"); - } else { - _P2W_WASM = await import("./solana/p2w-core/bundler/p2w_sdk"); - } + if (!_P2W_WASM) { + if (typeof window === "undefined") { + _P2W_WASM = await import("./solana/p2w-core/nodejs/p2w_sdk"); + } else { + _P2W_WASM = await import("./solana/p2w-core/bundler/p2w_sdk"); } - return _P2W_WASM; + } + return _P2W_WASM; } export type PriceAttestation = { - productId: string; - priceId: string; - price: string; - conf: string; - expo: number; - emaPrice: string; - emaConf: string; - status: PriceStatus; - numPublishers: number; - maxNumPublishers: number; - attestationTime: UnixTimestamp; - publishTime: UnixTimestamp; - prevPublishTime: UnixTimestamp; - prevPrice: string; - prevConf: string; + productId: string; + priceId: string; + price: string; + conf: string; + expo: number; + emaPrice: string; + emaConf: string; + status: number; + numPublishers: number; + maxNumPublishers: number; + attestationTime: UnixTimestamp; + publishTime: UnixTimestamp; + prevPublishTime: UnixTimestamp; + prevPrice: string; + prevConf: string; }; export type BatchPriceAttestation = { - priceAttestations: PriceAttestation[]; + priceAttestations: PriceAttestation[]; }; export async function parseBatchPriceAttestation( - arr: Buffer + arr: Buffer ): Promise { - - let wasm = await importWasm(); - let rawVal = await wasm.parse_batch_attestation(arr); + const wasm = await importWasm(); + const rawVal = await wasm.parse_batch_attestation(arr); - return rawVal; + return rawVal; } // Returns a hash of all priceIds within the batch, it can be used to identify whether there is a // new batch with exact same symbols (and ignore the old one) export function getBatchAttestationHashKey( - batchAttestation: BatchPriceAttestation + batchAttestation: BatchPriceAttestation ): string { - const priceIds: string[] = batchAttestation.priceAttestations.map( - (priceAttestation) => priceAttestation.priceId - ); - priceIds.sort(); + const priceIds: string[] = batchAttestation.priceAttestations.map( + (priceAttestation) => priceAttestation.priceId + ); + priceIds.sort(); - return priceIds.join("#"); + return priceIds.join("#"); } -export function getBatchSummary( - batch: BatchPriceAttestation -): string { - let abstractRepresentation = { - num_attestations: batch.priceAttestations.length, - prices: batch.priceAttestations.map((priceAttestation) => { - return { - price_id: priceAttestation.priceId, - price: computePrice(priceAttestation.price, priceAttestation.expo), - conf: computePrice( - priceAttestation.conf, - priceAttestation.expo - ), - }; - }), - }; - return JSON.stringify(abstractRepresentation); +export function getBatchSummary(batch: BatchPriceAttestation): string { + const abstractRepresentation = { + num_attestations: batch.priceAttestations.length, + prices: batch.priceAttestations.map((priceAttestation) => { + const priceFeed = priceAttestationToPriceFeed(priceAttestation); + return { + price_id: priceFeed.id, + price: priceFeed.getPriceUnchecked().getPriceAsNumberUnchecked(), + conf: priceFeed.getEmaPriceUnchecked().getConfAsNumberUnchecked(), + }; + }), + }; + return JSON.stringify(abstractRepresentation); } -export async function getSignedAttestation(host: string, p2w_addr: string, sequence: number, extraGrpcOpts = {}): Promise { - let [emitter, _] = await PublicKey.findProgramAddress([Buffer.from("p2w-emitter")], new PublicKey(p2w_addr)); +export async function getSignedAttestation( + host: string, + p2wAddr: string, + sequence: number, + extraGrpcOpts = {} +): Promise { + const [emitter, _] = await PublicKey.findProgramAddress( + [Buffer.from("p2w-emitter")], + new PublicKey(p2wAddr) + ); - let emitterHex = sol_addr2buf(emitter).toString("hex"); - return await getSignedVAA(host, CHAIN_ID_SOLANA, emitterHex, "" + sequence, extraGrpcOpts); + const emitterHex = sol_addr2buf(emitter).toString("hex"); + return await getSignedVAA( + host, + CHAIN_ID_SOLANA, + emitterHex, + "" + sequence, + extraGrpcOpts + ); } -export function priceAttestationToPriceFeed(priceAttestation: PriceAttestation): PriceFeed { - return new PriceFeed({ - conf: priceAttestation.conf.toString(), - emaConf: priceAttestation.emaConf.toString(), - emaPrice: priceAttestation.emaPrice.toString(), - expo: priceAttestation.expo as any, - id: priceAttestation.priceId, - maxNumPublishers: priceAttestation.maxNumPublishers as any, - numPublishers: priceAttestation.numPublishers as any, - prevConf: priceAttestation.prevConf.toString(), - prevPrice: priceAttestation.prevPrice.toString(), - prevPublishTime: priceAttestation.prevPublishTime as any, - price: priceAttestation.price.toString(), - productId: priceAttestation.productId, - publishTime: priceAttestation.publishTime as any, - status: priceAttestation.status, - }) -} +export function priceAttestationToPriceFeed( + priceAttestation: PriceAttestation +): PriceFeed { + const emaPrice: Price = new Price({ + conf: priceAttestation.emaConf, + expo: priceAttestation.expo, + price: priceAttestation.emaPrice, + publishTime: priceAttestation.publishTime, + }); + + let price: Price; + + if (priceAttestation.status === 1) { + // 1 means trading + price = new Price({ + conf: priceAttestation.conf, + expo: priceAttestation.expo, + price: priceAttestation.price, + publishTime: priceAttestation.publishTime, + }); + } else { + price = new Price({ + conf: priceAttestation.prevConf, + expo: priceAttestation.expo, + price: priceAttestation.prevPrice, + publishTime: priceAttestation.prevPublishTime, + }); + + // emaPrice won't get updated if the status is unknown and hence it uses + // the previous publish time + emaPrice.publishTime = priceAttestation.prevPublishTime; + } -function computePrice(rawPrice: string, expo: number): number { - return Number(rawPrice) * 10 ** expo; + return new PriceFeed({ + emaPrice, + id: priceAttestation.priceId, + price, + }); } function sol_addr2buf(addr: PublicKey): Buffer { - return Buffer.from(zeroPad(addr.toBytes(), 32)); + return Buffer.from(zeroPad(addr.toBytes(), 32)); } diff --git a/third_party/pyth/price-service/package-lock.json b/third_party/pyth/price-service/package-lock.json index 7e0222ced9..37e9ec3197 100644 --- a/third_party/pyth/price-service/package-lock.json +++ b/third_party/pyth/price-service/package-lock.json @@ -1,18 +1,18 @@ { "name": "@pythnetwork/pyth-price-service", - "version": "1.4.1", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@pythnetwork/pyth-price-service", - "version": "1.4.1", + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@certusone/p2w-sdk": "file:../p2w-sdk/js", "@certusone/wormhole-sdk": "^0.1.4", "@certusone/wormhole-spydk": "^0.0.1", - "@pythnetwork/pyth-sdk-js": "^0.3.0", + "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js", + "@pythnetwork/pyth-sdk-js": "^1.0.0", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/morgan": "^1.9.3", @@ -50,13 +50,13 @@ } }, "../p2w-sdk/js": { - "name": "@certusone/p2w-sdk", - "version": "0.1.0", + "name": "@pythnetwork/p2w-sdk-js", + "version": "1.0.0", "license": "MIT", "dependencies": { "@certusone/wormhole-sdk": "0.2.1", "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@pythnetwork/pyth-sdk-js": "^0.3.0" + "@pythnetwork/pyth-sdk-js": "^1.0.0" }, "devDependencies": { "@openzeppelin/contracts": "^4.2.0", @@ -616,10 +616,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@certusone/p2w-sdk": { - "resolved": "../p2w-sdk/js", - "link": true - }, "node_modules/@certusone/wormhole-sdk": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.6.tgz", @@ -2200,10 +2196,14 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "node_modules/@pythnetwork/p2w-sdk-js": { + "resolved": "../p2w-sdk/js", + "link": true + }, "node_modules/@pythnetwork/pyth-sdk-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.3.0.tgz", - "integrity": "sha512-7xsSM5PWD8+ez8lB5R0ofpaP1J1bRrtVkp9zm7Ry8QtKq5dOFfQqSqOjh9tLTX2h8i2xD93//0EnXXw35pzCkg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-1.0.0.tgz", + "integrity": "sha512-nZ3tmn5EhR7Y6177cAE7p7iQJK40bipMUI4ZBwRhgTONOcg35jG0fsvlETZYgij0baQ1PMJQE6dIqZ50EMZpJw==" }, "node_modules/@sideway/address": { "version": "4.1.4", @@ -9435,24 +9435,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@certusone/p2w-sdk": { - "version": "file:../p2w-sdk/js", - "requires": { - "@certusone/wormhole-sdk": "0.2.1", - "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", - "@openzeppelin/contracts": "^4.2.0", - "@pythnetwork/pyth-sdk-js": "^0.3.0", - "@typechain/ethers-v5": "^7.1.2", - "@types/long": "^4.0.1", - "@types/node": "^16.6.1", - "copy-dir": "^1.3.0", - "find": "^0.3.0", - "prettier": "^2.3.2", - "tslint": "^6.1.3", - "tslint-config-prettier": "^1.18.0", - "typescript": "^4.3.5" - } - }, "@certusone/wormhole-sdk": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.6.tgz", @@ -10544,10 +10526,28 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@pythnetwork/p2w-sdk-js": { + "version": "file:../p2w-sdk/js", + "requires": { + "@certusone/wormhole-sdk": "0.2.1", + "@improbable-eng/grpc-web-node-http-transport": "^0.14.1", + "@openzeppelin/contracts": "^4.2.0", + "@pythnetwork/pyth-sdk-js": "^1.0.0", + "@typechain/ethers-v5": "^7.1.2", + "@types/long": "^4.0.1", + "@types/node": "^16.6.1", + "copy-dir": "^1.3.0", + "find": "^0.3.0", + "prettier": "^2.3.2", + "tslint": "^6.1.3", + "tslint-config-prettier": "^1.18.0", + "typescript": "^4.3.5" + } + }, "@pythnetwork/pyth-sdk-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.3.0.tgz", - "integrity": "sha512-7xsSM5PWD8+ez8lB5R0ofpaP1J1bRrtVkp9zm7Ry8QtKq5dOFfQqSqOjh9tLTX2h8i2xD93//0EnXXw35pzCkg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-1.0.0.tgz", + "integrity": "sha512-nZ3tmn5EhR7Y6177cAE7p7iQJK40bipMUI4ZBwRhgTONOcg35jG0fsvlETZYgij0baQ1PMJQE6dIqZ50EMZpJw==" }, "@sideway/address": { "version": "4.1.4", diff --git a/third_party/pyth/price-service/package.json b/third_party/pyth/price-service/package.json index feddd5d358..4c9923e56e 100644 --- a/third_party/pyth/price-service/package.json +++ b/third_party/pyth/price-service/package.json @@ -1,13 +1,16 @@ { "name": "@pythnetwork/pyth-price-service", - "version": "1.4.1", + "version": "2.0.0", "description": "Pyth Price Service", "main": "index.js", "scripts": { "format": "prettier --write \"src/**/*.ts\"", "build": "tsc", "start": "node lib/index.js", - "test": "jest src/" + "test": "jest src/", + "lint": "tslint -p tsconfig.json", + "preversion": "npm run lint", + "version": "npm run format && git add -A src" }, "author": "", "license": "Apache-2.0", @@ -25,10 +28,10 @@ "typescript": "^4.3.5" }, "dependencies": { - "@certusone/p2w-sdk": "file:../p2w-sdk/js", + "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js", "@certusone/wormhole-sdk": "^0.1.4", "@certusone/wormhole-spydk": "^0.0.1", - "@pythnetwork/pyth-sdk-js": "^0.3.0", + "@pythnetwork/pyth-sdk-js": "^1.0.0", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/morgan": "^1.9.3", diff --git a/third_party/pyth/price-service/src/__tests__/rest.test.ts b/third_party/pyth/price-service/src/__tests__/rest.test.ts index 6e0b61f0f0..00ac51e08d 100644 --- a/third_party/pyth/price-service/src/__tests__/rest.test.ts +++ b/third_party/pyth/price-service/src/__tests__/rest.test.ts @@ -1,4 +1,4 @@ -import { HexString, PriceFeed, PriceStatus } from "@pythnetwork/pyth-sdk-js"; +import { HexString, PriceFeed, Price } from "@pythnetwork/pyth-sdk-js"; import { PriceStore, PriceInfo } from "../listen"; import { RestAPI } from "../rest"; import { Express } from "express"; @@ -14,20 +14,19 @@ function expandTo64Len(id: string): string { function dummyPriceFeed(id: string): PriceFeed { return new PriceFeed({ - conf: "0", - emaConf: "1", - emaPrice: "2", - expo: 4, + emaPrice: new Price({ + conf: "1", + expo: 2, + price: "3", + publishTime: 4, + }), id, - maxNumPublishers: 7, - numPublishers: 6, - prevConf: "8", - prevPrice: "9", - prevPublishTime: 10, - price: "11", - productId: "def456", - publishTime: 13, - status: PriceStatus.Trading, + price: new Price({ + conf: "5", + expo: 6, + price: "7", + publishTime: 8, + }), }); } @@ -56,11 +55,11 @@ beforeAll(async () => { dummyPriceInfoPair(expandTo64Len("10101"), 3, "bidbidbid"), ]); - let priceInfo: PriceStore = { + const priceInfo: PriceStore = { getLatestPriceInfo: (priceFeedId: string) => { return priceInfoMap.get(priceFeedId); }, - addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => {}, + addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => undefined, getPriceIds: () => new Set(), }; @@ -72,7 +71,9 @@ beforeAll(async () => { describe("Latest Price Feed Endpoint", () => { test("When called with valid ids, returns correct price feed", async () => { const ids = [expandTo64Len("abcd"), expandTo64Len("3456")]; - const resp = await request(app).get("/api/latest_price_feeds").query({ ids }); + const resp = await request(app) + .get("/api/latest_price_feeds") + .query({ ids }); expect(resp.status).toBe(StatusCodes.OK); expect(resp.body.length).toBe(2); expect(resp.body).toContainEqual(dummyPriceFeed(ids[0]).toJson()); @@ -85,7 +86,9 @@ describe("Latest Price Feed Endpoint", () => { expandTo64Len("3456"), expandTo64Len("effe"), ]; - const resp = await request(app).get("/api/latest_price_feeds").query({ ids }); + const resp = await request(app) + .get("/api/latest_price_feeds") + .query({ ids }); expect(resp.status).toBe(StatusCodes.BAD_REQUEST); expect(resp.body.message).toContain(ids[0]); expect(resp.body.message).not.toContain(ids[1]); diff --git a/third_party/pyth/price-service/src/__tests__/ws.test.ts b/third_party/pyth/price-service/src/__tests__/ws.test.ts index 442c8743d0..52913ebe1e 100644 --- a/third_party/pyth/price-service/src/__tests__/ws.test.ts +++ b/third_party/pyth/price-service/src/__tests__/ws.test.ts @@ -1,4 +1,4 @@ -import { HexString, PriceFeed, PriceStatus } from "@pythnetwork/pyth-sdk-js"; +import { HexString, PriceFeed } from "@pythnetwork/pyth-sdk-js"; import { Server } from "http"; import { WebSocket, WebSocketServer } from "ws"; import { sleep } from "../helpers"; @@ -33,12 +33,12 @@ function dummyPriceMetadata( function dummyPriceInfo( id: HexString, vaa: HexString, - priceMetadata: any + dummyPriceMetadataValue: any ): PriceInfo { return { - seqNum: priceMetadata.sequence_number, - attestationTime: priceMetadata.attestation_time, - emitterChainId: priceMetadata.emitter_chain, + seqNum: dummyPriceMetadataValue.sequence_number, + attestationTime: dummyPriceMetadataValue.attestation_time, + emitterChainId: dummyPriceMetadataValue.emitter_chain, priceFeed: dummyPriceFeed(id), vaaBytes: Buffer.from(vaa, "hex").toString("binary"), }; @@ -46,20 +46,19 @@ function dummyPriceInfo( function dummyPriceFeed(id: string): PriceFeed { return PriceFeed.fromJson({ - conf: "0", - ema_conf: "1", - ema_price: "2", - expo: 3, + ema_price: { + conf: "1", + expo: 2, + price: "3", + publish_time: 4, + }, id, - max_num_publishers: 5, - num_publishers: 6, - prev_conf: "7", - prev_price: "8", - prev_publish_time: 9, - price: "10", - product_id: "def456", - publish_time: 12, - status: PriceStatus.Trading, + price: { + conf: "5", + expo: 6, + price: "7", + publish_time: 8, + }, }); } @@ -101,11 +100,10 @@ beforeAll(async () => { dummyPriceInfo(expandTo64Len("6789"), "bidbidbid", priceMetadata), ]; - let priceInfo: PriceStore = { + const priceInfo: PriceStore = { getLatestPriceInfo: (_priceFeedId: string) => undefined, addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => undefined, - getPriceIds: () => - new Set(priceInfos.map((priceInfo) => priceInfo.priceFeed.id)), + getPriceIds: () => new Set(priceInfos.map((info) => info.priceFeed.id)), }; api = new WebSocketAPI(priceInfo); @@ -123,9 +121,9 @@ afterAll(async () => { describe("Client receives data", () => { test("When subscribes with valid ids without verbose flag, returns correct price feed", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id], type: "subscribe", }; @@ -162,9 +160,9 @@ describe("Client receives data", () => { }); test("When subscribes with valid ids and verbose flag set to true, returns correct price feed with metadata", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id], type: "subscribe", verbose: true, @@ -208,9 +206,9 @@ describe("Client receives data", () => { }); test("When subscribes with valid ids and verbose flag set to false, returns correct price feed without metadata", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id], type: "subscribe", verbose: false, @@ -248,9 +246,9 @@ describe("Client receives data", () => { }); test("When subscribes with invalid ids, returns error", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [expandTo64Len("aaaa")], type: "subscribe", }; @@ -268,9 +266,9 @@ describe("Client receives data", () => { }); test("When subscribes for Price Feed A, doesn't receive updates for Price Feed B", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [priceInfos[0].priceFeed.id], type: "subscribe", }; @@ -305,7 +303,7 @@ describe("Client receives data", () => { }); test("When subscribes for Price Feed A, receives updated and when unsubscribes stops receiving", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); let message: ClientMessage = { ids: [priceInfos[0].priceFeed.id], @@ -355,9 +353,9 @@ describe("Client receives data", () => { }); test("Unsubscribe on not subscribed price feed is ok", async () => { - let [client, serverMessages] = await createSocketClient(); + const [client, serverMessages] = await createSocketClient(); - let message: ClientMessage = { + const message: ClientMessage = { ids: [priceInfos[0].priceFeed.id], type: "unsubscribe", }; @@ -376,17 +374,17 @@ describe("Client receives data", () => { }); test("Multiple clients with different price feed works", async () => { - let [client1, serverMessages1] = await createSocketClient(); - let [client2, serverMessages2] = await createSocketClient(); + const [client1, serverMessages1] = await createSocketClient(); + const [client2, serverMessages2] = await createSocketClient(); - let message1: ClientMessage = { + const message1: ClientMessage = { ids: [priceInfos[0].priceFeed.id], type: "subscribe", }; client1.send(JSON.stringify(message1)); - let message2: ClientMessage = { + const message2: ClientMessage = { ids: [priceInfos[1].priceFeed.id], type: "subscribe", }; diff --git a/third_party/pyth/price-service/src/helpers.ts b/third_party/pyth/price-service/src/helpers.ts index 062960edf2..2793327bb4 100644 --- a/third_party/pyth/price-service/src/helpers.ts +++ b/third_party/pyth/price-service/src/helpers.ts @@ -9,9 +9,9 @@ export function sleep(ms: number) { // Shorthand for optional/mandatory envs export function envOrErr(env: string): string { - let val = process.env[env]; + const val = process.env[env]; if (!val) { - throw `environment variable "${env}" must be set`; + throw new Error(`environment variable "${env}" must be set`); } return String(process.env[env]); } diff --git a/third_party/pyth/price-service/src/index.ts b/third_party/pyth/price-service/src/index.ts index 5f3fabbbc3..8e97cec1f8 100644 --- a/third_party/pyth/price-service/src/index.ts +++ b/third_party/pyth/price-service/src/index.ts @@ -12,7 +12,9 @@ if (process.env.PYTH_PRICE_SERVICE_CONFIG) { configFile = process.env.PYTH_PRICE_SERVICE_CONFIG; } +// tslint:disable:no-console console.log("Loading config file [%s]", configFile); +// tslint:disable:no-var-requires require("dotenv").config({ path: configFile }); setDefaultWasm("node"); @@ -23,7 +25,7 @@ initLogger({ logLevel: process.env.LOG_LEVEL }); async function run() { const promClient = new PromClient({ name: "price_service", - port: parseInt(envOrErr("PROM_PORT")), + port: parseInt(envOrErr("PROM_PORT"), 10), }); const listener = new Listener( @@ -32,9 +34,13 @@ async function run() { filtersRaw: process.env.SPY_SERVICE_FILTERS, readiness: { spySyncTimeSeconds: parseInt( - envOrErr("READINESS_SPY_SYNC_TIME_SECONDS") + envOrErr("READINESS_SPY_SYNC_TIME_SECONDS"), + 10 + ), + numLoadedSymbols: parseInt( + envOrErr("READINESS_NUM_LOADED_SYMBOLS"), + 10 ), - numLoadedSymbols: parseInt(envOrErr("READINESS_NUM_LOADED_SYMBOLS")), }, }, promClient @@ -45,7 +51,7 @@ async function run() { const restAPI = new RestAPI( { - port: parseInt(envOrErr("REST_PORT")), + port: parseInt(envOrErr("REST_PORT"), 10), }, listener, isReady, diff --git a/third_party/pyth/price-service/src/listen.ts b/third_party/pyth/price-service/src/listen.ts index 2a255054d9..854c9ba1cf 100644 --- a/third_party/pyth/price-service/src/listen.ts +++ b/third_party/pyth/price-service/src/listen.ts @@ -1,12 +1,12 @@ import { ChainId, hexToUint8Array, - uint8ArrayToHex + uint8ArrayToHex, } from "@certusone/wormhole-sdk"; import { createSpyRPCServiceClient, - subscribeSignedVAA + subscribeSignedVAA, } from "@certusone/wormhole-spydk"; import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; @@ -14,11 +14,11 @@ import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; import { getBatchSummary, parseBatchPriceAttestation, - priceAttestationToPriceFeed -} from "@certusone/p2w-sdk"; + priceAttestationToPriceFeed, +} from "@pythnetwork/p2w-sdk-js"; import { FilterEntry, - SubscribeSignedVAAResponse + SubscribeSignedVAAResponse, } from "@certusone/wormhole-spydk/lib/cjs/proto/spy/v1/spy"; import { ClientReadableStream } from "@grpc/grpc-js"; import { HexString, PriceFeed } from "@pythnetwork/pyth-sdk-js"; @@ -75,12 +75,12 @@ export class Listener implements PriceStore { return; } - const parsedJsonFilters = eval(filtersRaw); + const parsedJsonFilters = JSON.parse(filtersRaw); - for (let i = 0; i < parsedJsonFilters.length; i++) { - let myChainId = parseInt(parsedJsonFilters[i].chain_id) as ChainId; - let myEmitterAddress = parsedJsonFilters[i].emitter_address; - let myEmitterFilter: FilterEntry = { + for (const filter of parsedJsonFilters) { + const myChainId = parseInt(filter.chain_id, 10) as ChainId; + const myEmitterAddress = filter.emitter_address; + const myEmitterFilter: FilterEntry = { emitterFilter: { chainId: myChainId, emitterAddress: myEmitterAddress, @@ -165,10 +165,10 @@ export class Listener implements PriceStore { return; } - let isAnyPriceNew = batchAttestation.priceAttestations.some( + const isAnyPriceNew = batchAttestation.priceAttestations.some( (priceAttestation) => { const key = priceAttestation.priceId; - let lastAttestationTime = + const lastAttestationTime = this.priceFeedVaaMap.get(key)?.attestationTime; return ( lastAttestationTime === undefined || @@ -181,10 +181,11 @@ export class Listener implements PriceStore { return; } - for (let priceAttestation of batchAttestation.priceAttestations) { + for (const priceAttestation of batchAttestation.priceAttestations) { const key = priceAttestation.priceId; - let lastAttestationTime = this.priceFeedVaaMap.get(key)?.attestationTime; + const lastAttestationTime = + this.priceFeedVaaMap.get(key)?.attestationTime; if ( lastAttestationTime === undefined || @@ -193,14 +194,14 @@ export class Listener implements PriceStore { const priceFeed = priceAttestationToPriceFeed(priceAttestation); const priceInfo = { seqNum: parsedVAA.sequence, - vaaBytes: vaaBytes, + vaaBytes, attestationTime: priceAttestation.attestationTime, priceFeed, emitterChainId: parsedVAA.emitter_chain, }; this.priceFeedVaaMap.set(key, priceInfo); - for (let callback of this.updateCallbacks) { + for (const callback of this.updateCallbacks) { callback(priceInfo); } } @@ -233,7 +234,7 @@ export class Listener implements PriceStore { } isReady(): boolean { - let currentTime: TimestampInSec = Math.floor(Date.now() / 1000); + const currentTime: TimestampInSec = Math.floor(Date.now() / 1000); if ( this.spyConnectionTime === undefined || currentTime < diff --git a/third_party/pyth/price-service/src/logging.ts b/third_party/pyth/price-service/src/logging.ts index bde0434b39..94d94e812e 100644 --- a/third_party/pyth/price-service/src/logging.ts +++ b/third_party/pyth/price-service/src/logging.ts @@ -12,6 +12,7 @@ export function initLogger(config?: { logLevel?: string }) { } let transport: any; + // tslint:disable:no-console console.log("p2w_api is logging to the console at level [%s]", logLevel); transport = new winston.transports.Console({ diff --git a/third_party/pyth/price-service/src/promClient.ts b/third_party/pyth/price-service/src/promClient.ts index 1039e9c85e..f3b69d038d 100644 --- a/third_party/pyth/price-service/src/promClient.ts +++ b/third_party/pyth/price-service/src/promClient.ts @@ -73,8 +73,8 @@ export class PromClient { addResponseTime(path: string, status: number, duration: DurationInMs) { this.apiResponseTimeSummary.observe( { - path: path, - status: status, + path, + status, }, duration ); @@ -87,7 +87,7 @@ export class PromClient { ) { this.apiRequestsPriceFreshnessHistogram.observe( { - path: path, + path, price_id: priceId, }, duration @@ -96,8 +96,8 @@ export class PromClient { addWebSocketInteraction(type: string, status: "ok" | "err") { this.webSocketInteractionCounter.inc({ - type: type, - status: status, + type, + status, }); } } diff --git a/third_party/pyth/price-service/src/rest.ts b/third_party/pyth/price-service/src/rest.ts index 48b8100a5c..bb2e307f52 100644 --- a/third_party/pyth/price-service/src/rest.ts +++ b/third_party/pyth/price-service/src/rest.ts @@ -71,7 +71,7 @@ export class RestAPI { }) ); - let endpoints: string[] = []; + const endpoints: string[] = []; const latestVaasInputSchema: schema = { query: Joi.object({ @@ -84,20 +84,20 @@ export class RestAPI { "/api/latest_vaas", validate(latestVaasInputSchema), (req: Request, res: Response) => { - let priceIds = req.query.ids as string[]; + const priceIds = req.query.ids as string[]; // Multiple price ids might share same vaa, we use sequence number as // key of a vaa and deduplicate using a map of seqnum to vaa bytes. - let vaaMap = new Map(); + const vaaMap = new Map(); - let notFoundIds: string[] = []; + const notFoundIds: string[] = []; for (let id of priceIds) { if (id.startsWith("0x")) { id = id.substring(2); } - let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id); + const latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id); if (latestPriceInfo === undefined) { notFoundIds.push(id); @@ -105,7 +105,8 @@ export class RestAPI { } const freshness: DurationInSec = - new Date().getTime() / 1000 - latestPriceInfo.priceFeed.publishTime; + new Date().getTime() / 1000 - + latestPriceInfo.priceFeed.getPriceUnchecked().publishTime; this.promClient?.addApiRequestsPriceFreshness( req.path, id, @@ -142,20 +143,20 @@ export class RestAPI { "/api/latest_price_feeds", validate(latestPriceFeedsInputSchema), (req: Request, res: Response) => { - let priceIds = req.query.ids as string[]; + const priceIds = req.query.ids as string[]; // verbose is optional, default to false - let verbose = req.query.verbose === "true"; + const verbose = req.query.verbose === "true"; - let responseJson = []; + const responseJson = []; - let notFoundIds: string[] = []; + const notFoundIds: string[] = []; for (let id of priceIds) { if (id.startsWith("0x")) { id = id.substring(2); } - let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id); + const latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id); if (latestPriceInfo === undefined) { notFoundIds.push(id); @@ -163,7 +164,8 @@ export class RestAPI { } const freshness: DurationInSec = - new Date().getTime() / 1000 - latestPriceInfo.priceFeed.publishTime; + new Date().getTime() / 1000 - + latestPriceInfo.priceFeed.getEmaPriceUnchecked().publishTime; this.promClient?.addApiRequestsPriceFreshness( req.path, id, @@ -209,29 +211,30 @@ export class RestAPI { threshold: Joi.number().required(), }).required(), }; - app.get("/api/stale_feeds", + app.get( + "/api/stale_feeds", validate(staleFeedsInputSchema), (req: Request, res: Response) => { - let stalenessThresholdSeconds = Number(req.query.threshold as string); + const stalenessThresholdSeconds = Number(req.query.threshold as string); - let currentTime: TimestampInSec = Math.floor(Date.now() / 1000); + const currentTime: TimestampInSec = Math.floor(Date.now() / 1000); - let priceIds = [...this.priceFeedVaaInfo.getPriceIds()]; - let stalePrices: Record = {} + const priceIds = [...this.priceFeedVaaInfo.getPriceIds()]; + const stalePrices: Record = {}; - for (let priceId of priceIds) { - const latency = currentTime - this.priceFeedVaaInfo.getLatestPriceInfo(priceId)!.attestationTime + for (const priceId of priceIds) { + const latency = + currentTime - + this.priceFeedVaaInfo.getLatestPriceInfo(priceId)!.attestationTime; if (latency > stalenessThresholdSeconds) { - stalePrices[priceId] = latency + stalePrices[priceId] = latency; } } res.json(stalePrices); } ); - endpoints.push( - "/api/stale_feeds?threshold=" - ); + endpoints.push("/api/stale_feeds?threshold="); app.get("/ready", (_, res: Response) => { if (this.isReady!()) { @@ -252,7 +255,7 @@ export class RestAPI { app.get("/", (_, res: Response) => res.json(endpoints)); - app.use(function (err: any, _: Request, res: Response, next: NextFunction) { + app.use((err: any, _: Request, res: Response, next: NextFunction) => { if (err instanceof ValidationError) { return res.status(err.statusCode).json(err); } @@ -268,7 +271,7 @@ export class RestAPI { } async run(): Promise { - let app = await this.createApp(); + const app = await this.createApp(); return app.listen(this.port, () => logger.debug("listening on REST port " + this.port) ); diff --git a/third_party/pyth/price-service/src/ws.ts b/third_party/pyth/price-service/src/ws.ts index 14fe46fd93..43cbccb045 100644 --- a/third_party/pyth/price-service/src/ws.ts +++ b/third_party/pyth/price-service/src/ws.ts @@ -82,21 +82,25 @@ export class WebSocketAPI { return; } - const clients: Set = this.priceFeedClients.get(priceInfo.priceFeed.id)!; + const clients: Set = this.priceFeedClients.get( + priceInfo.priceFeed.id + )!; logger.info( `Sending ${priceInfo.priceFeed.id} price update to ${ clients.size - } clients: ${Array.from(clients.values()).map((ws, _idx, _arr) => this.wsId.get(ws))}` + } clients: ${Array.from(clients.values()).map((ws, _idx, _arr) => + this.wsId.get(ws) + )}` ); - for (let client of clients.values()) { + for (const client of clients.values()) { this.promClient?.addWebSocketInteraction("server_update", "ok"); - let verbose = this.priceFeedClientsVerbosity + const verbose = this.priceFeedClientsVerbosity .get(priceInfo.priceFeed.id)! .get(client); - let priceUpdate: ServerPriceUpdate = verbose + const priceUpdate: ServerPriceUpdate = verbose ? { type: "price_update", price_feed: { @@ -118,7 +122,7 @@ export class WebSocketAPI { } clientClose(ws: WebSocket) { - for (let clients of this.priceFeedClients.values()) { + for (const clients of this.priceFeedClients.values()) { if (clients.has(ws)) { clients.delete(ws); } @@ -130,13 +134,13 @@ export class WebSocketAPI { handleMessage(ws: WebSocket, data: RawData) { try { - let jsonData = JSON.parse(data.toString()); - let validationResult = ClientMessageSchema.validate(jsonData); + const jsonData = JSON.parse(data.toString()); + const validationResult = ClientMessageSchema.validate(jsonData); if (validationResult.error !== undefined) { throw validationResult.error; } - let message = jsonData as ClientMessage; + const message = jsonData as ClientMessage; message.ids = message.ids.map((id) => { if (id.startsWith("0x")) { @@ -146,7 +150,7 @@ export class WebSocketAPI { }); const availableIds = this.priceFeedVaaInfo.getPriceIds(); - let notFoundIds = message.ids.filter((id) => !availableIds.has(id)); + const notFoundIds = message.ids.filter((id) => !availableIds.has(id)); if (notFoundIds.length > 0) { throw new Error( @@ -154,7 +158,7 @@ export class WebSocketAPI { ); } - if (message.type == "subscribe") { + if (message.type === "subscribe") { message.ids.forEach((id) => this.addPriceFeedClient(ws, id, message.verbose === true) ); @@ -162,7 +166,7 @@ export class WebSocketAPI { message.ids.forEach((id) => this.delPriceFeedClient(ws, id)); } } catch (e: any) { - let response: ServerResponse = { + const errorResponse: ServerResponse = { type: "response", status: "error", error: e.message, @@ -173,7 +177,7 @@ export class WebSocketAPI { ); this.promClient?.addWebSocketInteraction("client_message", "err"); - ws.send(JSON.stringify(response)); + ws.send(JSON.stringify(errorResponse)); return; } @@ -182,7 +186,7 @@ export class WebSocketAPI { ); this.promClient?.addWebSocketInteraction("client_message", "ok"); - let response: ServerResponse = { + const response: ServerResponse = { type: "response", status: "success", }; diff --git a/third_party/pyth/price-service/tslint.json b/third_party/pyth/price-service/tslint.json new file mode 100644 index 0000000000..d2ce6a4f04 --- /dev/null +++ b/third_party/pyth/price-service/tslint.json @@ -0,0 +1,9 @@ + +{ + "extends": ["tslint:recommended", "tslint-config-prettier"], + "rules": { + "max-classes-per-file": { + "severity": "off" + } + } +} \ No newline at end of file