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
This commit is contained in:
parent
5ff244941c
commit
d7f436a856
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<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
|
||||
// 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<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,
|
||||
});
|
||||
|
||||
function computePrice(rawPrice: string, expo: number): number {
|
||||
return Number(rawPrice) * 10 ** expo;
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 <
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<number, string>();
|
||||
const vaaMap = new Map<number, string>();
|
||||
|
||||
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<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) {
|
||||
stalePrices[priceId] = latency
|
||||
stalePrices[priceId] = latency;
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
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<Server> {
|
||||
let app = await this.createApp();
|
||||
const app = await this.createApp();
|
||||
return app.listen(this.port, () =>
|
||||
logger.debug("listening on REST port " + this.port)
|
||||
);
|
||||
|
|
|
@ -82,21 +82,25 @@ export class WebSocketAPI {
|
|||
return;
|
||||
}
|
||||
|
||||
const clients: Set<WebSocket> = this.priceFeedClients.get(priceInfo.priceFeed.id)!;
|
||||
const clients: Set<WebSocket> = 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",
|
||||
};
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"max-classes-per-file": {
|
||||
"severity": "off"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue