Bladeren bron

Cascade Pyth SDK JS update to downstream packages (#326)

* Fix annoying ethereum problem

* Update p2w-sdk

* rename + lint + format + bump to 1.0.0

* Update price service

* Bump version + format + add lint

* Fix price service linting issues

* Fix package rename issue

* Update package lock of dependencies

local dependency is aweful!

* Fix exclusion bug
Ali Behjati 3 jaren geleden
bovenliggende
commit
d7f436a856

+ 1 - 2
Dockerfile.ethereum

@@ -23,7 +23,6 @@ WORKDIR /home/node/ethereum
 # Only invalidate the npm install step if package.json changed
 # Only invalidate the npm install step if package.json changed
 ADD --chown=node:node ethereum/package.json .
 ADD --chown=node:node ethereum/package.json .
 ADD --chown=node:node ethereum/package-lock.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.
 # 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 \
 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
 RUN rm -rf node_modules && mv node_modules_cache node_modules
 
 
 ADD --chown=node:node ethereum/ .
 ADD --chown=node:node ethereum/ .
-
+ADD --chown=node:node ethereum/.env.test .env

+ 26 - 26
third_party/pyth/p2w-relay/package-lock.json

@@ -9,9 +9,9 @@
       "version": "1.0.0",
       "version": "1.0.0",
       "license": "Apache-2.0",
       "license": "Apache-2.0",
       "dependencies": {
       "dependencies": {
-        "@certusone/p2w-sdk": "file:../p2w-sdk/js",
         "@certusone/wormhole-sdk": "^0.1.4",
         "@certusone/wormhole-sdk": "^0.1.4",
         "@certusone/wormhole-spydk": "^0.0.1",
         "@certusone/wormhole-spydk": "^0.0.1",
+        "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js",
         "@solana/spl-token": "^0.1.8",
         "@solana/spl-token": "^0.1.8",
         "@solana/web3.js": "^1.24.0",
         "@solana/web3.js": "^1.24.0",
         "@terra-money/terra.js": "^3.1.3",
         "@terra-money/terra.js": "^3.1.3",
@@ -44,13 +44,13 @@
       }
       }
     },
     },
     "../p2w-sdk/js": {
     "../p2w-sdk/js": {
-      "name": "@certusone/p2w-sdk",
-      "version": "0.1.0",
+      "name": "@pythnetwork/p2w-sdk-js",
+      "version": "1.0.0",
       "license": "MIT",
       "license": "MIT",
       "dependencies": {
       "dependencies": {
         "@certusone/wormhole-sdk": "0.2.1",
         "@certusone/wormhole-sdk": "0.2.1",
         "@improbable-eng/grpc-web-node-http-transport": "^0.14.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": {
       "devDependencies": {
         "@openzeppelin/contracts": "^4.2.0",
         "@openzeppelin/contracts": "^4.2.0",
@@ -699,10 +699,6 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
       "dev": true
     },
     },
-    "node_modules/@certusone/p2w-sdk": {
-      "resolved": "../p2w-sdk/js",
-      "link": true
-    },
     "node_modules/@certusone/wormhole-sdk": {
     "node_modules/@certusone/wormhole-sdk": {
       "version": "0.1.4",
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.4.tgz",
       "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",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
     },
     },
+    "node_modules/@pythnetwork/p2w-sdk-js": {
+      "resolved": "../p2w-sdk/js",
+      "link": true
+    },
     "node_modules/@sinonjs/commons": {
     "node_modules/@sinonjs/commons": {
       "version": "1.8.3",
       "version": "1.8.3",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -8387,24 +8387,6 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
       "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": {
     "@certusone/wormhole-sdk": {
       "version": "0.1.4",
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.4.tgz",
       "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",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
       "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": {
     "@sinonjs/commons": {
       "version": "1.8.3",
       "version": "1.8.3",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",

+ 1 - 1
third_party/pyth/p2w-relay/package.json

@@ -30,7 +30,7 @@
     "typescript": "^4.3.5"
     "typescript": "^4.3.5"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@certusone/p2w-sdk": "file:../p2w-sdk/js",
+    "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js",
     "@certusone/wormhole-sdk": "^0.1.4",
     "@certusone/wormhole-sdk": "^0.1.4",
     "@certusone/wormhole-spydk": "^0.0.1",
     "@certusone/wormhole-spydk": "^0.0.1",
     "@solana/spl-token": "^0.1.8",
     "@solana/spl-token": "^0.1.8",

+ 1 - 1
third_party/pyth/p2w-relay/src/listen.ts

@@ -14,7 +14,7 @@ import {
   subscribeSignedVAA,
   subscribeSignedVAA,
 } from "@certusone/wormhole-spydk";
 } 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";
 import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
 
 

+ 1 - 1
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 { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
 
 
 import { PythUpgradable__factory, PythUpgradable } from "../evm/bindings/";
 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;
 let WH_WASM: any = null;
 
 

+ 1 - 1
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 * as helpers from "./helpers";
 import { logger } from "./helpers";
 import { logger } from "./helpers";
 import { PromHelper } from "./promHelpers";
 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();
 const mutex = new Mutex();
 let condition = new CondVar();
 let condition = new CondVar();

+ 11 - 11
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,
     "lockfileVersion": 2,
     "requires": true,
     "requires": true,
     "packages": {
     "packages": {
         "": {
         "": {
-            "name": "@certusone/p2w-sdk",
-            "version": "0.1.0",
+            "name": "@pythnetwork/p2w-sdk-js",
+            "version": "1.0.0",
             "license": "MIT",
             "license": "MIT",
             "dependencies": {
             "dependencies": {
                 "@certusone/wormhole-sdk": "0.2.1",
                 "@certusone/wormhole-sdk": "0.2.1",
                 "@improbable-eng/grpc-web-node-http-transport": "^0.14.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": {
             "devDependencies": {
                 "@openzeppelin/contracts": "^4.2.0",
                 "@openzeppelin/contracts": "^4.2.0",
@@ -913,9 +913,9 @@
             "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
             "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
         },
         },
         "node_modules/@pythnetwork/pyth-sdk-js": {
         "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": {
         "node_modules/@solana/buffer-layout": {
             "version": "4.0.0",
             "version": "4.0.0",
@@ -3236,9 +3236,9 @@
             "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
             "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
         },
         },
         "@pythnetwork/pyth-sdk-js": {
         "@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": {
         "@solana/buffer-layout": {
             "version": "4.0.0",
             "version": "4.0.0",

+ 4 - 3
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",
     "description": "TypeScript library for interacting with Pyth2Wormhole",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
     "main": "lib/index.js",
     "main": "lib/index.js",
@@ -11,6 +11,7 @@
         "build": "npm run build-lib",
         "build": "npm run build-lib",
         "build-lib": "npm run copy-artifacts && tsc",
         "build-lib": "npm run copy-artifacts && tsc",
         "build-watch": "npm run copy-artifacts && tsc --watch",
         "build-watch": "npm run copy-artifacts && tsc --watch",
+        "format": "prettier --write \"src/**/*.ts\"",
         "copy-artifacts": "node scripts/copyWasm.cjs",
         "copy-artifacts": "node scripts/copyWasm.cjs",
         "lint": "tslint -p tsconfig.json",
         "lint": "tslint -p tsconfig.json",
         "postversion": "git push && git push --tags",
         "postversion": "git push && git push --tags",
@@ -41,7 +42,7 @@
     "dependencies": {
     "dependencies": {
         "@certusone/wormhole-sdk": "0.2.1",
         "@certusone/wormhole-sdk": "0.2.1",
         "@improbable-eng/grpc-web-node-http-transport": "^0.14.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": {
     "bugs": {
         "url": "https://github.com/pyth-network/pyth-crosschain/issues"
         "url": "https://github.com/pyth-network/pyth-crosschain/issues"

+ 104 - 79
third_party/pyth/p2w-sdk/js/src/index.ts

@@ -1,116 +1,141 @@
 import { getSignedVAA, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
 import { getSignedVAA, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
 import { zeroPad } from "ethers/lib/utils";
 import { zeroPad } from "ethers/lib/utils";
 import { PublicKey } from "@solana/web3.js";
 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() {
 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 = {
 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 = {
 export type BatchPriceAttestation = {
-    priceAttestations: PriceAttestation[];
+  priceAttestations: PriceAttestation[];
 };
 };
 
 
 export async function parseBatchPriceAttestation(
 export async function parseBatchPriceAttestation(
-    arr: Buffer
+  arr: Buffer
 ): Promise<BatchPriceAttestation> {
 ): Promise<BatchPriceAttestation> {
-    
-    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
 // 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)
 // new batch with exact same symbols (and ignore the old one)
 export function getBatchAttestationHashKey(
 export function getBatchAttestationHashKey(
-    batchAttestation: BatchPriceAttestation
+  batchAttestation: BatchPriceAttestation
 ): string {
 ): 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<any> {
-    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<any> {
+  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 {
 function sol_addr2buf(addr: PublicKey): Buffer {
-    return Buffer.from(zeroPad(addr.toBytes(), 32));
+  return Buffer.from(zeroPad(addr.toBytes(), 32));
 }
 }

+ 35 - 35
third_party/pyth/price-service/package-lock.json

@@ -1,18 +1,18 @@
 {
 {
   "name": "@pythnetwork/pyth-price-service",
   "name": "@pythnetwork/pyth-price-service",
-  "version": "1.4.1",
+  "version": "2.0.0",
   "lockfileVersion": 2,
   "lockfileVersion": 2,
   "requires": true,
   "requires": true,
   "packages": {
   "packages": {
     "": {
     "": {
       "name": "@pythnetwork/pyth-price-service",
       "name": "@pythnetwork/pyth-price-service",
-      "version": "1.4.1",
+      "version": "2.0.0",
       "license": "Apache-2.0",
       "license": "Apache-2.0",
       "dependencies": {
       "dependencies": {
-        "@certusone/p2w-sdk": "file:../p2w-sdk/js",
         "@certusone/wormhole-sdk": "^0.1.4",
         "@certusone/wormhole-sdk": "^0.1.4",
         "@certusone/wormhole-spydk": "^0.0.1",
         "@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/cors": "^2.8.12",
         "@types/express": "^4.17.13",
         "@types/express": "^4.17.13",
         "@types/morgan": "^1.9.3",
         "@types/morgan": "^1.9.3",
@@ -50,13 +50,13 @@
       }
       }
     },
     },
     "../p2w-sdk/js": {
     "../p2w-sdk/js": {
-      "name": "@certusone/p2w-sdk",
-      "version": "0.1.0",
+      "name": "@pythnetwork/p2w-sdk-js",
+      "version": "1.0.0",
       "license": "MIT",
       "license": "MIT",
       "dependencies": {
       "dependencies": {
         "@certusone/wormhole-sdk": "0.2.1",
         "@certusone/wormhole-sdk": "0.2.1",
         "@improbable-eng/grpc-web-node-http-transport": "^0.14.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": {
       "devDependencies": {
         "@openzeppelin/contracts": "^4.2.0",
         "@openzeppelin/contracts": "^4.2.0",
@@ -616,10 +616,6 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
       "dev": true
     },
     },
-    "node_modules/@certusone/p2w-sdk": {
-      "resolved": "../p2w-sdk/js",
-      "link": true
-    },
     "node_modules/@certusone/wormhole-sdk": {
     "node_modules/@certusone/wormhole-sdk": {
       "version": "0.1.6",
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.6.tgz",
       "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",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
     },
     },
+    "node_modules/@pythnetwork/p2w-sdk-js": {
+      "resolved": "../p2w-sdk/js",
+      "link": true
+    },
     "node_modules/@pythnetwork/pyth-sdk-js": {
     "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": {
     "node_modules/@sideway/address": {
       "version": "4.1.4",
       "version": "4.1.4",
@@ -9435,24 +9435,6 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
       "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": {
     "@certusone/wormhole-sdk": {
       "version": "0.1.6",
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.1.6.tgz",
       "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",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
       "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": {
     "@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": {
     "@sideway/address": {
       "version": "4.1.4",
       "version": "4.1.4",

+ 7 - 4
third_party/pyth/price-service/package.json

@@ -1,13 +1,16 @@
 {
 {
   "name": "@pythnetwork/pyth-price-service",
   "name": "@pythnetwork/pyth-price-service",
-  "version": "1.4.1",
+  "version": "2.0.0",
   "description": "Pyth Price Service",
   "description": "Pyth Price Service",
   "main": "index.js",
   "main": "index.js",
   "scripts": {
   "scripts": {
     "format": "prettier --write \"src/**/*.ts\"",
     "format": "prettier --write \"src/**/*.ts\"",
     "build": "tsc",
     "build": "tsc",
     "start": "node lib/index.js",
     "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": "",
   "author": "",
   "license": "Apache-2.0",
   "license": "Apache-2.0",
@@ -25,10 +28,10 @@
     "typescript": "^4.3.5"
     "typescript": "^4.3.5"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@certusone/p2w-sdk": "file:../p2w-sdk/js",
+    "@pythnetwork/p2w-sdk-js": "file:../p2w-sdk/js",
     "@certusone/wormhole-sdk": "^0.1.4",
     "@certusone/wormhole-sdk": "^0.1.4",
     "@certusone/wormhole-spydk": "^0.0.1",
     "@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/cors": "^2.8.12",
     "@types/express": "^4.17.13",
     "@types/express": "^4.17.13",
     "@types/morgan": "^1.9.3",
     "@types/morgan": "^1.9.3",

+ 21 - 18
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 { PriceStore, PriceInfo } from "../listen";
 import { RestAPI } from "../rest";
 import { RestAPI } from "../rest";
 import { Express } from "express";
 import { Express } from "express";
@@ -14,20 +14,19 @@ function expandTo64Len(id: string): string {
 
 
 function dummyPriceFeed(id: string): PriceFeed {
 function dummyPriceFeed(id: string): PriceFeed {
   return new PriceFeed({
   return new PriceFeed({
-    conf: "0",
-    emaConf: "1",
-    emaPrice: "2",
-    expo: 4,
+    emaPrice: new Price({
+      conf: "1",
+      expo: 2,
+      price: "3",
+      publishTime: 4,
+    }),
     id,
     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"),
     dummyPriceInfoPair(expandTo64Len("10101"), 3, "bidbidbid"),
   ]);
   ]);
 
 
-  let priceInfo: PriceStore = {
+  const priceInfo: PriceStore = {
     getLatestPriceInfo: (priceFeedId: string) => {
     getLatestPriceInfo: (priceFeedId: string) => {
       return priceInfoMap.get(priceFeedId);
       return priceInfoMap.get(priceFeedId);
     },
     },
-    addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => {},
+    addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => undefined,
     getPriceIds: () => new Set(),
     getPriceIds: () => new Set(),
   };
   };
 
 
@@ -72,7 +71,9 @@ beforeAll(async () => {
 describe("Latest Price Feed Endpoint", () => {
 describe("Latest Price Feed Endpoint", () => {
   test("When called with valid ids, returns correct price feed", async () => {
   test("When called with valid ids, returns correct price feed", async () => {
     const ids = [expandTo64Len("abcd"), expandTo64Len("3456")];
     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.status).toBe(StatusCodes.OK);
     expect(resp.body.length).toBe(2);
     expect(resp.body.length).toBe(2);
     expect(resp.body).toContainEqual(dummyPriceFeed(ids[0]).toJson());
     expect(resp.body).toContainEqual(dummyPriceFeed(ids[0]).toJson());
@@ -85,7 +86,9 @@ describe("Latest Price Feed Endpoint", () => {
       expandTo64Len("3456"),
       expandTo64Len("3456"),
       expandTo64Len("effe"),
       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.status).toBe(StatusCodes.BAD_REQUEST);
     expect(resp.body.message).toContain(ids[0]);
     expect(resp.body.message).toContain(ids[0]);
     expect(resp.body.message).not.toContain(ids[1]);
     expect(resp.body.message).not.toContain(ids[1]);

+ 36 - 38
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 { Server } from "http";
 import { WebSocket, WebSocketServer } from "ws";
 import { WebSocket, WebSocketServer } from "ws";
 import { sleep } from "../helpers";
 import { sleep } from "../helpers";
@@ -33,12 +33,12 @@ function dummyPriceMetadata(
 function dummyPriceInfo(
 function dummyPriceInfo(
   id: HexString,
   id: HexString,
   vaa: HexString,
   vaa: HexString,
-  priceMetadata: any
+  dummyPriceMetadataValue: any
 ): PriceInfo {
 ): PriceInfo {
   return {
   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),
     priceFeed: dummyPriceFeed(id),
     vaaBytes: Buffer.from(vaa, "hex").toString("binary"),
     vaaBytes: Buffer.from(vaa, "hex").toString("binary"),
   };
   };
@@ -46,20 +46,19 @@ function dummyPriceInfo(
 
 
 function dummyPriceFeed(id: string): PriceFeed {
 function dummyPriceFeed(id: string): PriceFeed {
   return PriceFeed.fromJson({
   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,
     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),
     dummyPriceInfo(expandTo64Len("6789"), "bidbidbid", priceMetadata),
   ];
   ];
 
 
-  let priceInfo: PriceStore = {
+  const priceInfo: PriceStore = {
     getLatestPriceInfo: (_priceFeedId: string) => undefined,
     getLatestPriceInfo: (_priceFeedId: string) => undefined,
     addUpdateListener: (_callback: (priceInfo: PriceInfo) => any) => 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);
   api = new WebSocketAPI(priceInfo);
@@ -123,9 +121,9 @@ afterAll(async () => {
 
 
 describe("Client receives data", () => {
 describe("Client receives data", () => {
   test("When subscribes with valid ids without verbose flag, returns correct price feed", async () => {
   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],
       ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id],
       type: "subscribe",
       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 () => {
   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],
       ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id],
       type: "subscribe",
       type: "subscribe",
       verbose: true,
       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 () => {
   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],
       ids: [priceInfos[0].priceFeed.id, priceInfos[1].priceFeed.id],
       type: "subscribe",
       type: "subscribe",
       verbose: false,
       verbose: false,
@@ -248,9 +246,9 @@ describe("Client receives data", () => {
   });
   });
 
 
   test("When subscribes with invalid ids, returns error", async () => {
   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")],
       ids: [expandTo64Len("aaaa")],
       type: "subscribe",
       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 () => {
   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],
       ids: [priceInfos[0].priceFeed.id],
       type: "subscribe",
       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 () => {
   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 = {
     let message: ClientMessage = {
       ids: [priceInfos[0].priceFeed.id],
       ids: [priceInfos[0].priceFeed.id],
@@ -355,9 +353,9 @@ describe("Client receives data", () => {
   });
   });
 
 
   test("Unsubscribe on not subscribed price feed is ok", async () => {
   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],
       ids: [priceInfos[0].priceFeed.id],
       type: "unsubscribe",
       type: "unsubscribe",
     };
     };
@@ -376,17 +374,17 @@ describe("Client receives data", () => {
   });
   });
 
 
   test("Multiple clients with different price feed works", async () => {
   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],
       ids: [priceInfos[0].priceFeed.id],
       type: "subscribe",
       type: "subscribe",
     };
     };
 
 
     client1.send(JSON.stringify(message1));
     client1.send(JSON.stringify(message1));
 
 
-    let message2: ClientMessage = {
+    const message2: ClientMessage = {
       ids: [priceInfos[1].priceFeed.id],
       ids: [priceInfos[1].priceFeed.id],
       type: "subscribe",
       type: "subscribe",
     };
     };

+ 2 - 2
third_party/pyth/price-service/src/helpers.ts

@@ -9,9 +9,9 @@ export function sleep(ms: number) {
 
 
 // Shorthand for optional/mandatory envs
 // Shorthand for optional/mandatory envs
 export function envOrErr(env: string): string {
 export function envOrErr(env: string): string {
-  let val = process.env[env];
+  const val = process.env[env];
   if (!val) {
   if (!val) {
-    throw `environment variable "${env}" must be set`;
+    throw new Error(`environment variable "${env}" must be set`);
   }
   }
   return String(process.env[env]);
   return String(process.env[env]);
 }
 }

+ 10 - 4
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;
   configFile = process.env.PYTH_PRICE_SERVICE_CONFIG;
 }
 }
 
 
+// tslint:disable:no-console
 console.log("Loading config file [%s]", configFile);
 console.log("Loading config file [%s]", configFile);
+// tslint:disable:no-var-requires
 require("dotenv").config({ path: configFile });
 require("dotenv").config({ path: configFile });
 
 
 setDefaultWasm("node");
 setDefaultWasm("node");
@@ -23,7 +25,7 @@ initLogger({ logLevel: process.env.LOG_LEVEL });
 async function run() {
 async function run() {
   const promClient = new PromClient({
   const promClient = new PromClient({
     name: "price_service",
     name: "price_service",
-    port: parseInt(envOrErr("PROM_PORT")),
+    port: parseInt(envOrErr("PROM_PORT"), 10),
   });
   });
 
 
   const listener = new Listener(
   const listener = new Listener(
@@ -32,9 +34,13 @@ async function run() {
       filtersRaw: process.env.SPY_SERVICE_FILTERS,
       filtersRaw: process.env.SPY_SERVICE_FILTERS,
       readiness: {
       readiness: {
         spySyncTimeSeconds: parseInt(
         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
     promClient
@@ -45,7 +51,7 @@ async function run() {
 
 
   const restAPI = new RestAPI(
   const restAPI = new RestAPI(
     {
     {
-      port: parseInt(envOrErr("REST_PORT")),
+      port: parseInt(envOrErr("REST_PORT"), 10),
     },
     },
     listener,
     listener,
     isReady,
     isReady,

+ 18 - 17
third_party/pyth/price-service/src/listen.ts

@@ -1,12 +1,12 @@
 import {
 import {
   ChainId,
   ChainId,
   hexToUint8Array,
   hexToUint8Array,
-  uint8ArrayToHex
+  uint8ArrayToHex,
 } from "@certusone/wormhole-sdk";
 } from "@certusone/wormhole-sdk";
 
 
 import {
 import {
   createSpyRPCServiceClient,
   createSpyRPCServiceClient,
-  subscribeSignedVAA
+  subscribeSignedVAA,
 } from "@certusone/wormhole-spydk";
 } from "@certusone/wormhole-spydk";
 
 
 import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
 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 {
 import {
   getBatchSummary,
   getBatchSummary,
   parseBatchPriceAttestation,
   parseBatchPriceAttestation,
-  priceAttestationToPriceFeed
-} from "@certusone/p2w-sdk";
+  priceAttestationToPriceFeed,
+} from "@pythnetwork/p2w-sdk-js";
 import {
 import {
   FilterEntry,
   FilterEntry,
-  SubscribeSignedVAAResponse
+  SubscribeSignedVAAResponse,
 } from "@certusone/wormhole-spydk/lib/cjs/proto/spy/v1/spy";
 } from "@certusone/wormhole-spydk/lib/cjs/proto/spy/v1/spy";
 import { ClientReadableStream } from "@grpc/grpc-js";
 import { ClientReadableStream } from "@grpc/grpc-js";
 import { HexString, PriceFeed } from "@pythnetwork/pyth-sdk-js";
 import { HexString, PriceFeed } from "@pythnetwork/pyth-sdk-js";
@@ -75,12 +75,12 @@ export class Listener implements PriceStore {
       return;
       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: {
         emitterFilter: {
           chainId: myChainId,
           chainId: myChainId,
           emitterAddress: myEmitterAddress,
           emitterAddress: myEmitterAddress,
@@ -165,10 +165,10 @@ export class Listener implements PriceStore {
       return;
       return;
     }
     }
 
 
-    let isAnyPriceNew = batchAttestation.priceAttestations.some(
+    const isAnyPriceNew = batchAttestation.priceAttestations.some(
       (priceAttestation) => {
       (priceAttestation) => {
         const key = priceAttestation.priceId;
         const key = priceAttestation.priceId;
-        let lastAttestationTime =
+        const lastAttestationTime =
           this.priceFeedVaaMap.get(key)?.attestationTime;
           this.priceFeedVaaMap.get(key)?.attestationTime;
         return (
         return (
           lastAttestationTime === undefined ||
           lastAttestationTime === undefined ||
@@ -181,10 +181,11 @@ export class Listener implements PriceStore {
       return;
       return;
     }
     }
 
 
-    for (let priceAttestation of batchAttestation.priceAttestations) {
+    for (const priceAttestation of batchAttestation.priceAttestations) {
       const key = priceAttestation.priceId;
       const key = priceAttestation.priceId;
 
 
-      let lastAttestationTime = this.priceFeedVaaMap.get(key)?.attestationTime;
+      const lastAttestationTime =
+        this.priceFeedVaaMap.get(key)?.attestationTime;
 
 
       if (
       if (
         lastAttestationTime === undefined ||
         lastAttestationTime === undefined ||
@@ -193,14 +194,14 @@ export class Listener implements PriceStore {
         const priceFeed = priceAttestationToPriceFeed(priceAttestation);
         const priceFeed = priceAttestationToPriceFeed(priceAttestation);
         const priceInfo = {
         const priceInfo = {
           seqNum: parsedVAA.sequence,
           seqNum: parsedVAA.sequence,
-          vaaBytes: vaaBytes,
+          vaaBytes,
           attestationTime: priceAttestation.attestationTime,
           attestationTime: priceAttestation.attestationTime,
           priceFeed,
           priceFeed,
           emitterChainId: parsedVAA.emitter_chain,
           emitterChainId: parsedVAA.emitter_chain,
         };
         };
         this.priceFeedVaaMap.set(key, priceInfo);
         this.priceFeedVaaMap.set(key, priceInfo);
 
 
-        for (let callback of this.updateCallbacks) {
+        for (const callback of this.updateCallbacks) {
           callback(priceInfo);
           callback(priceInfo);
         }
         }
       }
       }
@@ -233,7 +234,7 @@ export class Listener implements PriceStore {
   }
   }
 
 
   isReady(): boolean {
   isReady(): boolean {
-    let currentTime: TimestampInSec = Math.floor(Date.now() / 1000);
+    const currentTime: TimestampInSec = Math.floor(Date.now() / 1000);
     if (
     if (
       this.spyConnectionTime === undefined ||
       this.spyConnectionTime === undefined ||
       currentTime <
       currentTime <

+ 1 - 0
third_party/pyth/price-service/src/logging.ts

@@ -12,6 +12,7 @@ export function initLogger(config?: { logLevel?: string }) {
   }
   }
 
 
   let transport: any;
   let transport: any;
+  // tslint:disable:no-console
   console.log("p2w_api is logging to the console at level [%s]", logLevel);
   console.log("p2w_api is logging to the console at level [%s]", logLevel);
 
 
   transport = new winston.transports.Console({
   transport = new winston.transports.Console({

+ 5 - 5
third_party/pyth/price-service/src/promClient.ts

@@ -73,8 +73,8 @@ export class PromClient {
   addResponseTime(path: string, status: number, duration: DurationInMs) {
   addResponseTime(path: string, status: number, duration: DurationInMs) {
     this.apiResponseTimeSummary.observe(
     this.apiResponseTimeSummary.observe(
       {
       {
-        path: path,
-        status: status,
+        path,
+        status,
       },
       },
       duration
       duration
     );
     );
@@ -87,7 +87,7 @@ export class PromClient {
   ) {
   ) {
     this.apiRequestsPriceFreshnessHistogram.observe(
     this.apiRequestsPriceFreshnessHistogram.observe(
       {
       {
-        path: path,
+        path,
         price_id: priceId,
         price_id: priceId,
       },
       },
       duration
       duration
@@ -96,8 +96,8 @@ export class PromClient {
 
 
   addWebSocketInteraction(type: string, status: "ok" | "err") {
   addWebSocketInteraction(type: string, status: "ok" | "err") {
     this.webSocketInteractionCounter.inc({
     this.webSocketInteractionCounter.inc({
-      type: type,
-      status: status,
+      type,
+      status,
     });
     });
   }
   }
 }
 }

+ 28 - 25
third_party/pyth/price-service/src/rest.ts

@@ -71,7 +71,7 @@ export class RestAPI {
       })
       })
     );
     );
 
 
-    let endpoints: string[] = [];
+    const endpoints: string[] = [];
 
 
     const latestVaasInputSchema: schema = {
     const latestVaasInputSchema: schema = {
       query: Joi.object({
       query: Joi.object({
@@ -84,20 +84,20 @@ export class RestAPI {
       "/api/latest_vaas",
       "/api/latest_vaas",
       validate(latestVaasInputSchema),
       validate(latestVaasInputSchema),
       (req: Request, res: Response) => {
       (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
         // 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.
         // key of a vaa and deduplicate using a map of seqnum to vaa bytes.
-        let vaaMap = new Map<number, string>();
+        const vaaMap = new Map<number, string>();
 
 
-        let notFoundIds: string[] = [];
+        const notFoundIds: string[] = [];
 
 
         for (let id of priceIds) {
         for (let id of priceIds) {
           if (id.startsWith("0x")) {
           if (id.startsWith("0x")) {
             id = id.substring(2);
             id = id.substring(2);
           }
           }
 
 
-          let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id);
+          const latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id);
 
 
           if (latestPriceInfo === undefined) {
           if (latestPriceInfo === undefined) {
             notFoundIds.push(id);
             notFoundIds.push(id);
@@ -105,7 +105,8 @@ export class RestAPI {
           }
           }
 
 
           const freshness: DurationInSec =
           const freshness: DurationInSec =
-            new Date().getTime() / 1000 - latestPriceInfo.priceFeed.publishTime;
+            new Date().getTime() / 1000 -
+            latestPriceInfo.priceFeed.getPriceUnchecked().publishTime;
           this.promClient?.addApiRequestsPriceFreshness(
           this.promClient?.addApiRequestsPriceFreshness(
             req.path,
             req.path,
             id,
             id,
@@ -142,20 +143,20 @@ export class RestAPI {
       "/api/latest_price_feeds",
       "/api/latest_price_feeds",
       validate(latestPriceFeedsInputSchema),
       validate(latestPriceFeedsInputSchema),
       (req: Request, res: Response) => {
       (req: Request, res: Response) => {
-        let priceIds = req.query.ids as string[];
+        const priceIds = req.query.ids as string[];
         // verbose is optional, default to false
         // 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) {
         for (let id of priceIds) {
           if (id.startsWith("0x")) {
           if (id.startsWith("0x")) {
             id = id.substring(2);
             id = id.substring(2);
           }
           }
 
 
-          let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id);
+          const latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id);
 
 
           if (latestPriceInfo === undefined) {
           if (latestPriceInfo === undefined) {
             notFoundIds.push(id);
             notFoundIds.push(id);
@@ -163,7 +164,8 @@ export class RestAPI {
           }
           }
 
 
           const freshness: DurationInSec =
           const freshness: DurationInSec =
-            new Date().getTime() / 1000 - latestPriceInfo.priceFeed.publishTime;
+            new Date().getTime() / 1000 -
+            latestPriceInfo.priceFeed.getEmaPriceUnchecked().publishTime;
           this.promClient?.addApiRequestsPriceFreshness(
           this.promClient?.addApiRequestsPriceFreshness(
             req.path,
             req.path,
             id,
             id,
@@ -209,29 +211,30 @@ export class RestAPI {
         threshold: Joi.number().required(),
         threshold: Joi.number().required(),
       }).required(),
       }).required(),
     };
     };
-    app.get("/api/stale_feeds",
+    app.get(
+      "/api/stale_feeds",
       validate(staleFeedsInputSchema),
       validate(staleFeedsInputSchema),
       (req: Request, res: Response) => {
       (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<HexString, number> = {}
+        const priceIds = [...this.priceFeedVaaInfo.getPriceIds()];
+        const stalePrices: Record<HexString, number> = {};
 
 
-        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) {
           if (latency > stalenessThresholdSeconds) {
-            stalePrices[priceId] = latency
+            stalePrices[priceId] = latency;
           }
           }
         }
         }
 
 
         res.json(stalePrices);
         res.json(stalePrices);
       }
       }
     );
     );
-    endpoints.push(
-      "/api/stale_feeds?threshold=<staleness_threshold_seconds>"
-    );
+    endpoints.push("/api/stale_feeds?threshold=<staleness_threshold_seconds>");
 
 
     app.get("/ready", (_, res: Response) => {
     app.get("/ready", (_, res: Response) => {
       if (this.isReady!()) {
       if (this.isReady!()) {
@@ -252,7 +255,7 @@ export class RestAPI {
 
 
     app.get("/", (_, res: Response) => res.json(endpoints));
     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) {
       if (err instanceof ValidationError) {
         return res.status(err.statusCode).json(err);
         return res.status(err.statusCode).json(err);
       }
       }
@@ -268,7 +271,7 @@ export class RestAPI {
   }
   }
 
 
   async run(): Promise<Server> {
   async run(): Promise<Server> {
-    let app = await this.createApp();
+    const app = await this.createApp();
     return app.listen(this.port, () =>
     return app.listen(this.port, () =>
       logger.debug("listening on REST port " + this.port)
       logger.debug("listening on REST port " + this.port)
     );
     );

+ 18 - 14
third_party/pyth/price-service/src/ws.ts

@@ -82,21 +82,25 @@ export class WebSocketAPI {
       return;
       return;
     }
     }
 
 
-    const clients: Set<WebSocket> = this.priceFeedClients.get(priceInfo.priceFeed.id)!;
+    const clients: Set<WebSocket> = this.priceFeedClients.get(
+      priceInfo.priceFeed.id
+    )!;
     logger.info(
     logger.info(
       `Sending ${priceInfo.priceFeed.id} price update to ${
       `Sending ${priceInfo.priceFeed.id} price update to ${
         clients.size
         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");
       this.promClient?.addWebSocketInteraction("server_update", "ok");
 
 
-      let verbose = this.priceFeedClientsVerbosity
+      const verbose = this.priceFeedClientsVerbosity
         .get(priceInfo.priceFeed.id)!
         .get(priceInfo.priceFeed.id)!
         .get(client);
         .get(client);
 
 
-      let priceUpdate: ServerPriceUpdate = verbose
+      const priceUpdate: ServerPriceUpdate = verbose
         ? {
         ? {
             type: "price_update",
             type: "price_update",
             price_feed: {
             price_feed: {
@@ -118,7 +122,7 @@ export class WebSocketAPI {
   }
   }
 
 
   clientClose(ws: WebSocket) {
   clientClose(ws: WebSocket) {
-    for (let clients of this.priceFeedClients.values()) {
+    for (const clients of this.priceFeedClients.values()) {
       if (clients.has(ws)) {
       if (clients.has(ws)) {
         clients.delete(ws);
         clients.delete(ws);
       }
       }
@@ -130,13 +134,13 @@ export class WebSocketAPI {
 
 
   handleMessage(ws: WebSocket, data: RawData) {
   handleMessage(ws: WebSocket, data: RawData) {
     try {
     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) {
       if (validationResult.error !== undefined) {
         throw validationResult.error;
         throw validationResult.error;
       }
       }
 
 
-      let message = jsonData as ClientMessage;
+      const message = jsonData as ClientMessage;
 
 
       message.ids = message.ids.map((id) => {
       message.ids = message.ids.map((id) => {
         if (id.startsWith("0x")) {
         if (id.startsWith("0x")) {
@@ -146,7 +150,7 @@ export class WebSocketAPI {
       });
       });
 
 
       const availableIds = this.priceFeedVaaInfo.getPriceIds();
       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) {
       if (notFoundIds.length > 0) {
         throw new Error(
         throw new Error(
@@ -154,7 +158,7 @@ export class WebSocketAPI {
         );
         );
       }
       }
 
 
-      if (message.type == "subscribe") {
+      if (message.type === "subscribe") {
         message.ids.forEach((id) =>
         message.ids.forEach((id) =>
           this.addPriceFeedClient(ws, id, message.verbose === true)
           this.addPriceFeedClient(ws, id, message.verbose === true)
         );
         );
@@ -162,7 +166,7 @@ export class WebSocketAPI {
         message.ids.forEach((id) => this.delPriceFeedClient(ws, id));
         message.ids.forEach((id) => this.delPriceFeedClient(ws, id));
       }
       }
     } catch (e: any) {
     } catch (e: any) {
-      let response: ServerResponse = {
+      const errorResponse: ServerResponse = {
         type: "response",
         type: "response",
         status: "error",
         status: "error",
         error: e.message,
         error: e.message,
@@ -173,7 +177,7 @@ export class WebSocketAPI {
       );
       );
       this.promClient?.addWebSocketInteraction("client_message", "err");
       this.promClient?.addWebSocketInteraction("client_message", "err");
 
 
-      ws.send(JSON.stringify(response));
+      ws.send(JSON.stringify(errorResponse));
       return;
       return;
     }
     }
 
 
@@ -182,7 +186,7 @@ export class WebSocketAPI {
     );
     );
     this.promClient?.addWebSocketInteraction("client_message", "ok");
     this.promClient?.addWebSocketInteraction("client_message", "ok");
 
 
-    let response: ServerResponse = {
+    const response: ServerResponse = {
       type: "response",
       type: "response",
       status: "success",
       status: "success",
     };
     };

+ 9 - 0
third_party/pyth/price-service/tslint.json

@@ -0,0 +1,9 @@
+
+{
+  "extends": ["tslint:recommended", "tslint-config-prettier"],
+  "rules": {
+    "max-classes-per-file": {
+      "severity": "off"
+    }
+  }
+}